cinch 1.1.3 → 2.0.0.pre.1

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 (83) hide show
  1. data/LICENSE +1 -0
  2. data/README.md +3 -3
  3. data/docs/bot_options.md +435 -0
  4. data/docs/changes.md +440 -0
  5. data/docs/common_mistakes.md +35 -0
  6. data/docs/common_tasks.md +47 -0
  7. data/docs/encodings.md +67 -0
  8. data/docs/events.md +272 -0
  9. data/docs/logging.md +5 -0
  10. data/docs/migrating.md +267 -0
  11. data/docs/readme.md +18 -0
  12. data/examples/plugins/custom_prefix.rb +1 -1
  13. data/examples/plugins/dice_roll.rb +38 -0
  14. data/examples/plugins/lambdas.rb +1 -1
  15. data/examples/plugins/memo.rb +16 -10
  16. data/examples/plugins/url_shorten.rb +1 -0
  17. data/lib/cinch.rb +5 -60
  18. data/lib/cinch/ban.rb +13 -7
  19. data/lib/cinch/bot.rb +228 -403
  20. data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
  21. data/lib/cinch/callback.rb +3 -0
  22. data/lib/cinch/channel.rb +119 -195
  23. data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
  24. data/lib/cinch/configuration.rb +73 -0
  25. data/lib/cinch/configuration/bot.rb +47 -0
  26. data/lib/cinch/configuration/dcc.rb +16 -0
  27. data/lib/cinch/configuration/plugins.rb +41 -0
  28. data/lib/cinch/configuration/sasl.rb +17 -0
  29. data/lib/cinch/configuration/ssl.rb +19 -0
  30. data/lib/cinch/configuration/storage.rb +37 -0
  31. data/lib/cinch/configuration/timeouts.rb +14 -0
  32. data/lib/cinch/constants.rb +531 -369
  33. data/lib/cinch/dcc.rb +12 -0
  34. data/lib/cinch/dcc/dccable_object.rb +37 -0
  35. data/lib/cinch/dcc/incoming.rb +1 -0
  36. data/lib/cinch/dcc/incoming/send.rb +131 -0
  37. data/lib/cinch/dcc/outgoing.rb +1 -0
  38. data/lib/cinch/dcc/outgoing/send.rb +115 -0
  39. data/lib/cinch/exceptions.rb +8 -1
  40. data/lib/cinch/formatting.rb +106 -0
  41. data/lib/cinch/handler.rb +104 -0
  42. data/lib/cinch/handler_list.rb +86 -0
  43. data/lib/cinch/helpers.rb +167 -10
  44. data/lib/cinch/irc.rb +525 -110
  45. data/lib/cinch/isupport.rb +11 -9
  46. data/lib/cinch/logger.rb +168 -0
  47. data/lib/cinch/logger/formatted_logger.rb +72 -55
  48. data/lib/cinch/logger/zcbot_logger.rb +9 -24
  49. data/lib/cinch/logger_list.rb +62 -0
  50. data/lib/cinch/mask.rb +19 -10
  51. data/lib/cinch/message.rb +94 -28
  52. data/lib/cinch/message_queue.rb +70 -28
  53. data/lib/cinch/mode_parser.rb +6 -1
  54. data/lib/cinch/network.rb +104 -0
  55. data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
  56. data/lib/cinch/pattern.rb +24 -4
  57. data/lib/cinch/plugin.rb +352 -177
  58. data/lib/cinch/plugin_list.rb +35 -0
  59. data/lib/cinch/rubyext/float.rb +3 -0
  60. data/lib/cinch/rubyext/module.rb +7 -0
  61. data/lib/cinch/rubyext/string.rb +9 -0
  62. data/lib/cinch/sasl.rb +34 -0
  63. data/lib/cinch/sasl/dh_blowfish.rb +71 -0
  64. data/lib/cinch/sasl/diffie_hellman.rb +47 -0
  65. data/lib/cinch/sasl/mechanism.rb +6 -0
  66. data/lib/cinch/sasl/plain.rb +26 -0
  67. data/lib/cinch/storage.rb +62 -0
  68. data/lib/cinch/storage/null.rb +12 -0
  69. data/lib/cinch/storage/yaml.rb +96 -0
  70. data/lib/cinch/syncable.rb +13 -1
  71. data/lib/cinch/target.rb +144 -0
  72. data/lib/cinch/timer.rb +145 -0
  73. data/lib/cinch/user.rb +169 -225
  74. data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
  75. data/lib/cinch/utilities/deprecation.rb +12 -0
  76. data/lib/cinch/utilities/encoding.rb +54 -0
  77. data/lib/cinch/utilities/kernel.rb +13 -0
  78. data/lib/cinch/utilities/string.rb +13 -0
  79. data/lib/cinch/version.rb +4 -0
  80. metadata +88 -47
  81. data/lib/cinch/logger/logger.rb +0 -44
  82. data/lib/cinch/logger/null_logger.rb +0 -18
  83. data/lib/cinch/rubyext/infinity.rb +0 -1
