cakewalk 3.0.0

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