cinch 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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