@@ -1,5 +1,9 @@
1
1
  module Cinch
2
- class CacheManager
2
+ # @api private
3
+ # @since 2.0.0
4
+ # @version 1.1.0
5
+ # @note In prior versions, this class was called CacheManager
6
+ class CachedList
3
7
  include Enumerable
4
8
 
5
9
  def initialize(bot)
@@ -1,4 +1,7 @@
1
1
  module Cinch
2
+ # Class used for encapsulating handlers to prevent them from
3
+ # overwriting instance variables in {Bot}
4
+ #
2
5
  # @api private
3
6
  class Callback
4
7
  include Helpers
@@ -1,62 +1,24 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  require "set"
3
+ require "cinch/target"
3
4
 
4
5
  module Cinch
5
- class Channel
6
+ # @attr limit
7
+ # @attr secret
8
+ # @attr moderated
9
+ # @attr invite_only
10
+ # @attr key
11
+ #
12
+ # @version 2.0.0
13
+ class Channel < Target
6
14
  include Syncable
7
- @channels = {}
8
-
9
- class << self
10
- # Finds or creates a channel.
11
- #
12
- # @param [String] name name of a channel
13
- # @param [Bot] bot a bot
14
- # @return [Channel]
15
- # @see Bot#Channel
16
- # @deprecated See {Bot#channel_manager} and {ChannelManager#find_ensured} instead
17
- # @note This method does not work properly if running more than one bot
18
- # @note This method will be removed in Cinch 2.0.0
19
- def find_ensured(name, bot)
20
- $stderr.puts "Deprecation warning: Beginning with version 1.1.0, Channel.find_ensured should not be used anymore."
21
- puts caller
22
-
23
- downcased_name = name.irc_downcase(bot.irc.isupport["CASEMAPPING"])
24
- @channels[downcased_name] ||= bot.channel_manager.find_ensured(name)
25
- end
26
-
27
- # Finds a channel.
28
- #
29
- # @param [String] name name of a channel
30
- # @return [Channel, nil]
31
- # @deprecated See {Bot#channel_manager} and {ChannelManager#find} instead
32
- # @note This method does not work properly if running more than one bot
33
- # @note This method will be removed in Cinch 2.0.0
34
- def find(name)
35
- $stderr.puts "Deprecation warning: Beginning with version 1.1.0, Channel.find should not be used anymore."
36
- puts caller
37
-
38
- @channels[name]
39
- end
40
-
41
- # @return [Array<Channel>] Returns all channels
42
- # @deprecated See {Bot#channel_manager} and {CacheManager#each} instead
43
- # @note This method does not work properly if running more than one bot
44
- # @note This method will be removed in Cinch 2.0.0
45
- def all
46
- $stderr.puts "Deprecation warning: Beginning with version 1.1.0, User.all should not be used anymore."
47
- puts caller
48
-
49
- @channels.values
50
- end
51
- end
52
-
53
- # @return [Bot]
54
- attr_reader :bot
55
-
56
- # @return [String] the channel's name
57
- attr_reader :name
15
+ include Helpers
58
16
 
