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.
- data/LICENSE +1 -0
- data/README.md +3 -3
- data/docs/bot_options.md +435 -0
- data/docs/changes.md +440 -0
- data/docs/common_mistakes.md +35 -0
- data/docs/common_tasks.md +47 -0
- data/docs/encodings.md +67 -0
- data/docs/events.md +272 -0
- data/docs/logging.md +5 -0
- data/docs/migrating.md +267 -0
- data/docs/readme.md +18 -0
- data/examples/plugins/custom_prefix.rb +1 -1
- data/examples/plugins/dice_roll.rb +38 -0
- data/examples/plugins/lambdas.rb +1 -1
- data/examples/plugins/memo.rb +16 -10
- data/examples/plugins/url_shorten.rb +1 -0
- data/lib/cinch.rb +5 -60
- data/lib/cinch/ban.rb +13 -7
- data/lib/cinch/bot.rb +228 -403
- data/lib/cinch/{cache_manager.rb → cached_list.rb} +5 -1
- data/lib/cinch/callback.rb +3 -0
- data/lib/cinch/channel.rb +119 -195
- data/lib/cinch/{channel_manager.rb → channel_list.rb} +6 -3
- data/lib/cinch/configuration.rb +73 -0
- data/lib/cinch/configuration/bot.rb +47 -0
- data/lib/cinch/configuration/dcc.rb +16 -0
- data/lib/cinch/configuration/plugins.rb +41 -0
- data/lib/cinch/configuration/sasl.rb +17 -0
- data/lib/cinch/configuration/ssl.rb +19 -0
- data/lib/cinch/configuration/storage.rb +37 -0
- data/lib/cinch/configuration/timeouts.rb +14 -0
- data/lib/cinch/constants.rb +531 -369
- data/lib/cinch/dcc.rb +12 -0
- data/lib/cinch/dcc/dccable_object.rb +37 -0
- data/lib/cinch/dcc/incoming.rb +1 -0
- data/lib/cinch/dcc/incoming/send.rb +131 -0
- data/lib/cinch/dcc/outgoing.rb +1 -0
- data/lib/cinch/dcc/outgoing/send.rb +115 -0
- data/lib/cinch/exceptions.rb +8 -1
- data/lib/cinch/formatting.rb +106 -0
- data/lib/cinch/handler.rb +104 -0
- data/lib/cinch/handler_list.rb +86 -0
- data/lib/cinch/helpers.rb +167 -10
- data/lib/cinch/irc.rb +525 -110
- data/lib/cinch/isupport.rb +11 -9
- data/lib/cinch/logger.rb +168 -0
- data/lib/cinch/logger/formatted_logger.rb +72 -55
- data/lib/cinch/logger/zcbot_logger.rb +9 -24
- data/lib/cinch/logger_list.rb +62 -0
- data/lib/cinch/mask.rb +19 -10
- data/lib/cinch/message.rb +94 -28
- data/lib/cinch/message_queue.rb +70 -28
- data/lib/cinch/mode_parser.rb +6 -1
- data/lib/cinch/network.rb +104 -0
- data/lib/cinch/{rubyext/queue.rb → open_ended_queue.rb} +8 -1
- data/lib/cinch/pattern.rb +24 -4
- data/lib/cinch/plugin.rb +352 -177
- data/lib/cinch/plugin_list.rb +35 -0
- data/lib/cinch/rubyext/float.rb +3 -0
- data/lib/cinch/rubyext/module.rb +7 -0
- data/lib/cinch/rubyext/string.rb +9 -0
- data/lib/cinch/sasl.rb +34 -0
- data/lib/cinch/sasl/dh_blowfish.rb +71 -0
- data/lib/cinch/sasl/diffie_hellman.rb +47 -0
- data/lib/cinch/sasl/mechanism.rb +6 -0
- data/lib/cinch/sasl/plain.rb +26 -0
- data/lib/cinch/storage.rb +62 -0
- data/lib/cinch/storage/null.rb +12 -0
- data/lib/cinch/storage/yaml.rb +96 -0
- data/lib/cinch/syncable.rb +13 -1
- data/lib/cinch/target.rb +144 -0
- data/lib/cinch/timer.rb +145 -0
- data/lib/cinch/user.rb +169 -225
- data/lib/cinch/{user_manager.rb → user_list.rb} +7 -2
- data/lib/cinch/utilities/deprecation.rb +12 -0
- data/lib/cinch/utilities/encoding.rb +54 -0
- data/lib/cinch/utilities/kernel.rb +13 -0
- data/lib/cinch/utilities/string.rb +13 -0
- data/lib/cinch/version.rb +4 -0
- metadata +88 -47
- data/lib/cinch/logger/logger.rb +0 -44
- data/lib/cinch/logger/null_logger.rb +0 -18
- data/lib/cinch/rubyext/infinity.rb +0 -1
data/lib/cinch/mask.rb
CHANGED
@@ -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 [
|
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 [
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
63
|
+
mask = Mask.new(target.to_s)
|
57
64
|
end
|
65
|
+
|
66
|
+
return mask
|
58
67
|
end
|
59
68
|
end
|
60
69
|
end
|
data/lib/cinch/message.rb
CHANGED
@@ -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.
|
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.
|
96
|
-
when RPL_NAMEREPLY.to_s
|
97
|
-
@bot.
|
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.
|
155
|
+
@bot.channel_list.find_ensured(params.first)
|
101
156
|
elsif numeric_reply? and params[1].start_with?("#")
|
102
|
-
@bot.
|
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
|
-
#
|
148
|
-
#
|
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
|
-
|
220
|
+
target.send(text)
|
157
221
|
end
|
158
222
|
|
159
|
-
# Like #reply, but using {
|
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
|
-
|
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
|
data/lib/cinch/message_queue.rb
CHANGED
@@ -1,12 +1,20 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
require "
|
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
|
-
@
|
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(" ")
|
26
|
+
command, *rest = message.split(" ")
|
19
27
|
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
-
|
31
|
-
max_queue_size = @bot.config.server_queue_size
|
57
|
+
wait
|
32
58
|
|
33
|
-
|
34
|
-
|
59
|
+
queue = @queues_to_process.pop
|
60
|
+
message = queue.pop.to_s.chomp
|
35
61
|
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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.
|
74
|
+
@bot.loggers.outgoing(message)
|
58
75
|
|
59
76
|
@time_since_last_send = Time.now
|
60
77
|
rescue IOError
|
61
|
-
@bot.
|
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
|
data/lib/cinch/mode_parser.rb
CHANGED
@@ -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
|