cinch 1.0.2 → 1.1.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 (46) hide show
  1. data/README.md +25 -44
  2. data/examples/basic/autovoice.rb +1 -1
  3. data/examples/basic/join_part.rb +0 -4
  4. data/examples/plugins/autovoice.rb +2 -5
  5. data/examples/plugins/google.rb +1 -2
  6. data/examples/plugins/hooks.rb +36 -0
  7. data/examples/plugins/lambdas.rb +35 -0
  8. data/examples/plugins/last_nick.rb +24 -0
  9. data/examples/plugins/multiple_matches.rb +1 -10
  10. data/examples/plugins/own_events.rb +37 -0
  11. data/examples/plugins/timer.rb +22 -0
  12. data/examples/plugins/url_shorten.rb +1 -1
  13. data/lib/cinch.rb +50 -1
  14. data/lib/cinch/ban.rb +5 -2
  15. data/lib/cinch/bot.rb +360 -193
  16. data/lib/cinch/cache_manager.rb +15 -0
  17. data/lib/cinch/callback.rb +6 -0
  18. data/lib/cinch/channel.rb +150 -96
  19. data/lib/cinch/channel_manager.rb +26 -0
  20. data/lib/cinch/constants.rb +6 -4
  21. data/lib/cinch/exceptions.rb +9 -0
  22. data/lib/cinch/irc.rb +197 -82
  23. data/lib/cinch/logger/formatted_logger.rb +8 -8
  24. data/lib/cinch/logger/zcbot_logger.rb +37 -0
  25. data/lib/cinch/mask.rb +17 -3
  26. data/lib/cinch/message.rb +14 -7
  27. data/lib/cinch/message_queue.rb +8 -4
  28. data/lib/cinch/mode_parser.rb +56 -0
  29. data/lib/cinch/pattern.rb +45 -0
  30. data/lib/cinch/plugin.rb +129 -34
  31. data/lib/cinch/rubyext/string.rb +4 -4
  32. data/lib/cinch/syncable.rb +8 -0
  33. data/lib/cinch/user.rb +68 -13
  34. data/lib/cinch/user_manager.rb +60 -0
  35. metadata +17 -35
  36. data/Rakefile +0 -66
  37. data/lib/cinch/PLANNED +0 -4
  38. data/spec/bot_spec.rb +0 -5
  39. data/spec/channel_spec.rb +0 -5
  40. data/spec/cinch_spec.rb +0 -5
  41. data/spec/irc_spec.rb +0 -5
  42. data/spec/message_spec.rb +0 -5
  43. data/spec/plugin_spec.rb +0 -5
  44. data/spec/spec.opts +0 -2
  45. data/spec/spec_helper.rb +0 -8
  46. data/spec/user_spec.rb +0 -5
@@ -0,0 +1,15 @@
1
+ module Cinch
2
+ class CacheManager
3
+ include Enumerable
4
+
5
+ def initialize(bot)
6
+ @bot = bot
7
+ @cache = {}
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ def each(&block)
12
+ @cache.each_value(&block)
13
+ end
14
+ end
15
+ end
@@ -3,9 +3,15 @@ module Cinch
3
3
  class Callback
4
4
  include Helpers
5
5
 
6
+ # @return [Bot]
6
7
  attr_reader :bot
7
8
  def initialize(bot)
8
9
  @bot = bot
9
10
  end
11
+
12
+ # (see Bot#synchronize)
13
+ def synchronize(*args, &block)
14
+ @bot.synchronize(*args, &block)
15
+ end
10
16
  end
11
17
  end
@@ -13,22 +13,39 @@ module Cinch
13
13
  # @param [Bot] bot a bot
14
14
  # @return [Channel]
15
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
16
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
+
17
23
  downcased_name = name.irc_downcase(bot.irc.isupport["CASEMAPPING"])
18
- @channels[downcased_name] ||= new(name, bot)
19
- @channels[downcased_name]
24
+ @channels[downcased_name] ||= bot.channel_manager.find_ensured(name)
20
25
  end
21
26
 
22
27
  # Finds a channel.
23
28
  #
24
29
  # @param [String] name name of a channel
25
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
26
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
+
27
38
  @channels[name]
28
39
  end
29
40
 
30
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
31
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
+
32
49
  @channels.values
33
50
  end
34
51
  end
@@ -36,28 +53,28 @@ module Cinch
36
53
  # @return [Bot]