59
- # @return [Array<User>] all users in the channel
17
+ # Users are represented by a Hash, mapping individual users to an
18
+ # array of modes (e.g. "o" for opped).
19
+ #
20
+ # @return [Hash<User => Array<String>>] all users in the channel
21
+ # @version 1.1.0
60
22
  attr_reader :users
61
23
  synced_attr_reader :users
62
24
 
@@ -68,6 +30,16 @@ module Cinch
68
30
  attr_reader :bans
69
31
  synced_attr_reader :bans
70
32
 
33
+ # @return [Array<User>] all channel owners
34
+ # @note Only some networks implement this
35
+ attr_reader :owners
36
+ synced_attr_reader :owners
37
+
38
+ # This attribute describes all modes set in the channel. They're
39
+ # represented as a Hash, mapping the mode (e.g. "i", "k", …) to
40
+ # either a value in the case of modes that take an option (e.g.
41
+ # "k" for the channel key) or true.
42
+ #
71
43
  # @return [Hash<String => Object>]
72
44
  attr_reader :modes
73
45
  synced_attr_reader :modes
@@ -76,6 +48,7 @@ module Cinch
76
48
  @name = name
77
49
  @users = Hash.new {|h,k| h[k] = []}
78
50
  @bans = []
51
+ @owners = []
79
52
 
80
53
  @modes = {}
81
54
  # TODO raise if not a channel
@@ -90,13 +63,21 @@ module Cinch
90
63
  unsync(attr)
91
64
  case attr
92
65
  when :users
93
- @bot.raw "NAMES #@name"
66
+ @bot.irc.send "NAMES #@name"
94
67
  when :topic
95
- @bot.raw "TOPIC #@name"
68
+ @bot.irc.send "TOPIC #@name"
96
69
  when :bans
97
- @bot.raw "MODE #@name +b"
70
+ @bot.irc.send "MODE #@name +b"
71
+ when :owners
72
+ if @bot.irc.network.owner_list_mode
73
+ @bot.irc.send "MODE #@name +#{@bot.irc.network.owner_list_mode}"
74
+ else
75
+ # the current IRCd does not support channel owners, so
76
+ # just mark the empty array as synced
77
+ mark_as_synced(:owners)
78
+ end
98
79
  when :modes
99
- @bot.raw "MODE #@name"
80
+ @bot.irc.send "MODE #@name"
100
81
  end
101
82
  end
102
83
  }
@@ -106,37 +87,60 @@ module Cinch
106
87
 
107
88
  # @param [User, String] user An {User}-object or a nickname
108
89
  # @return [Boolean] Check if a user is in the channel
90
+ # @since 1.1.0
91
+ # @version 1.1.2
109
92
  def has_user?(user)
110
- user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
111
- @users.has_key?(user)
93
+ @users.has_key?(User(user))
112
94
  end
113
95
 
114
-
115
96
  # @return [Boolean] true if `user` is opped in the channel
97
+ # @since 1.1.0
116
98
  def opped?(user)
117
- user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
118
- @users[user].include? "o"
99
+ @users[User(user)].include? "o"
119
100
  end
120
101
 
121
102
  # @return [Boolean] true if `user` is half-opped in the channel
103
+ # @since 1.1.0
122
104
  def half_opped?(user)
123
- user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
124
- @users[user].include? "h"
105
+ @users[User(user)].include? "h"
125
106
  end
126
107
 
127
108
  # @return [Boolean] true if `user` is voiced in the channel
109
+ # @since 1.1.0
128
110
  def voiced?(user)
129
- user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
130
- @users[user].include? "v"
111
+ @users[User(user)].include? "v"
112
+ end
113
+
114
+ # @endgroup
115
+
116
+ # @group User groups
117
+ # @return [Array<User>] All ops in the channel
118
+ # @since 2.0.0
119
+ def ops
120
+ @users.select {|user, modes| modes.include?("o")}.keys
131
121
  end
132
122
 
