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