cinch 1.1.3 → 2.0.0.pre.1

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 (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