cinch 1.1.3 → 2.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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,4 +1,5 @@
1
1
  module Cinch
2
+ # This class represents masks, which are primarily used for bans.
2
3
  class Mask
3
4
  # @return [String]
4
5
  attr_reader :nick
@@ -8,6 +9,9 @@ module Cinch
8
9
  attr_reader :host
9
10
  # @return [String]
10
11
  attr_reader :mask
12
+
13
+ # @version 1.1.2
14
+ # @param [String] mask
11
15
  def initialize(mask)
12
16
  @mask = mask
13
17
  @nick, @user, @host = mask.match(/(.+)!(.+)@(.+)/)[1..-1]
@@ -15,21 +19,25 @@ module Cinch
15
19
  end
16
20
 
17
21
  # @return [Boolean]
22
+ # @since 1.1.0
18
23
  def ==(other)
19
24
  other.respond_to?(:mask) && other.mask == @mask
20
25
  end
21
26
 
22
27
  # @return [Boolean]
28
+ # @since 1.1.0
23
29
  def eql?(other)
24
30
  other.is_a?(self.class) && self == other
25
31
  end
26
32
 
33
+ # @return [Fixnum]
27
34
  def hash
28
35
  @mask.hash
29
36
  end
30
37
 
31
- # @param [Ban, Mask, User, String] target
38
+ # @param [Mask, String, #mask] target
32
39
  # @return [Boolean]
40
+ # @version 1.1.2
33
41
  def match(target)
34
42
  return self.class.from(target).mask =~ @regexp
35
43
 
@@ -42,19 +50,20 @@ module Cinch
42
50
  @mask.dup
43
51
  end
44
52
 
45
- # @param [Ban, Mask, User, String]
53
+ # @param [String, #mask]
54
+ # @return [target] if already a Mask
46
55
  # @return [Mask]
56
+ # @version 2.0.0
47
57
  def self.from(target)
48
- case target
49
- when User, Ban
50
- target.mask
51
- when String
52
- Mask.new(target)
53
- when Mask
54
- target
58
+ return target if target.is_a?(self)
59
+
60
+ if target.respond_to?(:mask)
61
+ mask = target.mask
55
62
  else
56
- raise ArgumentError
63
+ mask = Mask.new(target.to_s)
57
64
  end
65
+
66
+ return mask
58
67
  end
59
68
  end
60
69
  end
@@ -1,39 +1,71 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require "time"
3
+
2
4
  module Cinch
5
+ # This class serves two purposes. For one, it simply
6
+ # represents incoming messages and allows for querying various
7
+ # details (who sent the message, what kind of message it is, etc).
8
+ #
9
+ # At the same time, it allows **responding** to messages, which
10
+ # means sending messages to either users or channels.
11
+ #
12
+ # @attr_reader user
13
+ # @attr_reader error
14
+ # @attr_reader message
15
+ # @attr_reader server
16
+ # @attr_reader target
17
+ # @attr_reader channel
18
+ # @attr_reader action_message
19
+ # @attr_reader ctcp_args
20
+ # @attr_reader ctcp_command
21
+ # @attr_reader ctcp_message
3
22
  class Message
4
23
  # @return [String]
5
24
  attr_accessor :raw
25
+
6
26
  # @return [String]
7
27
  attr_accessor :prefix
28
+
8
29
  # @return [String]
9
30
  attr_accessor :command
31
+
10
32
  # @return [Array<String>]
11
33
  attr_accessor :params
34
+
35
+ # @return [Array<Symbol>]
12
36
  attr_reader :events
37
+ # @api private
38
+ attr_writer :events
39
+
40
+ # @return [Time]
41
+ # @since 2.0.0
42
+ attr_reader :time
43
+
13
44
  # @return [Bot]
45
+ # @since 1.1.0
14
46
  attr_reader :bot
47
+
15
48
  def initialize(msg, bot)
16
49
  @raw = msg
17
50
  @bot = bot
18
- @matches = {:ctcp => {}, :other => {}}
51
+ @matches = {:ctcp => {}, :action => {}, :other => {}}
19
52
  @events = []