37
54
  attr_reader :bot
38
55
 
39
- # @return [String]
56
+ # @return [String] the channel's name
40
57
  attr_reader :name
41
58
 
42
- # @return [Array<User>]
59
+ # @return [Array<User>] all users in the channel
43
60
  attr_reader :users
44
61
  synced_attr_reader :users
45
62
 
46
- # @return [String]
63
+ # @return [String] the channel's topic
47
64
  attr_accessor :topic
48
65
  synced_attr_reader :topic
49
66
 
50
- # @return [Array<Ban>]
67
+ # @return [Array<Ban>] all active bans
51
68
  attr_reader :bans
52
69
  synced_attr_reader :bans
53
70
 
54
- # @return [Array<String>]
71
+ # @return [Hash<String => Object>]
55
72
  attr_reader :modes
56
73
  synced_attr_reader :modes
57
74
  def initialize(name, bot)
58
75
  @bot = bot
59
76
  @name = name
60
- @users = {}
77
+ @users = Hash.new {|h,k| h[k] = []}
61
78
  @bans = []
62
79
 
63
80
  @modes = {}
@@ -85,8 +102,40 @@ module Cinch
85
102
  }
86
103
  end
87
104
 
105
+ # @group Checks
106
+
107
+ # @param [User, String] user An {User}-object or a nickname
108
+ # @return [Boolean] Check if a user is in the channel
109
+ def has_user?(user)
110
+ @users.has_key?(User(user))
111
+ end
112
+
113
+
114
+ # @return [Boolean] true if `user` is opped in the channel
115
+ def opped?(user)
116
+ user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
117
+ @users[user].include? "o"
118
+ end
119
+
120
+ # @return [Boolean] true if `user` is half-opped in the channel
121
+ def half_opped?(user)
122
+ user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
123
+ @users[user].include? "h"
124
+ end
125
+
126
+ # @return [Boolean] true if `user` is voiced in the channel
127
+ def voiced?(user)
128
+ user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
129
+ @users[user].include? "v"
130
+ end
131
+
132
+ # @endgroup
133
+
134
+ # @return [Number] The maximum number of allowed users in the
135
+ # channel. 0 if unlimited.
88
136
  attr_accessor :limit
89
- # @return [Number]
137
+ undef_method "limit"
138
+ undef_method "limit="
90
139
  def limit
91
140
  @modes["l"].to_i
92
141
  end
@@ -151,7 +200,7 @@ module Cinch
151
200
  end
152
201
  end
153
202
 
154
- # @return [String, nil]
203
+ # @return [String, nil] The channel's key (aka password)
155
204
  attr_accessor :key
156
205
  undef_method "key"
157
206
  undef_method "key="
@@ -167,17 +216,6 @@ module Cinch
167
216
  end
168
217
  end
169
218
 
170
- # Sets or unsets modes. Most of the time you won't need this but
171
- # use setter methods like {Channel#invite_only=}.
172
- #
173
- # @param [String] s a mode string
174
- # @return [void]
175
- # @example
176
- # channel.mode "+n"
177
- def mode(s)
178
- @bot.raw "MODE #@name #{s}"
179
- end
180
-
181
219
  # @api private
182
220
  # @return [void]
183
221
  def sync_modes(all = true)
@@ -189,17 +227,7 @@ module Cinch
189
227
  @bot.raw "MODE #@name"
190
228
  end
191
229
 
192
- # @return [Boolean] true if `user` is opped in the channel
193
- def opped?(user)
194
- user = User.find_ensured(user, @bot) unless user.is_a?(User)
195
- @users[user] == "@"
196
- end
197
-
198
- # @return [Boolean] true if `user` is voiced in the channel
199
- def voiced?(user)
200
- user = User.find_ensured(user, @bot) unless user.is_a?(User)
201
- @users[user] == "+"
202
- end
230
+ # @group Channel Manipulation
203
231
 
204
232
  # Bans someone from the channel.
205
233
  #
@@ -247,11 +275,78 @@ module Cinch
247
275
  @bot.raw "MODE #@name -v #{user}"
248
276
  end
249
277
 