123
+ # @return [Array<User>] All half-ops in the channel
124
+ # @since 2.0.0
125
+ def half_ops
126
+ @users.select {|user, modes| modes.include?("h")}.keys
127
+ end
128
+
129
+ # @return [Array<User>] All voiced users in the channel
130
+ # @since 2.0.0
131
+ def voiced
132
+ @users.select {|user, modes| modes.include?("v")}.keys
133
+ end
134
+
135
+ # @return [Array<User>] All admins in the channel
136
+ # @since 2.0.0
137
+ def admins
138
+ @users.select {|user, modes| modes.include?("a")}.keys
139
+ end
133
140
  # @endgroup
134
141
 
135
142
  # @return [Number] The maximum number of allowed users in the
136
143
  # channel. 0 if unlimited.
137
- attr_accessor :limit
138
- undef_method "limit"
139
- undef_method "limit="
140
144
  def limit
141
145
  @modes["l"].to_i
142
146
  end
@@ -150,9 +154,6 @@ module Cinch
150
154
  end
151
155
 
152
156
  # @return [Boolean] true if the channel is secret (+s)
153
- attr_accessor :secret
154
- undef_method "secret"
155
- undef_method "secret="
156
157
  def secret
157
158
  @modes["s"]
158
159
  end
@@ -166,17 +167,13 @@ module Cinch
166
167
  end
167
168
  end
168
169
 
169
- # @return [Boolean] true if the channel is moderated (only users
170
- # with +o and +v are able to send messages)
171
- attr_accessor :moderated
172
- undef_method "moderated"
173
- undef_method "moderated="
170
+ # @return [Boolean] true if the channel is moderated
174
171
  def moderated
175
172
  @modes["m"]
176
173
  end
177
174
  alias_method :moderated?, :moderated
178
175
 
179
- def moderated=(val)
176
+ def moderated=(bool)
180
177
  if bool
181
178
  mode "+m"
182
179
  else
@@ -185,9 +182,6 @@ module Cinch
185
182
  end
186
183
 
187
184
  # @return [Boolean] true if the channel is invite only (+i)
188
- attr_accessor :invite_only
189
- undef_method "invite_only"
190
- undef_method "invite_only="
191
185
  def invite_only
192
186
  @modes["i"]
193
187
  end
@@ -202,9 +196,6 @@ module Cinch
202
196
  end
203
197
 
204
198
  # @return [String, nil] The channel's key (aka password)
205
- attr_accessor :key
206
- undef_method "key"
207
- undef_method "key="
208
199
  def key
209
200
  @modes["k"]
210
201
  end
@@ -219,61 +210,82 @@ module Cinch
219
210
 
220
211
  # @api private
221
212
  # @return [void]
222
- def sync_modes(all = true)
213
+ def sync_modes
223
214
  unsync :users
224
215
  unsync :bans
225
216
  unsync :modes
226
- @bot.raw "NAMES #@name" if all
227
- @bot.raw "MODE #@name +b" # bans
228
- @bot.raw "MODE #@name"
217
+ unsync :owners
218
+
219
+ if @bot.irc.isupport["WHOX"]
220
+ @bot.irc.send "WHO #@name %acfhnru"
221
+ else
222
+ @bot.irc.send "WHO #@name"
223
+ end
224
+ @bot.irc.send "MODE #@name +b" # bans
225
+ @bot.irc.send "MODE #@name"
226
+ if @bot.irc.network.owner_list_mode
227
+ @bot.irc.send "MODE #@name +#{@bot.irc.network.owner_list_mode}"
228
+ else
229
+ mark_as_synced :owners
230
+ end
229
231
  end
230
232
 
231
233
  # @group Channel Manipulation
232
234
 
233
235
  # Bans someone from the channel.
234
236
  #
235
- # @param [Ban, Mask, User, String] target the mask to ban
237
+ # @param [Mask, String, #mask] target the mask, or an object having a mask, to ban
236
238
  # @return [Mask] the mask used for banning
