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,391 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "time"
3
+ require "cakewalk/formatting"
4
+
5
+ module Cakewalk
6
+ # This class serves two purposes. For one, it simply
7
+ # represents incoming messages and allows for querying various
8
+ # details (who sent the message, what kind of message it is, etc).
9
+ #
10
+ # At the same time, it allows **responding** to messages, which
11
+ # means sending messages to either users or channels.
12
+ class Message
13
+ # @return [String]
14
+ attr_reader :raw
15
+
16
+ # @return [String]
17
+ attr_reader :prefix
18
+
19
+ # @return [String]
20
+ attr_reader :command
21
+
22
+ # @return [Array<String>]
23
+ attr_reader :params
24
+
25
+ # @return [Hash]
26
+ attr_reader :tags
27
+
28
+ # @return [Array<Symbol>]
29
+ attr_reader :events
30
+ # @api private
31
+ attr_writer :events
32
+
33
+ # @return [Time]
34
+ # @since 2.0.0
35
+ attr_reader :time
36
+
37
+ # @return [Bot]
38
+ # @since 1.1.0
39
+ attr_reader :bot
40
+
41
+ # @return [User] The user who sent this message
42
+ attr_reader :user
43
+
44
+ # @return [String, nil]
45
+ attr_reader :server
46
+
47
+ # @return [Integer, nil] the numeric error code, if any
48
+ attr_reader :error
49
+
50
+ # @return [String, nil] the command part of an CTCP message
51
+ attr_reader :ctcp_command
52
+
53
+ # @return [Channel] The channel in which this message was sent
54
+ attr_reader :channel
55
+
56
+ # @return [String, nil] the CTCP message, without \001 control characters
57
+ attr_reader :ctcp_message
58
+
59
+ # @return [Array<String>, nil]
60
+ attr_reader :ctcp_args
61
+
62
+ # @return [String, nil]
63
+ attr_reader :message
64
+
65
+ # @return [String, nil] The action message
66
+ # @since 2.0.0
67
+ attr_reader :action_message
68
+
69
+ # @return [Target]
70
+ attr_reader :target
71
+
72
+ # The STATUSMSG mode a channel message was sent to.
73
+ #
74
+ # Some IRC servers allow sending messages limited to people in a
75
+ # channel who have a certain mode. For example, by sending a
76
+ # message to `+#channel`, only people who are voiced, or have a
77
+ # higher mode (op) will receive the message.
78
+ #
79
+ # This attribute contains the mode character the message was sent
80
+ # to, or nil if it was a normal message. For the previous example,
81
+ # this attribute would be set to `"v"`, for voiced.
82
+ #
83
+ # @return [String, nil]
84
+ # @since 2.3.0
85
+ attr_reader :statusmsg_mode
86
+
87
+ def initialize(msg, bot)
88
+ @raw = msg
89
+ @bot = bot
90
+ @matches = {ctcp: {}, action: {}, other: {}}
91
+ @events = []
92
+ @time = Time.now
93
+ @statusmsg_mode = nil
94
+ parse if msg
95
+ end
96
+
97
+ # @api private
98
+ # @return [void]
99
+ def parse
100
+ match = @raw.match(/(?:^@([^:]+))?(?::?(\S+) )?(\S+)(.*)/)
101
+ if match.captures[3].empty?
102
+ # no IRCv3 tags
103
+ @prefix, @command, raw_params = match.captures
104
+ else
105
+ tags, @prefix, @command, raw_params = match.captures
106
+ end
107
+
108
+ @params = parse_params(raw_params)
109
+ @tags = parse_tags(tags)
110
+
111
+ @user = parse_user
112
+ @channel, @statusmsg_mode = parse_channel
113
+ @target = @channel || @user
114
+ @server = parse_server
115
+ @error = parse_error
116
+ @message = parse_message
117
+
118
+ @ctcp_message = parse_ctcp_message
119
+ @ctcp_command = parse_ctcp_command
120
+ @ctcp_args = parse_ctcp_args
121
+
122
+ @action_message = parse_action_message
123
+ end
124
+
125
+ # @group Type checking
126
+
127
+ # @return [Boolean] true if the message is an numeric reply (as
128
+ # opposed to a command)
129
+ def numeric_reply?
130
+ !!@command.match(/^\d{3}$/)
131
+ end
132
+
133
+ # @return [Boolean] true if the message describes an error
134
+ def error?
135
+ !@error.nil?
136
+ end
137
+
138
+ # @return [Boolean] true if this message was sent in a channel
139
+ def channel?
140
+ !@channel.nil?
141
+ end
142
+
143
+ # @return [Boolean] true if the message is an CTCP message
144
+ def ctcp?
145
+ !!(@params.last =~ /\001.+\001/)
146
+ end
147
+
148
+ # @return [Boolean] true if the message is an action (/me)
149
+ # @since 2.0.0
150
+ def action?
151
+ @ctcp_command == "ACTION"
152
+ end
153
+
154
+ # @endgroup
155
+
156
+ # @api private
157
+ # @return [MatchData]
158
+ def match(regexp, type, strip_colors)
159
+ text = ""
160
+ case type
161
+ when :ctcp
162
+ text = ctcp_message
163
+ when :action
164
+ text = action_message
165
+ else
166
+ text = message.to_s
167
+ type = :other
168
+ end
169
+
170
+ if strip_colors
171
+ text = Cakewalk::Formatting.unformat(text)
172
+ end
173
+
174
+ @matches[type][regexp] ||= text.match(regexp)
175
+ end
176
+
177
+ # @group Replying
178
+
179
+ # Replies to a message, automatically determining if it was a
180
+ # channel or a private message.
181
+ #
182
+ # If the message is a STATUSMSG, i.e. it was send to `+#channel`
183
+ # or `@#channel` instead of `#channel`, the reply will be sent as
184
+ # the same kind of STATUSMSG. See {#statusmsg_mode} for more
185
+ # information on STATUSMSG.
186
+ #
187
+ # @param [String] text the message
188
+ # @param [Boolean] prefix if prefix is true and the message was in
189
+ # a channel, the reply will be prefixed by the nickname of whoever
190
+ # send the mesage
191
+ # @return [void]
192
+ def reply(text, prefix = false)
193
+ text = text.to_s
194
+ if @channel && prefix
195
+ text = text.split("\n").map {|l| "#{user.nick}: #{l}"}.join("\n")
196
+ end
197
+
198
+ reply_target.send(text)
199
+ end
200
+
201
+ # Like {#reply}, but using {Target#safe_send} instead
202
+ #
203
+ # @param (see #reply)
204
+ # @return (see #reply)
205
+ def safe_reply(text, prefix = false)
206
+ text = text.to_s
207
+ if channel && prefix
208
+ text = "#{@user.nick}: #{text}"
209
+ end
210
+ reply_target.safe_send(text)
211
+ end
212
+
213
+ # Reply to a message with an action.
214
+ #
215
+ # For its behaviour with regard to STATUSMSG, see {#reply}.
216
+ #
217
+ # @param [String] text the action message
218
+ # @return [void]
219
+ def action_reply(text)
220
+ text = text.to_s
221
+ reply_target.action(text)
222
+ end
223
+
224
+ # Like {#action_reply}, but using {Target#safe_action} instead
225
+ #
226
+ # @param (see #action_reply)
227
+ # @return (see #action_reply)
228
+ def safe_action_reply(text)
229
+ text = text.to_s
230
+ reply_target.safe_action(text)
231
+ end
232
+
233
+ # Reply to a CTCP message
234
+ #
235
+ # @return [void]
236
+ def ctcp_reply(answer)
237
+ return unless ctcp?
238
+ @user.notice "\001#{@ctcp_command} #{answer}\001"
239
+ end
240
+
241
+ # @endgroup
242
+
243
+ # @return [String]
244
+ # @since 1.1.0
245
+ def to_s
246
+ "#<Cakewalk::Message @raw=#{@raw.chomp.inspect} @params=#{@params.inspect} channel=#{@channel.inspect} user=#{@user.inspect}>"
247
+ end
248
+
249
+ private
250
+ def reply_target
251
+ if @channel.nil? || @statusmsg_mode.nil?
252
+ return @target
253
+ end
254
+ prefix = @bot.irc.isupport["PREFIX"][@statusmsg_mode]
255
+ return Target.new(prefix + @channel.name, @bot)
256
+ end
257
+ def regular_command?
258
+ !numeric_reply? # a command can only be numeric or "regular"…
259
+ end
260
+
261
+ def parse_params(raw_params)
262
+ params = []
263
+ if match = raw_params.match(/(?:^:| :)(.*)$/)
264
+ params = match.pre_match.split(" ")
265
+ params << match[1]
266
+ else
267
+ params = raw_params.split(" ")
268
+ end
269
+
270
+ return params
271
+ end
272
+
273
+ def parse_tags(raw_tags)
274
+ return {} if raw_tags.nil?
275
+
276
+ def to_symbol(string)
277
+ return string.gsub(/-/, "_").downcase.to_sym
278
+ end
279
+
280
+ tags = {}
281
+ raw_tags.split(";").each do |tag|
282
+ tag_name, tag_value = tag.split("=")
283
+ if tag_value =~ /,/
284
+ tag_value = tag_value.split(',')
285
+ elsif tag_value.nil?
286
+ tag_value = tag_name
287
+ end
288
+ if tag_name =~ /\//
289
+ vendor, tag_name = tag_name.split('/')
290
+ tags[to_symbol(vendor)] = {
291
+ to_symbol(tag_name) => tag_value
292
+ }
293
+ else
294
+ tags[to_symbol(tag_name)] = tag_value
295
+ end
296
+ end
297
+ return tags
298
+ end
299
+
300
+ def parse_user
301
+ return unless @prefix
302
+ nick = @prefix[/^(\S+)!/, 1]
303
+ user = @prefix[/^\S+!(\S+)@/, 1]
304
+ host = @prefix[/@(\S+)$/, 1]
305
+
306
+ return nil if nick.nil?
307
+ return @bot.user_list.find_ensured(user, nick, host)
308
+ end
309
+
310
+ def parse_channel
311
+ # has to be called after parse_params
312
+ return nil if @params.empty?
313
+
314
+ case @command
315
+ when "INVITE", Constants::RPL_CHANNELMODEIS.to_s, Constants::RPL_BANLIST.to_s
316
+ @bot.channel_list.find_ensured(@params[1])
317
+ when Constants::RPL_NAMEREPLY.to_s
318
+ @bot.channel_list.find_ensured(@params[2])
319
+ else
320
+ # Note that this will also find channels for messages that
321
+ # don't actually include a channel parameter. For example
322
+ # `QUIT :#sometext` will be interpreted as a channel. The
323
+ # alternative to the currently used heuristic would be to
324
+ # hardcode a list of commands that provide a channel argument.
325
+ ch, status = privmsg_channel_name(@params.first)
326
+ if ch.nil? && numeric_reply? && @params.size > 1
327
+ ch, status = privmsg_channel_name(@params[1])
328
+ end
329
+ if ch
330
+ return @bot.channel_list.find_ensured(ch), status
331
+ end
332
+ end
333
+ end
334
+
335
+ def privmsg_channel_name(s)
336
+ chantypes = @bot.irc.isupport["CHANTYPES"]
337
+ statusmsg = @bot.irc.isupport["STATUSMSG"]
338
+ if statusmsg.include?(s[0]) && chantypes.include?(s[1])
339
+ status = @bot.irc.isupport["PREFIX"].invert[s[0]]
340
+ return s[1..-1], status
341
+ elsif chantypes.include?(s[0])
342
+ return s, nil
343
+ end
344
+ end
345
+
346
+ def parse_server
347
+ return unless @prefix
348
+ return if @prefix.match(/[@!]/)
349
+ return @prefix[/^(\S+)/, 1]
350
+ end
351
+
352
+ def parse_error
353
+ return @command.to_i if numeric_reply? && @command[/[45]\d\d/]
354
+ end
355
+
356
+ def parse_message
357
+ # has to be called after parse_params
358
+ if error?
359
+ @error.to_s
360
+ elsif regular_command?
361
+ @params.last
362
+ end
363
+ end
364
+
365
+ def parse_ctcp_message
366
+ # has to be called after parse_params
367
+ return unless ctcp?
368
+ @params.last =~ /\001(.+)\001/
369
+ $1
370
+ end
371
+
372
+ def parse_ctcp_command
373
+ # has to be called after parse_ctcp_message
374
+ return unless ctcp?
375
+ @ctcp_message.split(" ").first
376
+ end
377
+
378
+ def parse_ctcp_args
379
+ # has to be called after parse_ctcp_message
380
+ return unless ctcp?
381
+ @ctcp_message.split(" ")[1..-1]
382
+ end
383
+
384
+ def parse_action_message
385
+ # has to be called after parse_ctcp_message
386
+ return nil unless action?
387
+ @ctcp_message.split(" ", 2).last
388
+ end
389
+
390
+ end
391
+ end
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ require "cakewalk/open_ended_queue"
3
+
4
+ module Cakewalk
5
+ # This class manages all outgoing messages, applying rate throttling
6
+ # and fair distribution.
7
+ #
8
+ # @api private
9
+ class MessageQueue
10
+ def initialize(socket, bot)
11
+ @socket = socket
12
+ @bot = bot
13
+
14
+ @queues = {:generic => OpenEndedQueue.new}
15
+ @queues_to_process = Queue.new
16
+ @queued_queues = Set.new
17
+
18
+ @mutex = Mutex.new
19
+
20
+ @time_since_last_send = nil
21
+
22
+ @log = []
23
+ end
24
+
25
+ # @return [void]
26
+ def queue(message)
27
+ command, target, _ = message.split(" ", 3)
28
+
29
+ queue = nil
30
+ case command
31
+ when "PRIVMSG", "NOTICE"
32
+ @mutex.synchronize do
33
+ # we are assuming that each message has only one target,
34
+ # which will be true as long as the user does not send raw
35
+ # messages.
36
+ #
37
+ # this assumption is also reflected in the computation of
38
+ # passed time and processed messages, since our score does
39
+ # not take weights into account.
40
+ queue = @queues[target] ||= OpenEndedQueue.new
41
+ end
42
+ else
43
+ queue = @queues[:generic]
44
+ end
45
+ queue << message
46
+
47
+ @mutex.synchronize do
48
+ unless @queued_queues.include?(queue)
49
+ @queued_queues << queue
50
+ @queues_to_process << queue
51
+ end
52
+ end
53
+ end
54
+
55
+ # @return [void]
56
+ def process!
57
+ loop do
58
+ wait
59
+ process_one
60
+ end
61
+ end
62
+
63
+ private
64
+ def wait
65
+ if @log.size > 1
66
+ mps = @bot.config.messages_per_second || @bot.irc.network.default_messages_per_second
67
+ max_queue_size = @bot.config.server_queue_size || @bot.irc.network.default_server_queue_size
68
+
69
+ time_passed = @log.last - @log.first
70
+
71
+ messages_processed = (time_passed * mps).floor
72
+ effective_size = @log.size - messages_processed
73
+
74
+ if effective_size <= 0
75
+ @log.clear
76
+ elsif effective_size >= max_queue_size
77
+ sleep 1.0/mps
78
+ end
79
+ end
80
+ end
81
+
82
+ def process_one
83
+ queue = @queues_to_process.pop
84
+ message = queue.pop.to_s.each_line.first.chomp
85
+
86
+ if queue.empty?
87
+ @mutex.synchronize do
88
+ @queued_queues.delete(queue)
89
+ end
90
+ else
91
+ @queues_to_process << queue
92
+ end
93
+
94
+ begin
95
+ to_send = Cakewalk::Utilities::Encoding.encode_outgoing(message, @bot.config.encoding)
96
+ @socket.write(to_send + "\r\n")
97
+ @log << Time.now
98
+ @bot.loggers.outgoing(message)
99
+
100
+ @time_since_last_send = Time.now
101
+ rescue IOError
102
+ @bot.loggers.error "Could not send message (connectivity problems): #{message}"
103
+ end
104
+ end
105
+
106
+ end # class MessageQueue
107
+ end
@@ -0,0 +1,76 @@
1
+ require "cakewalk/exceptions"
2
+
3
+ module Cakewalk
4
+ # @api private
5
+ # @since 1.1.0
6
+ module ModeParser
7
+ ErrEmptyString = "Empty mode string"
8
+ MalformedError = Struct.new(:modes)
9
+ EmptySequenceError = Struct.new(:modes)
10
+ NotEnoughParametersError = Struct.new(:op)
11
+ TooManyParametersError = Struct.new(:modes, :params)
12
+
13
+ # @param [String] modes The mode string as sent by the server
14
+ # @param [Array<String>] params Parameters belonging to the modes
15
+ # @param [Hash{:add, :remove => Array<String>}] param_modes
16
+ # A mapping describing which modes require parameters
17
+ # @return [(Array<(Symbol<:add, :remove>, String<char>, String<param>), foo)]
18
+ def self.parse_modes(modes, params, param_modes = {})
19
+ if modes.size == 0
20
+ return nil, ErrEmptyString
21
+ # raise Exceptions::InvalidModeString, 'Empty mode string'
22
+ end
23
+
24
+ if modes[0] !~ /[+-]/
25
+ return nil, MalformedError.new(modes)
26
+ # raise Exceptions::InvalidModeString, "Malformed modes string: %s" % modes
27
+ end
28
+
29
+ changes = []
30
+
31
+ direction = nil
32
+ count = -1
33
+
34
+ modes.each_char do |ch|
35
+ if ch =~ /[+-]/
36
+ if count == 0
37
+ return changes, EmptySequenceError.new(modes)
38
+ # raise Exceptions::InvalidModeString, 'Empty mode sequence: %s' % modes
39
+ end
40
+
41
+ direction = case ch
42
+ when "+"
43
+ :add
44
+ when "-"
45
+ :remove
46
+ end
47
+ count = 0
48
+ else
49
+ param = nil
50
+ if param_modes.has_key?(direction) && param_modes[direction].include?(ch)
51
+ if params.size > 0
52
+ param = params.shift
53
+ else
54
+ return changes, NotEnoughParametersError.new(ch)
55
+ # raise Exceptions::InvalidModeString, 'Not enough parameters: %s' % ch.inspect
56
+ end
57
+ end
58
+ changes << [direction, ch, param]
59
+ count += 1
60
+ end
61
+ end
62
+
63
+ if params.size > 0
64
+ return changes, TooManyParametersError.new(modes, params)
65
+ # raise Exceptions::InvalidModeString, 'Too many parameters: %s %s' % [modes, params]
66
+ end
67
+
68
+ if count == 0
69
+ return changes, EmptySequenceError.new(modes)
70
+ # raise Exceptions::InvalidModeString, 'Empty mode sequence: %s' % modes
71
+ end
72
+
73
+ return changes, nil
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,89 @@
1
+ module Cakewalk
2
+ # This class allows querying the IRC network for its name and used
3
+ # server software as well as certain non-standard behaviour.
4
+ #
5
+ # @since 2.0.0
6
+ class Network
7
+ # @return [Symbol] The name of the network. `:unknown` if the
8
+ # network couldn't be detected.
9
+ attr_reader :name
10
+
11
+ # @api private
12
+ attr_writer :name
13
+
14
+ # @return [Symbol] The server software used by the network.
15
+ # `:unknown` if the software couldn't be detected.
16
+ attr_reader :ircd
17
+
18
+ # @api private
19
+ attr_writer :ircd
20
+
21
+ # @return [Array<Symbol>] All client capabilities supported by the
22
+ # network.
23
+ attr_reader :capabilities
24
+
25
+ # @api private
26
+ attr_writer :capabilities
27
+
28
+ # @param [Symbol] name
29
+ # @param [Symbol] ircd
30
+ # @api private
31
+ # @note The user should not create instances of this class but use
32
+ # {IRC#network} instead.
33
+ def initialize(name, ircd)
34
+ @name = name
35
+ @ircd = ircd
36
+ @capabilities = []
37
+ end
38
+
39
+ # @return [String, nil] The mode used for getting the list of
40
+ # channel owners, if any
41
+ def owner_list_mode
42
+ return "q" if @ircd == :unreal || @ircd == :inspircd
43
+ end
44
+
45
+ # @return [String, nil] The mode used for getting the list of
46
+ # channel quiets, if any
47
+ def quiet_list_mode
48
+ return "q" if @ircd == :"ircd-seven"
49
+ end
50
+
51
+ # @return [Boolean] True if we do not know which network we are
52
+ # connected to
53
+ def unknown_network?
54
+ @name == :unknown
55
+ end
56
+
57
+ # @return [Boolean] True if we do not know which software the
58
+ # server is running
59
+ def unknown_ircd?
60
+ @ircd == :unknown
61
+ end
62
+
63
+ # Note for the default_* methods: Always make sure to return a
64
+ # value for when no network/ircd was detected so that MessageQueue
65
+ # doesn't break.
66
+
67
+ # @return [Numeric] The `messages per second` value that best suits
68
+ # the current network
69
+ def default_messages_per_second
70
+ case @name
71
+ when :freenode
72
+ 0.7
73
+ else
74
+ 0.5
75
+ end
76
+ end
77
+
78
+ # @return [Integer] The `server queue size` value that best suits
79
+ # the current network
80
+ def default_server_queue_size
81
+ case @name
82
+ when :quakenet
83
+ 40
84
+ else
85
+ 10
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,26 @@
1
+ require "thread"
2
+
3
+ # Like Ruby's Queue class, but allowing both pushing and unshifting
4
+ # objects.
5
+ #
6
+ # @api private
7
+ class OpenEndedQueue < Queue
8
+ # @param [Object] obj
9
+ # @return [void]
10
+ def unshift(obj)
11
+ t = nil
12
+ @mutex.synchronize{
13
+ @que.unshift obj
14
+ begin
15
+ t = @waiting.shift
16
+ t.wakeup if t
17
+ rescue ThreadError
18
+ retry
19
+ end
20
+ }
21
+ begin
22
+ t.run if t
23
+ rescue ThreadError
24
+ end
25
+ end
26
+ end