278
+ # Invites a user to the channel.
279
+ #
280
+ # @param [String, User] user the user to invite
281
+ # @return [void]
282
+ def invite(user)
283
+ @bot.raw("INVITE #{user} #@name")
284
+ end
285
+
286
+ # Sets the topic.
287
+ #
288
+ # @param [String] new_topic the new topic
289
+ # @raise [Exceptions::TopicTooLong]
290
+ def topic=(new_topic)
291
+ if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
292
+ raise Exceptions::TopicTooLong, new_topic
293
+ end
294
+
295
+ @bot.raw "TOPIC #@name :#{new_topic}"
296
+ end
297
+
298
+ # Kicks a user from the channel.
299
+ #
300
+ # @param [String, User] user the user to kick
301
+ # @param [String] a reason for the kick
302
+ # @raise [Exceptions::KickReasonTooLong]
303
+ # @return [void]
304
+ def kick(user, reason = nil)
305
+ if reason.to_s.size > @bot.irc.isupport["KICKLEN"] && @bot.strict?
306
+ raise Exceptions::KickReasonTooLong, reason
307
+ end
308
+
309
+ @bot.raw("KICK #@name #{user} :#{reason}")
310
+ end
311
+
312
+ # Sets or unsets modes. Most of the time you won't need this but
313
+ # use setter methods like {Channel#invite_only=}.
314
+ #
315
+ # @param [String] s a mode string
316
+ # @return [void]
317
+ # @example
318
+ # channel.mode "+n"
319
+ def mode(s)
320
+ @bot.raw "MODE #@name #{s}"
321
+ end
322
+
323
+ # Causes the bot to part from the channel.
324
+ #
325
+ # @param [String] message the part message.
326
+ # @return [void]
327
+ def part(message = nil)
328
+ @bot.raw "PART #@name :#{message}"
329
+ end
330
+
331
+ # Joins the channel
332
+ #
333
+ # @param [String] key the channel key, if any. If none is
334
+ # specified but @key is set, @key will be used
335
+ # @return [void]
336
+ def join(key = nil)
337
+ if key.nil? and self.key != true
338
+ key = self.key
339
+ end
340
+ @bot.raw "JOIN #{[@name, key].compact.join(" ")}"
341
+ end
342
+
343
+ # @endgroup
344
+
250
345
  # @api private
251
346
  # @return [void]
252
- def add_user(user, mode = nil)
347
+ def add_user(user, modes = [])
253
348
  @in_channel = true if user == @bot
254
- @users[user] = mode # TODO can a user have more than one mode?
349
+ @users[user] = modes
255
350
  end
256
351
 
257
352
  # @api private
@@ -269,6 +364,8 @@ module Cinch
269
364
  @users.clear
270
365
  end
271
366
 
367
+ # @group Sending messages
368
+
272
369
  # Send a message to the channel.
273
370
  #
274
371
  # @param [String] message the message
@@ -280,6 +377,24 @@ module Cinch
280
377
  alias_method :privmsg, :send
281
378
  alias_method :msg, :send
282
379
 
380
+ # Send a notice to the channel.
381
+ #
382
+ # @param [String] message the message
383
+ # @return [void]
384
+ def notice(message)
385
+ @bot.notice(@name, message)
386
+ end
387
+
388
+ # Like {#safe_send} but for notices.
389
+ #
390
+ # @param (see #safe_send)
391
+ # @return (see #safe_send)
392
+ # @see #safe_send
393
+ # @todo (see #safe_send)
394
+ def safe_notice(message)
395
+ @bot.safe_notice(@name, message)
396
+ end
397
+
283
398
  # Send a message to the channel, but remove any non-printable
284
399
  # characters. The purpose of this method is to send text from
285
400
  # untrusted sources, like other users or feeds.
@@ -331,68 +446,7 @@ module Cinch
331
446
  @bot.safe_action(@name, message)
332
447
  end
333
448
 