239
+ # @see #unban #unban for unbanning users
237
240
  def ban(target)
238
241
  mask = Mask.from(target)
239
242
 
240
- @bot.raw "MODE #@name +b #{mask}"
243
+ @bot.irc.send "MODE #@name +b #{mask}"
241
244
  mask
242
245
  end
243
246
 
244
247
  # Unbans someone from the channel.
245
248
  #
246
- # @param [Ban, Mask, User, String] target the mask to unban
249
+ # @param [Mask, String, #mask] target the mask to unban
247
250
  # @return [Mask] the mask used for unbanning
251
+ # @see #ban #ban for banning users
248
252
  def unban(target)
249
253
  mask = Mask.from(target)
250
254
 
251
- @bot.raw "MODE #@name -b #{mask}"
255
+ @bot.irc.send "MODE #@name -b #{mask}"
252
256
  mask
253
257
  end
254
258
 
259
+ # Ops a user.
260
+ #
255
261
  # @param [String, User] user the user to op
256
262
  # @return [void]
257
263
  def op(user)
258
- @bot.raw "MODE #@name +o #{user}"
264
+ @bot.irc.send "MODE #@name +o #{user}"
259
265
  end
260
266
 
267
+ # Deops a user.
268
+ #
261
269
  # @param [String, User] user the user to deop
262
270
  # @return [void]
263
271
  def deop(user)
264
- @bot.raw "MODE #@name -o #{user}"
272
+ @bot.irc.send "MODE #@name -o #{user}"
265
273
  end
266
274
 
275
+ # Voices a user.
276
+ #
267
277
  # @param [String, User] user the user to voice
268
278
  # @return [void]
269
279
  def voice(user)
270
- @bot.raw "MODE #@name +v #{user}"
280
+ @bot.irc.send "MODE #@name +v #{user}"
271
281
  end
272
282
 
283
+ # Devoices a user.
284
+ #
273
285
  # @param [String, User] user the user to devoice
274
286
  # @return [void]
275
287
  def devoice(user)
276
- @bot.raw "MODE #@name -v #{user}"
288
+ @bot.irc.send "MODE #@name -v #{user}"
277
289
  end
278
290
 
279
291
  # Invites a user to the channel.
@@ -281,19 +293,20 @@ module Cinch
281
293
  # @param [String, User] user the user to invite
282
294
  # @return [void]
283
295
  def invite(user)
284
- @bot.raw("INVITE #{user} #@name")
296
+ @bot.irc.send("INVITE #{user} #@name")
285
297
  end
286
298
 
287
299
  # Sets the topic.
288
300
  #
289
301
  # @param [String] new_topic the new topic
290
- # @raise [Exceptions::TopicTooLong]
302
+ # @raise [Exceptions::TopicTooLong] Raised if the bot is operating
303
+ # in {Bot#strict? strict mode} and when the new topic is too long.
291
304
  def topic=(new_topic)
292
305
  if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
293
306
  raise Exceptions::TopicTooLong, new_topic
294
307
  end
295
308
 
296
- @bot.raw "TOPIC #@name :#{new_topic}"
309
+ @bot.irc.send "TOPIC #@name :#{new_topic}"
297
310
  end
298
311
 
299
312
  # Kicks a user from the channel.
@@ -307,7 +320,7 @@ module Cinch
307
320
  raise Exceptions::KickReasonTooLong, reason
308
321
  end
309
322
 
310
- @bot.raw("KICK #@name #{user} :#{reason}")
323
+ @bot.irc.send("KICK #@name #{user} :#{reason}")
311
324
  end
312
325
 
313
326
  # Sets or unsets modes. Most of the time you won't need this but
@@ -318,7 +331,7 @@ module Cinch
318
331
  # @example
319
332
  # channel.mode "+n"
320
333
  def mode(s)
321
- @bot.raw "MODE #@name #{s}"
334
+ @bot.irc.send "MODE #@name #{s}"
322
335
  end
323
336
 
324
337
  # Causes the bot to part from the channel.
