cinch 1.0.2 → 1.1.0
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/README.md +25 -44
- data/examples/basic/autovoice.rb +1 -1
- data/examples/basic/join_part.rb +0 -4
- data/examples/plugins/autovoice.rb +2 -5
- data/examples/plugins/google.rb +1 -2
- data/examples/plugins/hooks.rb +36 -0
- data/examples/plugins/lambdas.rb +35 -0
- data/examples/plugins/last_nick.rb +24 -0
- data/examples/plugins/multiple_matches.rb +1 -10
- data/examples/plugins/own_events.rb +37 -0
- data/examples/plugins/timer.rb +22 -0
- data/examples/plugins/url_shorten.rb +1 -1
- data/lib/cinch.rb +50 -1
- data/lib/cinch/ban.rb +5 -2
- data/lib/cinch/bot.rb +360 -193
- data/lib/cinch/cache_manager.rb +15 -0
- data/lib/cinch/callback.rb +6 -0
- data/lib/cinch/channel.rb +150 -96
- data/lib/cinch/channel_manager.rb +26 -0
- data/lib/cinch/constants.rb +6 -4
- data/lib/cinch/exceptions.rb +9 -0
- data/lib/cinch/irc.rb +197 -82
- data/lib/cinch/logger/formatted_logger.rb +8 -8
- data/lib/cinch/logger/zcbot_logger.rb +37 -0
- data/lib/cinch/mask.rb +17 -3
- data/lib/cinch/message.rb +14 -7
- data/lib/cinch/message_queue.rb +8 -4
- data/lib/cinch/mode_parser.rb +56 -0
- data/lib/cinch/pattern.rb +45 -0
- data/lib/cinch/plugin.rb +129 -34
- data/lib/cinch/rubyext/string.rb +4 -4
- data/lib/cinch/syncable.rb +8 -0
- data/lib/cinch/user.rb +68 -13
- data/lib/cinch/user_manager.rb +60 -0
- metadata +17 -35
- data/Rakefile +0 -66
- data/lib/cinch/PLANNED +0 -4
- data/spec/bot_spec.rb +0 -5
- data/spec/channel_spec.rb +0 -5
- data/spec/cinch_spec.rb +0 -5
- data/spec/irc_spec.rb +0 -5
- data/spec/message_spec.rb +0 -5
- data/spec/plugin_spec.rb +0 -5
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -8
- data/spec/user_spec.rb +0 -5
data/lib/cinch/callback.rb
CHANGED
data/lib/cinch/channel.rb
CHANGED
@@ -13,22 +13,39 @@ module Cinch
|
|
13
13
|
# @param [Bot] bot a bot
|
14
14
|
# @return [Channel]
|
15
15
|
# @see Bot#Channel
|
16
|
+
# @deprecated See {Bot#channel_manager} and {ChannelManager#find_ensured} instead
|
17
|
+
# @note This method does not work properly if running more than one bot
|
18
|
+
# @note This method will be removed in Cinch 2.0.0
|
16
19
|
def find_ensured(name, bot)
|
20
|
+
$stderr.puts "Deprecation warning: Beginning with version 1.1.0, Channel.find_ensured should not be used anymore."
|
21
|
+
puts caller
|
22
|
+
|
17
23
|
downcased_name = name.irc_downcase(bot.irc.isupport["CASEMAPPING"])
|
18
|
-
@channels[downcased_name] ||=
|
19
|
-
@channels[downcased_name]
|
24
|
+
@channels[downcased_name] ||= bot.channel_manager.find_ensured(name)
|
20
25
|
end
|
21
26
|
|
22
27
|
# Finds a channel.
|
23
28
|
#
|
24
29
|
# @param [String] name name of a channel
|
25
30
|
# @return [Channel, nil]
|
31
|
+
# @deprecated See {Bot#channel_manager} and {ChannelManager#find} instead
|
32
|
+
# @note This method does not work properly if running more than one bot
|
33
|
+
# @note This method will be removed in Cinch 2.0.0
|
26
34
|
def find(name)
|
35
|
+
$stderr.puts "Deprecation warning: Beginning with version 1.1.0, Channel.find should not be used anymore."
|
36
|
+
puts caller
|
37
|
+
|
27
38
|
@channels[name]
|
28
39
|
end
|
29
40
|
|
30
41
|
# @return [Array<Channel>] Returns all channels
|
42
|
+
# @deprecated See {Bot#channel_manager} and {CacheManager#each} instead
|
43
|
+
# @note This method does not work properly if running more than one bot
|
44
|
+
# @note This method will be removed in Cinch 2.0.0
|
31
45
|
def all
|
46
|
+
$stderr.puts "Deprecation warning: Beginning with version 1.1.0, User.all should not be used anymore."
|
47
|
+
puts caller
|
48
|
+
|
32
49
|
@channels.values
|
33
50
|
end
|
34
51
|
end
|
@@ -36,28 +53,28 @@ module Cinch
|
|
36
53
|
# @return [Bot]
|
37
54
|
attr_reader :bot
|
38
55
|
|
39
|
-
# @return [String]
|
56
|
+
# @return [String] the channel's name
|
40
57
|
attr_reader :name
|
41
58
|
|
42
|
-
# @return [Array<User>]
|
59
|
+
# @return [Array<User>] all users in the channel
|
43
60
|
attr_reader :users
|
44
61
|
synced_attr_reader :users
|
45
62
|
|
46
|
-
# @return [String]
|
63
|
+
# @return [String] the channel's topic
|
47
64
|
attr_accessor :topic
|
48
65
|
synced_attr_reader :topic
|
49
66
|
|
50
|
-
# @return [Array<Ban>]
|
67
|
+
# @return [Array<Ban>] all active bans
|
51
68
|
attr_reader :bans
|
52
69
|
synced_attr_reader :bans
|
53
70
|
|
54
|
-
# @return [
|
71
|
+
# @return [Hash<String => Object>]
|
55
72
|
attr_reader :modes
|
56
73
|
synced_attr_reader :modes
|
57
74
|
def initialize(name, bot)
|
58
75
|
@bot = bot
|
59
76
|
@name = name
|
60
|
-
@users = {}
|
77
|
+
@users = Hash.new {|h,k| h[k] = []}
|
61
78
|
@bans = []
|
62
79
|
|
63
80
|
@modes = {}
|
@@ -85,8 +102,40 @@ module Cinch
|
|
85
102
|
}
|
86
103
|
end
|
87
104
|
|
105
|
+
# @group Checks
|
106
|
+
|
107
|
+
# @param [User, String] user An {User}-object or a nickname
|
108
|
+
# @return [Boolean] Check if a user is in the channel
|
109
|
+
def has_user?(user)
|
110
|
+
@users.has_key?(User(user))
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# @return [Boolean] true if `user` is opped in the channel
|
115
|
+
def opped?(user)
|
116
|
+
user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
|
117
|
+
@users[user].include? "o"
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Boolean] true if `user` is half-opped in the channel
|
121
|
+
def half_opped?(user)
|
122
|
+
user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
|
123
|
+
@users[user].include? "h"
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [Boolean] true if `user` is voiced in the channel
|
127
|
+
def voiced?(user)
|
128
|
+
user = @bot.user_manager.find_ensured(user) unless user.is_a?(User)
|
129
|
+
@users[user].include? "v"
|
130
|
+
end
|
131
|
+
|
132
|
+
# @endgroup
|
133
|
+
|
134
|
+
# @return [Number] The maximum number of allowed users in the
|
135
|
+
# channel. 0 if unlimited.
|
88
136
|
attr_accessor :limit
|
89
|
-
|
137
|
+
undef_method "limit"
|
138
|
+
undef_method "limit="
|
90
139
|
def limit
|
91
140
|
@modes["l"].to_i
|
92
141
|
end
|
@@ -151,7 +200,7 @@ module Cinch
|
|
151
200
|
end
|
152
201
|
end
|
153
202
|
|
154
|
-
# @return [String, nil]
|
203
|
+
# @return [String, nil] The channel's key (aka password)
|
155
204
|
attr_accessor :key
|
156
205
|
undef_method "key"
|
157
206
|
undef_method "key="
|
@@ -167,17 +216,6 @@ module Cinch
|
|
167
216
|
end
|
168
217
|
end
|
169
218
|
|
170
|
-
# Sets or unsets modes. Most of the time you won't need this but
|
171
|
-
# use setter methods like {Channel#invite_only=}.
|
172
|
-
#
|
173
|
-
# @param [String] s a mode string
|
174
|
-
# @return [void]
|
175
|
-
# @example
|
176
|
-
# channel.mode "+n"
|
177
|
-
def mode(s)
|
178
|
-
@bot.raw "MODE #@name #{s}"
|
179
|
-
end
|
180
|
-
|
181
219
|
# @api private
|
182
220
|
# @return [void]
|
183
221
|
def sync_modes(all = true)
|
@@ -189,17 +227,7 @@ module Cinch
|
|
189
227
|
@bot.raw "MODE #@name"
|
190
228
|
end
|
191
229
|
|
192
|
-
# @
|
193
|
-
def opped?(user)
|
194
|
-
user = User.find_ensured(user, @bot) unless user.is_a?(User)
|
195
|
-
@users[user] == "@"
|
196
|
-
end
|
197
|
-
|
198
|
-
# @return [Boolean] true if `user` is voiced in the channel
|
199
|
-
def voiced?(user)
|
200
|
-
user = User.find_ensured(user, @bot) unless user.is_a?(User)
|
201
|
-
@users[user] == "+"
|
202
|
-
end
|
230
|
+
# @group Channel Manipulation
|
203
231
|
|
204
232
|
# Bans someone from the channel.
|
205
233
|
#
|
@@ -247,11 +275,78 @@ module Cinch
|
|
247
275
|
@bot.raw "MODE #@name -v #{user}"
|
248
276
|
end
|
249
277
|
|
278
|
+
# Invites a user to the channel.
|
279
|
+
#
|
280
|
+
# @param [String, User] user the user to invite
|
281
|
+
# @return [void]
|
282
|
+
def invite(user)
|
283
|
+
@bot.raw("INVITE #{user} #@name")
|
284
|
+
end
|
285
|
+
|
286
|
+
# Sets the topic.
|
287
|
+
#
|
288
|
+
# @param [String] new_topic the new topic
|
289
|
+
# @raise [Exceptions::TopicTooLong]
|
290
|
+
def topic=(new_topic)
|
291
|
+
if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
|
292
|
+
raise Exceptions::TopicTooLong, new_topic
|
293
|
+
end
|
294
|
+
|
295
|
+
@bot.raw "TOPIC #@name :#{new_topic}"
|
296
|
+
end
|
297
|
+
|
298
|
+
# Kicks a user from the channel.
|
299
|
+
#
|
300
|
+
# @param [String, User] user the user to kick
|
301
|
+
# @param [String] a reason for the kick
|
302
|
+
# @raise [Exceptions::KickReasonTooLong]
|
303
|
+
# @return [void]
|
304
|
+
def kick(user, reason = nil)
|
305
|
+
if reason.to_s.size > @bot.irc.isupport["KICKLEN"] && @bot.strict?
|
306
|
+
raise Exceptions::KickReasonTooLong, reason
|
307
|
+
end
|
308
|
+
|
309
|
+
@bot.raw("KICK #@name #{user} :#{reason}")
|
310
|
+
end
|
311
|
+
|
312
|
+
# Sets or unsets modes. Most of the time you won't need this but
|
313
|
+
# use setter methods like {Channel#invite_only=}.
|
314
|
+
#
|
315
|
+
# @param [String] s a mode string
|
316
|
+
# @return [void]
|
317
|
+
# @example
|
318
|
+
# channel.mode "+n"
|
319
|
+
def mode(s)
|
320
|
+
@bot.raw "MODE #@name #{s}"
|
321
|
+
end
|
322
|
+
|
323
|
+
# Causes the bot to part from the channel.
|
324
|
+
#
|
325
|
+
# @param [String] message the part message.
|
326
|
+
# @return [void]
|
327
|
+
def part(message = nil)
|
328
|
+
@bot.raw "PART #@name :#{message}"
|
329
|
+
end
|
330
|
+
|
331
|
+
# Joins the channel
|
332
|
+
#
|
333
|
+
# @param [String] key the channel key, if any. If none is
|
334
|
+
# specified but @key is set, @key will be used
|
335
|
+
# @return [void]
|
336
|
+
def join(key = nil)
|
337
|
+
if key.nil? and self.key != true
|
338
|
+
key = self.key
|
339
|
+
end
|
340
|
+
@bot.raw "JOIN #{[@name, key].compact.join(" ")}"
|
341
|
+
end
|
342
|
+
|
343
|
+
# @endgroup
|
344
|
+
|
250
345
|
# @api private
|
251
346
|
# @return [void]
|
252
|
-
def add_user(user,
|
347
|
+
def add_user(user, modes = [])
|
253
348
|
@in_channel = true if user == @bot
|
254
|
-
@users[user] =
|
349
|
+
@users[user] = modes
|
255
350
|
end
|
256
351
|
|
257
352
|
# @api private
|
@@ -269,6 +364,8 @@ module Cinch
|
|
269
364
|
@users.clear
|
270
365
|
end
|
271
366
|
|
367
|
+
# @group Sending messages
|
368
|
+
|
272
369
|
# Send a message to the channel.
|
273
370
|
#
|
274
371
|
# @param [String] message the message
|
@@ -280,6 +377,24 @@ module Cinch
|
|
280
377
|
alias_method :privmsg, :send
|
281
378
|
alias_method :msg, :send
|
282
379
|
|
380
|
+
# Send a notice to the channel.
|
381
|
+
#
|
382
|
+
# @param [String] message the message
|
383
|
+
# @return [void]
|
384
|
+
def notice(message)
|
385
|
+
@bot.notice(@name, message)
|
386
|
+
end
|
387
|
+
|
388
|
+
# Like {#safe_send} but for notices.
|
389
|
+
#
|
390
|
+
# @param (see #safe_send)
|
391
|
+
# @return (see #safe_send)
|
392
|
+
# @see #safe_send
|
393
|
+
# @todo (see #safe_send)
|
394
|
+
def safe_notice(message)
|
395
|
+
@bot.safe_notice(@name, message)
|
396
|
+
end
|
397
|
+
|
283
398
|
# Send a message to the channel, but remove any non-printable
|
284
399
|
# characters. The purpose of this method is to send text from
|
285
400
|
# untrusted sources, like other users or feeds.
|
@@ -331,68 +446,7 @@ module Cinch
|
|
331
446
|
@bot.safe_action(@name, message)
|
332
447
|
end
|
333
448
|
|
334
|
-
|
335
|
-
# Invites a user to the channel.
|
336
|
-
#
|
337
|
-
# @param [String, User] user the user to invite
|
338
|
-
# @return [void]
|
339
|
-
def invite(user)
|
340
|
-
@bot.raw("INVITE #{user} #@name")
|
341
|
-
end
|
342
|
-
|
343
|
-
# Sets the topic.
|
344
|
-
#
|
345
|
-
# @param [String] new_topic the new topic
|
346
|
-
# @raise [Exceptions::TopicTooLong]
|
347
|
-
def topic=(new_topic)
|
348
|
-
if new_topic.size > @bot.irc.isupport["TOPICLEN"] && @bot.strict?
|
349
|
-
raise Exceptions::TopicTooLong, new_topic
|
350
|
-
end
|
351
|
-
|
352
|
-
@bot.raw "TOPIC #@name :#{new_topic}"
|
353
|
-
end
|
354
|
-
|
355
|
-
# Kicks a user from the channel.
|
356
|
-
#
|
357
|
-
# @param [String, User] user the user to kick
|
358
|
-
# @param [String] a reason for the kick
|
359
|
-
# @raise [Exceptions::KickReasonTooLong]
|
360
|
-
# @return [void]
|
361
|
-
def kick(user, reason = nil)
|
362
|
-
if reason.to_s.size > @bot.irc.isupport["KICKLEN"] && @bot.strict?
|
363
|
-
raise Exceptions::KickReasonTooLong, reason
|
364
|
-
end
|
365
|
-
|
366
|
-
@bot.raw("KICK #@name #{user} :#{reason}")
|
367
|
-
end
|
368
|
-
|
369
|
-
# Invites a user to the channel.
|
370
|
-
#
|
371
|
-
# @param [String, User] user the user to invite
|
372
|
-
# @return [void]
|
373
|
-
def invite(user)
|
374
|
-
@bot.raw "INVITE #{user} #@name"
|
375
|
-
end
|
376
|
-
|
377
|
-
# Causes the bot to part from the channel.
|
378
|
-
#
|
379
|
-
# @param [String] message the part message.
|
380
|
-
# @return [void]
|
381
|
-
def part(message = nil)
|
382
|
-
@bot.raw "PART #@name :#{message}"
|
383
|
-
end
|
384
|
-
|
385
|
-
# Joins the channel
|
386
|
-
#
|
387
|
-
# @param [String] key the channel key, if any. If none is
|
388
|
-
# specified but @key is set, @key will be used
|
389
|
-
# @return [void]
|
390
|
-
def join(key = nil)
|
391
|
-
if key.nil? and self.key != true
|
392
|
-
key = self.key
|
393
|
-
end
|
394
|
-
@bot.raw "JOIN #{[@name, key].compact.join(" ")}"
|
395
|
-
end
|
449
|
+
# @endgroup
|
396
450
|
|
397
451
|
# @return [Boolean]
|
398
452
|
def ==(other)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "cinch/cache_manager"
|
2
|
+
|
3
|
+
module Cinch
|
4
|
+
class ChannelManager < CacheManager
|
5
|
+
# Finds or creates a channel.
|
6
|
+
#
|
7
|
+
# @param [String] name name of a channel
|
8
|
+
# @return [Channel]
|
9
|
+
# @see Bot#Channel
|
10
|
+
def find_ensured(name)
|
11
|
+
downcased_name = name.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
|
12
|
+
@mutex.synchronize do
|
13
|
+
@cache[downcased_name] ||= Channel.new(name, @bot)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Finds a channel.
|
18
|
+
#
|
19
|
+
# @param [String] name name of a channel
|
20
|
+
# @return [Channel, nil]
|
21
|
+
def find(name)
|
22
|
+
downcased_name = name.irc_downcase(@bot.irc.isupport["CASEMAPPING"])
|
23
|
+
@cache[downcased_name]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/cinch/constants.rb
CHANGED
@@ -38,7 +38,7 @@ module Cinch
|
|
38
38
|
# 412 - 414 are returned by PRIVMSG to indicate that the message
|
39
39
|
# wasn't delivered for some reason. ERR_NOTOPLEVEL and
|
40
40
|
# ERR_WILDTOPLEVEL are errors that are returned when an invalid use of
|
41
|
-
# "PRIVMSG
|
41
|
+
# "PRIVMSG $<server>" or "PRIVMSG #<host>" is attempted.
|
42
42
|
ERR_WILDTOPLEVEL = 414
|
43
43
|
|
44
44
|
# Returned to a registered client to indicate that the command sent is
|
@@ -229,8 +229,10 @@ module Cinch
|
|
229
229
|
# Reply by the server showing its version details. The <version>
|
230
230
|
# is the version of the software being used (including any patchlevel
|
231
231
|
# revisions) and the <debuglevel> is used to indicate if the
|
232
|
-
# server is running in "debug mode"
|
233
|
-
#
|
232
|
+
# server is running in "debug mode".
|
233
|
+
#
|
234
|
+
#The "comments" field may contain any comments about the version or
|
235
|
+
# further version details.
|
234
236
|
RPL_VERSION = 351
|
235
237
|
RPL_WHOREPLY = 352
|
236
238
|
|
@@ -238,7 +240,7 @@ module Cinch
|
|
238
240
|
# message. The RPL_WHOREPLY is only sent if there is an appropriate
|
239
241
|
# match to the WHO query. If there is a list of parameters supplied
|
240
242
|
# with a WHO message, a RPL_ENDOFWHO must be sent after processing
|
241
|
-
# each list item with
|
243
|
+
# each list item with <name> being the item.
|
242
244
|
RPL_ENDOFWHO = 315
|
243
245
|
RPL_NAMREPLY = 353
|
244
246
|
RPL_NAMEREPLY = RPL_NAMREPLY
|
data/lib/cinch/exceptions.rb
CHANGED
@@ -21,5 +21,14 @@ module Cinch
|
|
21
21
|
|
22
22
|
class UnsupportedFeature < Generic
|
23
23
|
end
|
24
|
+
|
25
|
+
class UnsupportedMode < Generic
|
26
|
+
def initialize(mode)
|
27
|
+
super "Cinch does not support the mode #{mode} yet."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvalidModeString < Generic
|
32
|
+
end
|
24
33
|
end
|
25
34
|
end
|