334
-
335
- # Invites a user to the channel.
336
- #
337
- # @param [String, User] user the user to invite
338
- # @return [void]
339
- def invite(user)
340
- @bot.raw("INVITE #{user} #@name")
341
- end
342
-
343
- # Sets the topic.
344
- #
345
- # @param [String] new_topic the new topic
346
- # @raise [Exceptions::TopicTooLong]
347
- def topic=(new_topic)
348
- if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
349
- raise Exceptions::TopicTooLong, new_topic
350
- end
351
-
352
- @bot.raw "TOPIC #@name :#{new_topic}"
353
- end
354
-
355
- # Kicks a user from the channel.
356
- #
357
- # @param [String, User] user the user to kick
358
- # @param [String] a reason for the kick
359
- # @raise [Exceptions::KickReasonTooLong]
360
- # @return [void]
361
- def kick(user, reason = nil)
362
- if reason.to_s.size > @bot.irc.isupport["KICKLEN"] && @bot.strict?
363
- raise Exceptions::KickReasonTooLong, reason
364
- end
365
-
366
- @bot.raw("KICK #@name #{user} :#{reason}")
367
- end
368
-
369
- # Invites a user to the channel.
370
- #
371
- # @param [String, User] user the user to invite
372
- # @return [void]
373
- def invite(user)
374
- @bot.raw "INVITE #{user} #@name"
375
- end
376
-
377
- # Causes the bot to part from the channel.
378
- #
379
- # @param [String] message the part message.
380
- # @return [void]
381
- def part(message = nil)
382
- @bot.raw "PART #@name :#{message}"
383
- end
384
-
385
- # Joins the channel
386
- #
387
- # @param [String] key the channel key, if any. If none is
388
- # specified but @key is set, @key will be used
389
- # @return [void]
390
- def join(key = nil)
391
- if key.nil? and self.key != true
392
- key = self.key
393
- end
394
- @bot.raw "JOIN #{[@name, key].compact.join(" ")}"
395
- end
449
+ # @endgroup
396
450
 
397
451
  # @return [Boolean]
398
452
  def ==(other)
@@ -0,0 +1,26 @@
1
+ require "cinch/cache_manager"
2
+
3
+ module Cinch
4
+ class ChannelManager < CacheManager
5
+ # Finds or creates a channel.
6
+ #
7
+ # @param [String] name name of a channel
8
+ # @return [Channel]
9
+ # @see Bot#Channel
10
+ def find_ensured(name)
11
+ downcased_name = name.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
12
+ @mutex.synchronize do
13
+ @cache[downcased_name] ||= Channel.new(name, @bot)
14
+ end
15
+ end
16
+
17
+ # Finds a channel.
18
+ #
19
+ # @param [String] name name of a channel
20
+ # @return [Channel, nil]
21
+ def find(name)
22
+ downcased_name = name.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
23
+ @cache[downcased_name]
24
+ end
25
+ end
26
+ end
@@ -38,7 +38,7 @@ module Cinch
38
38
  # 412 - 414 are returned by PRIVMSG to indicate that the message
39
39
  # wasn't delivered for some reason. ERR_NOTOPLEVEL and
40
40
  # ERR_WILDTOPLEVEL are errors that are returned when an invalid use of
41
- # "PRIVMSG $<server>" or "PRIVMSG #<host>" is attempted.
41
+ # "PRIVMSG $&lt;server&gt;" or "PRIVMSG #&lt;host&gt;" is attempted.
42
42
  ERR_WILDTOPLEVEL = 414
43
43
 
44
44
  # Returned to a registered client to indicate that the command sent is
@@ -229,8 +229,10 @@ module Cinch
229
229
  # Reply by the server showing its version details. The &lt;version&gt;
230
230
  # is the version of the software being used (including any patchlevel
231
231
  # revisions) and the &lt;debuglevel&gt; is used to indicate if the
232
- # server is running in "debug mode".<BR> The "comments" field may
233
- # contain any comments about the version or further version details.
232
+ # server is running in "debug mode".
233
+ #
234
+ #The "comments" field may contain any comments about the version or
235
+ # further version details.
234
236
  RPL_VERSION = 351
235
237
  RPL_WHOREPLY = 352
236
238
 
@@ -238,7 +240,7 @@ module Cinch
238
240
  # message. The RPL_WHOREPLY is only sent if there is an appropriate
239
241
  # match to the WHO query. If there is a list of parameters supplied
240
242
  # with a WHO message, a RPL_ENDOFWHO must be sent after processing
241
- # each list item with <name> being the item.
243
+ # each list item with &lt;name&gt; being the item.
242
244
  RPL_ENDOFWHO = 315
243
245
  RPL_NAMREPLY = 353
244
246
  RPL_NAMEREPLY = RPL_NAMREPLY
@@ -21,5 +21,14 @@ module Cinch
21
21
 
22
22
  class UnsupportedFeature < Generic
23
23
  end
24
+
25
+ class UnsupportedMode < Generic
26
+ def initialize(mode)
27
+ super "Cinch does not support the mode #{mode} yet."
28
+ end
29
+ end
30
+
31
+ class InvalidModeString < Generic
32
+ end
24
33
  end
25
34
  end