53
+ @time = Time.now
20
54
  parse if msg
21
55
  end
22
56
 
23
-
24
-
25
- # @return [Boolean] true if the message is an numeric reply (as
26
- # opposed to a command)
27
- def numeric_reply?
28
- !!(@numeric_reply ||= @command.match(/^\d{3}$/))
29
- end
30
-
31
57
  # @api private
32
58
  # @return [void]
33
59
  def parse
34
60
  match = @raw.match(/(^:(\S+) )?(\S+)(.*)/)
35
61
  _, @prefix, @command, raw_params = match.captures
36
62
 
63
+ if @bot.irc.network.ngametv?
64
+ if @prefix != "ngame"
65
+ @prefix = "%s!user@host" % [@prefix, @prefix, @prefix]
66
+ end
67
+ end
68
+
37
69
  raw_params.strip!
38
70
  if match = raw_params.match(/(?:^:| :)(.*)$/)
39
71
  @params = match.pre_match.split(" ")
@@ -51,7 +83,7 @@ module Cinch
51
83
  host = @prefix[/@(\S+)$/, 1]
52
84
 
53
85
  return nil if nick.nil?
54
- @user ||= @bot.user_manager.find_ensured(user, nick, host)
86
+ @user ||= @bot.user_list.find_ensured(user, nick, host)
55
87
  end
56
88
 
57
89
  # @return [String, nil]
@@ -61,16 +93,24 @@ module Cinch
61
93
  @server ||= @prefix[/^(\S+)/, 1]
62
94
  end
63
95
 
64
- # @return [Boolean] true if the message describes an error
65
- def error?
66
- !!error
67
- end
68
-
69
96
  # @return [Number, nil] the numeric error code, if any
70
97
  def error
71
98
  @error ||= (command.to_i if numeric_reply? && command[/[45]\d\d/])
72
99
  end
73
100
 
101
+ # @group Type checking
102
+
103
+ # @return [Boolean] true if the message is an numeric reply (as
104
+ # opposed to a command)
105
+ def numeric_reply?
106
+ !!(@numeric_reply ||= @command.match(/^\d{3}$/))
107
+ end
108
+
109
+ # @return [Boolean] true if the message describes an error
110
+ def error?
111
+ !!error
112
+ end
113
+
74
114
  # @return [Boolean] true if this message was sent in a channel
75
115
  def channel?
76
116
  !!channel
@@ -78,7 +118,22 @@ module Cinch
78
118
 
79
119
  # @return [Boolean] true if the message is an CTCP message
80
120
  def ctcp?
81
- params.last =~ /\001.+\001/
121
+ !!(params.last =~ /\001.+\001/)
122
+ end
123
+
124
+ # @return [Boolean] true if the message is an action (/me)
125
+ # @since 2.0.0
126
+ def action?
127
+ ctcp_command == "ACTION"
128
+ end
129
+
130
+ # @endgroup
131
+
132
+ # @return [String, nil] The action message
133
+ # @since 2.0.0
134
+ def action_message
135
+ return nil unless action?
136
+ ctcp_message.split(" ", 2).last
82
137
  end
83
138
 
84
139
  # @return [String, nil] the command part of an CTCP message
@@ -91,25 +146,32 @@ module Cinch
91
146
  def channel
92
147
  @channel ||= begin
93
148
  case command
94
- when "INVITE", RPL_CHANNELMODEIS.to_s, RPL_BANLIST.to_s
95
- @bot.channel_manager.find_ensured(params[1])
96
- when RPL_NAMEREPLY.to_s
97
- @bot.channel_manager.find_ensured(params[2])
149
+ when "INVITE", Constants::RPL_CHANNELMODEIS.to_s, Constants::RPL_BANLIST.to_s
150
+ @bot.channel_list.find_ensured(params[1])
151
+ when Constants::RPL_NAMEREPLY.to_s
152
+ @bot.channel_list.find_ensured(params[2])
98
153
  else
99
154
  if params.first.start_with?("#")
100
- @bot.channel_manager.find_ensured(params.first)
155
+ @bot.channel_list.find_ensured(params.first)
101
156
  elsif numeric_reply? and params[1].start_with?("#")