@@ -326,7 +339,7 @@ module Cinch
326
339
  # @param [String] message the part message.
327
340
  # @return [void]
328
341
  def part(message = nil)
329
- @bot.raw "PART #@name :#{message}"
342
+ @bot.irc.send "PART #@name :#{message}"
330
343
  end
331
344
 
332
345
  # Joins the channel
@@ -338,20 +351,21 @@ module Cinch
338
351
  if key.nil? and self.key != true
339
352
  key = self.key
340
353
  end
341
- @bot.raw "JOIN #{[@name, key].compact.join(" ")}"
354
+ @bot.irc.send "JOIN #{[@name, key].compact.join(" ")}"
342
355
  end
343
356
 
344
357
  # @endgroup
345
358
 
346
359
  # @api private
347
- # @return [void]
360
+ # @return [User] The added user
348
361
  def add_user(user, modes = [])
349
362
  @in_channel = true if user == @bot
350
363
  @users[user] = modes
364
+ user
351
365
  end
352
366
 
353
367
  # @api private
354
- # @return [void]
368
+ # @return [User, nil] The removed user
355
369
  def remove_user(user)
356
370
  @in_channel = false if user == @bot
357
371
  @users.delete(user)
@@ -365,96 +379,6 @@ module Cinch
365
379
  @users.clear
366
380
  end
367
381
 
368
- # @group Sending messages
369
-
370
- # Send a message to the channel.
371
- #
372
- # @param [String] message the message
373
- # @return [void]
374
- # @see #safe_send
375
- def send(message)
376
- @bot.msg(@name, message)
377
- end
378
- alias_method :privmsg, :send
379
- alias_method :msg, :send
380
-
381
- # Send a notice to the channel.
382
- #
383
- # @param [String] message the message
384
- # @return [void]
385
- def notice(message)
386
- @bot.notice(@name, message)
387
- end
388
-
389
- # Like {#safe_send} but for notices.
390
- #
391
- # @param (see #safe_send)
392
- # @return (see #safe_send)
393
- # @see #safe_send
394
- # @todo (see #safe_send)
395
- def safe_notice(message)
396
- @bot.safe_notice(@name, message)
397
- end
398
-
399
- # Send a message to the channel, but remove any non-printable
400
- # characters. The purpose of this method is to send text from
401
- # untrusted sources, like other users or feeds.
402
- #
403
- # Note: this will **break** any mIRC color codes embedded in the
404
- # string.
405
- #
406
- # @param (see #send)
407
- # @return (see #send)
408
- # @see #send
409
- # @todo Handle mIRC color codes more gracefully.
410
- def safe_send(message)
411
- @bot.safe_msg(@name, message)
412
- end
413
- alias_method :safe_privmsg, :safe_send
414
- alias_method :safe_msg, :safe_send
415
-
416
-
417
-
418
- # Send a CTCP to the channel.
419
- #
420
- # @param [String] message the ctcp message
421
- # @return [void]
422
- def ctcp(message)
423
- send "\001#{message}\001"
424
- end
425
-
426
- # Invoke an action (/me) in the channel.
427
- #
428
- # @param [String] message the message
429
- # @return [void]
430
- # @see #safe_action
431
- def action(message)
432
- @bot.action(@name, message)
433
- end
434
-
435
- # Invoke an action (/me) in the channel but remove any
436
- # non-printable characters. The purpose of this method is to send
437
- # text from untrusted sources, like other users or feeds.
438
- #
439
- # Note: this will **break** any mIRC color codes embedded in the
440
- # string.
441
- #
442
- # @param (see #action)
443
- # @return (see #action)
444
- # @see #action
445
- # @todo Handle mIRC color codes more gracefully.
446
- def safe_action(message)
447
- @bot.safe_action(@name, message)
448
- end
449
-
450
- # @endgroup
451
-
452
- # @return [Boolean]
453
- def ==(other)
454
- @name == other.to_s
455
- end
456
- alias_method :eql?, "=="
457
-
458
382
  # @return [Fixnum]
459
383
  def hash
460
384
  @name.hash