102
- @bot.channel_manager.find_ensured(params[1])
157
+ @bot.channel_list.find_ensured(params[1])
103
158
  end
104
159
  end
105
160
  end
106
161
  end
107
162
 
163
+ # @return [Target]
164
+ def target
165
+ channel || user
166
+ end
167
+
108
168
  # @api private
109
169
  # @return [MatchData]
110
170
  def match(regexp, type)
111
171
  if type == :ctcp
112
172
  @matches[:ctcp][regexp] ||= ctcp_message.match(regexp)
173
+ elsif type == :action
174
+ @matches[:action][regexp] ||= action_message.match(regexp)
113
175
  else
114
176
  @matches[:other][regexp] ||= message.to_s.match(regexp)
115
177
  end
@@ -139,13 +201,15 @@ module Cinch
139
201
  end
140
202
  end
141
203
 
204
+ # @group Replying
205
+
142
206
  # Replies to a message, automatically determining if it was a
143
207
  # channel or a private message.
144
208
  #
145
209
  # @param [String] text the message
146
210
  # @param [Boolean] prefix if prefix is true and the message was in
147
- # a channel, the reply will be prefixed by the nickname of whoever
148
- # send the mesage
211
+ # a channel, the reply will be prefixed by the nickname of whoever
212
+ # send the mesage
149
213
  # @return [void]
150
214
  def reply(text, prefix = false)
151
215
  text = text.to_s
@@ -153,11 +217,10 @@ module Cinch
153
217
  text = text.split("\n").map {|l| "#{user.nick}: #{l}"}.join("\n")
154
218
  end
155
219
 
156
- (channel || user).send(text)
220
+ target.send(text)
157
221
  end
158
222
 
159
- # Like #reply, but using {Channel#safe_send}/{User#safe_send}
160
- # instead
223
+ # Like #reply, but using {Target#safe_send} instead
161
224
  #
162
225
  # @param (see #reply)
163
226
  # @return (see #reply)
@@ -166,7 +229,7 @@ module Cinch
166
229
  if channel && prefix
167
230
  text = "#{user.nick}: #{text}"
168
231
  end
169
- (channel || user).safe_send(text)
232
+ target.safe_send(text)
170
233
  end
171
234
 
172
235
  # Reply to a CTCP message
@@ -177,7 +240,10 @@ module Cinch
177
240
  user.notice "\001#{ctcp_command} #{answer}\001"
178
241
  end
179
242
 
243
+ # @endgroup
244
+
180
245
  # @return [String]
246
+ # @since 1.1.0
181
247
  def to_s
182
248
  "#<Cinch::Message @raw=#{raw.chomp.inspect} @params=#{@params.inspect} channel=#{channel.inspect} user=#{user.inspect}>"
183
249
  end
@@ -1,12 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
- require "thread"
2
+ require "cinch/open_ended_queue"
3
3
 
4
4
  module Cinch
5
+ # This class manages all outgoing messages, applying rate throttling
6
+ # and fair distribution.
7
+ #
5
8
  # @api private
6
9
  class MessageQueue
7
10
  def initialize(socket, bot)
8
11
  @socket = socket
9
- @queue = Queue.new
12
+ @queues = {:generic => OpenEndedQueue.new}
13
+
14
+ @queues_to_process = Queue.new
15
+ @queued_queues = Set.new
16
+
17
+ @mutex = Mutex.new
10
18
  @time_since_last_send = nil
11
19
  @bot = bot
12
20
 
@@ -15,50 +23,84 @@ module Cinch
15
23
 
16
24
  # @return [void]
17
25
  def queue(message)
18
- command = message.split(" ").first
26
+ command, *rest = message.split(" ")
19
27
 
20
- if command == "PONG"
21
- @queue.unshift(message)
28
+ queue = nil
29
+ case command
30
+ when "PRIVMSG", "NOTICE"
31
+ @mutex.synchronize do
32
+ # we are assuming that each message has only one target,
33
+ # which will be true as long as the user does not send raw
34
+ # messages.
35
+ #
36
+ # this assumption is also reflected in the computation of
37
+ # passed time and processed messages, since our score does
38
+ # not take weights into account.
39
+ queue = @queues[rest.first] ||= OpenEndedQueue.new
40
+ end
22
41
  else
23
- @queue << message
42
+ queue = @queues[:generic]
43
+ end
44
+ queue << message
45
+
46
+ @mutex.synchronize do
47
+ unless @queued_queues.include?(queue)
48
+ @queued_queues << queue
49
+ @queues_to_process << queue
50
+ end
24
51
  end
25
52
  end
26
53
 
27
54
  # @return [void]
28
55
  def process!
29
56
  while true
30
- mps = @bot.config.messages_per_second
31
- max_queue_size = @bot.config.server_queue_size
57
+ wait
32
58
 
33
- if @log.size > 1
34
- time_passed = 0
59
+ queue = @queues_to_process.pop
60
+ message = queue.pop.to_s.chomp
35
61
 
36
- @log.each_with_index do |one, index|
37
- second = @log[index+1]
38
- time_passed += second - one
39
- break if index == @log.size - 2
40
- end
41
-
42
- messages_processed = (time_passed * mps).floor
43
- effective_size = @log.size - messages_processed
44
-
45
- if effective_size <= 0
46
- @log.clear
47
- elsif effective_size >= max_queue_size
48
- sleep 1.0/mps
62
+ if queue.empty?
63
+ @mutex.synchronize do
64
+ @queued_queues.delete(queue)
49
65
  end
66
+ else
67
+ @queues_to_process << queue
50
68
  end
51
69
 
52
- message = @queue.pop.to_s.chomp
53
-
54
70
  begin
55
- @socket.writeline Cinch.encode_outgoing(message, @bot.config.encoding) + "\r\n"
71
+ to_send = Cinch::Utilities::Encoding.encode_outgoing(message, @bot.config.encoding)
72
+ @socket.write to_send + "\r\n"
56
73
  @log << Time.now
57
- @bot.logger.log(message, :outgoing) if @bot.config.verbose
74
+ @bot.loggers.outgoing(message)
58
75
 
59
76
  @time_since_last_send = Time.now
60
77
  rescue IOError
61
- @bot.debug "Could not send message (connectivity problems): #{message}"
78
+ @bot.loggers.error "Could not send message (connectivity problems): #{message}"
79
+ end
80
+ end
81
+ end
82
+
83
+ private
84
+ def wait
85
+ mps = @bot.config.messages_per_second || @bot.irc.network.default_messages_per_second
86
+ max_queue_size = @bot.config.server_queue_size || @bot.irc.network.default_server_queue_size
87
+
88
+ if @log.size > 1
89
+ time_passed = 0
90
+
91
+ @log.each_with_index do |one, index|
92
+ second = @log[index+1]
93
+ time_passed += second - one
94
+ break if index == @log.size - 2
95
+ end
96
+
97
+ messages_processed = (time_passed * mps).floor
98
+ effective_size = @log.size - messages_processed
99
+
100
+ if effective_size <= 0
101
+ @log.clear
102
+ elsif effective_size >= max_queue_size
103
+ sleep 1.0/mps
62
104
  end
63
105
  end
64
106
  end
@@ -1,6 +1,11 @@
1
1
  module Cinch
2
2
  # @api private
3
+ # @since 1.1.0
3
4
  module ModeParser
5
+ # @param [String] modes The mode string as sent by the server
6
+ # @param [Array<String>] params Parameters belonging to the modes
7
+ # @param [Hash<:add => Array<String>, :remove => Array<String>] param_modes
8
+ # A mapping describing which modes require parameters
4
9
  def self.parse_modes(modes, params, param_modes = {})
5
10
  if modes.size == 0
6
11
  raise InvalidModeString, 'Empty mode string'
@@ -30,7 +35,7 @@ module Cinch
30
35
  count = 0
31
36
  else
32
37
  param = nil
33
- if param_modes[direction].include?(ch)
38
+ if param_modes.has_key?(direction) && param_modes[direction].include?(ch)
34
39
  if params.size > 0
35
40
  param = params.shift
36
41
  else