ircsupport 0.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/.gitignore +6 -0
- data/.travis.yml +8 -0
- data/CHANGES.md +3 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.md +134 -0
- data/Rakefile +16 -0
- data/ircsupport.gemspec +26 -0
- data/lib/ircsupport.rb +10 -0
- data/lib/ircsupport/case.rb +84 -0
- data/lib/ircsupport/encoding.rb +74 -0
- data/lib/ircsupport/formatting.rb +149 -0
- data/lib/ircsupport/masks.rb +72 -0
- data/lib/ircsupport/message.rb +616 -0
- data/lib/ircsupport/modes.rb +101 -0
- data/lib/ircsupport/numerics.rb +242 -0
- data/lib/ircsupport/parser.rb +340 -0
- data/lib/ircsupport/validations.rb +33 -0
- data/lib/ircsupport/version.rb +4 -0
- data/test/case_test.rb +41 -0
- data/test/encoding_test.rb +27 -0
- data/test/formatting_test.rb +65 -0
- data/test/masks_test.rb +36 -0
- data/test/message_test.rb +416 -0
- data/test/modes_test.rb +34 -0
- data/test/numerics_test.rb +14 -0
- data/test/parser_test.rb +55 -0
- data/test/test_coverage.rb +8 -0
- data/test/test_helper.rb +5 -0
- data/test/validations_test.rb +35 -0
- metadata +143 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'ircsupport/case'
|
2
|
+
|
3
|
+
module IRCSupport
|
4
|
+
module Masks
|
5
|
+
# @private
|
6
|
+
@@mask_wildcard = '[\x01-\xFF]{0,}'
|
7
|
+
# @private
|
8
|
+
@@mask_optional = '[\x01-\xFF]{1,1}'
|
9
|
+
|
10
|
+
# @param [String] mask The mask to match against.
|
11
|
+
# @param [String] string The string to match against the mask.
|
12
|
+
# @param [Symbol] casemapping The IRC casemapping to use in the match.
|
13
|
+
# @return [Boolean] Will be true of the string matches the mask.
|
14
|
+
def matches_mask(mask, string, casemapping = :rfc1459)
|
15
|
+
if mask =~ /\$/
|
16
|
+
raise ArgumentError, "Extended bans are not supported"
|
17
|
+
end
|
18
|
+
string = IRCSupport::Case.irc_upcase(string, casemapping)
|
19
|
+
mask = Regexp.quote(irc_upcase(mask, casemapping))
|
20
|
+
mask.gsub!('\*', @@mask_wildcard)
|
21
|
+
mask.gsub!('\?', @@mask_optional)
|
22
|
+
mask = Regexp.new(mask, nil, 'n')
|
23
|
+
return true if string =~ /\A#{mask}\z/
|
24
|
+
return false
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Array] mask The masks to match against.
|
28
|
+
# @param [Array] strings The strings to match against the masks.
|
29
|
+
# @param [Symbol] casemapping The IRC casemapping to use in the match.
|
30
|
+
# @return [Hash] Each mask that was matched will be present as a key,
|
31
|
+
# and the values will be arrays of the strings that matched.
|
32
|
+
def matches_mask_array(masks, strings, casemapping = :rfc1459)
|
33
|
+
results = {}
|
34
|
+
masks.each do |mask|
|
35
|
+
strings.each do |string|
|
36
|
+
if matches_mask(mask, string, casemapping)
|
37
|
+
results[mask] ||= []
|
38
|
+
results[mask] << string
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
return results
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [String] mask A partial mask (e.g. 'foo*').
|
46
|
+
# @return [String] A normalized mask (e.g. 'foo*!*@*).
|
47
|
+
def normalize_mask(mask)
|
48
|
+
mask = mask.dup
|
49
|
+
mask.gsub!(/\*{2,}/, '*')
|
50
|
+
parts = []
|
51
|
+
remainder = nil
|
52
|
+
|
53
|
+
if mask !~ /!/ && mask =~ /@/
|
54
|
+
remainder = mask
|
55
|
+
parts[0] = '*'
|
56
|
+
else
|
57
|
+
parts[0], remainder = mask.split(/!/, 2)
|
58
|
+
end
|
59
|
+
|
60
|
+
if remainder
|
61
|
+
remainder.gsub!(/!/, '')
|
62
|
+
parts[1..2] = remainder.split(/@/, 2)
|
63
|
+
end
|
64
|
+
parts[2].gsub!(/@/, '') if parts[2]
|
65
|
+
|
66
|
+
(1..2).each { |i| parts[i] ||= '*' }
|
67
|
+
return parts[0] + "!" + parts[1] + "@" + parts[2]
|
68
|
+
end
|
69
|
+
|
70
|
+
module_function :matches_mask, :matches_mask_array, :normalize_mask
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,616 @@
|
|
1
|
+
require 'ircsupport/numerics'
|
2
|
+
require 'ipaddr'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module IRCSupport
|
6
|
+
class Message
|
7
|
+
# @return [String] The sender prefix of the IRC message, if any.
|
8
|
+
attr_accessor :prefix
|
9
|
+
|
10
|
+
# @return [String] The IRC command.
|
11
|
+
attr_accessor :command
|
12
|
+
|
13
|
+
# @return [Array] The arguments to the IRC command.
|
14
|
+
attr_accessor :args
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def initialize(args)
|
18
|
+
@prefix = args[:prefix]
|
19
|
+
@command = args[:command]
|
20
|
+
@args = args[:args]
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] The type of the IRC message.
|
24
|
+
def type
|
25
|
+
return @type if @type
|
26
|
+
return @command.downcase if self.class.name == 'IRCSupport::Message'
|
27
|
+
type = self.class.name.match(/^IRCSupport::Message::(.*)/)[1]
|
28
|
+
return type.gsub(/::|(?<=[[:lower:]])(?=[[:upper:]])/, '_').downcase
|
29
|
+
end
|
30
|
+
|
31
|
+
class Numeric < Message
|
32
|
+
# @return [String] The IRC command numeric.
|
33
|
+
attr_accessor :numeric
|
34
|
+
|
35
|
+
# @return [String] The name of the IRC command numeric.
|
36
|
+
attr_accessor :numeric_name
|
37
|
+
|
38
|
+
# @return [String] The arguments to the numeric command.
|
39
|
+
attr_accessor :numeric_args
|
40
|
+
|
41
|
+
# @private
|
42
|
+
def initialize(args)
|
43
|
+
super(args)
|
44
|
+
@numeric = args[:command]
|
45
|
+
@numeric_args = args[:args]
|
46
|
+
@numeric_name = IRCSupport::Numerics.numeric_to_name(@numeric)
|
47
|
+
@type = @numeric
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [Boolean] Will be true if this is an error numeric.
|
51
|
+
def is_error?
|
52
|
+
return @numeric_name =~ /^ERR/ ? true : false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Numeric005 < Numeric
|
57
|
+
# @private
|
58
|
+
@@isupport_mappings = {
|
59
|
+
%w[MODES MAXCHANNELS NICKLEN MAXBANS TOPICLEN
|
60
|
+
KICKLEN CHANNELLEN CHIDLEN SILENCE AWAYLEN
|
61
|
+
MAXTARGETS WATCH MONITOR] => ->(v) { v.to_i },
|
62
|
+
|
63
|
+
%w[STATUSMSG ELIST CHANTYPES] => ->(v) { v.split("") },
|
64
|
+
|
65
|
+
%w[CASEMAPPING] => ->(v) { v.to_sym },
|
66
|
+
|
67
|
+
%w[NETWORK] => ->(v) { v },
|
68
|
+
|
69
|
+
%w[PREFIX] => ->(v) {
|
70
|
+
modes, prefixes = v.match(/^\((.+)\)(.+)$/)[1..2]
|
71
|
+
h = {}
|
72
|
+
modes.split("").each_with_index do |c, i|
|
73
|
+
h[c] = prefixes[i]
|
74
|
+
end
|
75
|
+
h
|
76
|
+
},
|
77
|
+
|
78
|
+
%w[CHANMODES] => ->(v) {
|
79
|
+
h = {}
|
80
|
+
h["A"], h["B"], h["C"], h["D"] = v.split(",").map {|l| l.split("")}
|
81
|
+
h
|
82
|
+
},
|
83
|
+
|
84
|
+
%w[CHANLIMIT MAXLIST IDCHAN] => ->(v) {
|
85
|
+
h = {}
|
86
|
+
v.split(",").each do |pair|
|
87
|
+
args, num = pair.split(":")
|
88
|
+
args.split("").each do |arg|
|
89
|
+
h[arg] = num.to_i
|
90
|
+
end
|
91
|
+
end
|
92
|
+
h
|
93
|
+
},
|
94
|
+
|
95
|
+
%w[TARGMAX] => ->(v) {
|
96
|
+
h = {}
|
97
|
+
v.split(",").each do |pair|
|
98
|
+
name, value = pair.split(":")
|
99
|
+
h[name] = value.to_i
|
100
|
+
end
|
101
|
+
h
|
102
|
+
},
|
103
|
+
}
|
104
|
+
|
105
|
+
# @return [Hash] The isupport options contained in the command.
|
106
|
+
attr_accessor :isupport
|
107
|
+
|
108
|
+
# @private
|
109
|
+
def initialize(args)
|
110
|
+
super(args)
|
111
|
+
@isupport = {}
|
112
|
+
args[:args].each do |value|
|
113
|
+
name, value = value.split(/=/, 2)
|
114
|
+
if value
|
115
|
+
proc = @@isupport_mappings.find {|key, _| key.include?(name)}
|
116
|
+
@isupport[name] = (proc && proc[1].call(value)) || value
|
117
|
+
else
|
118
|
+
@isupport[name] = true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class Numeric353 < Numeric
|
125
|
+
# @return [String] The channel.
|
126
|
+
attr_accessor :channel
|
127
|
+
|
128
|
+
# @return [String] The channel type.
|
129
|
+
attr_accessor :channel_type
|
130
|
+
|
131
|
+
# @return [Array] Each element is an array of two elements: the user
|
132
|
+
# prefix (if any), and the name of the user.
|
133
|
+
attr_accessor :users
|
134
|
+
|
135
|
+
# @private
|
136
|
+
def initialize(args)
|
137
|
+
super(args)
|
138
|
+
data = @args.last(@args.size - 1)
|
139
|
+
@channel_type = data.shift if data[0] =~ /^[@=*]$/
|
140
|
+
@channel = data[0]
|
141
|
+
@users = []
|
142
|
+
prefixes = args[:isupport]["PREFIX"].values.map { |p| Regexp.quote p }
|
143
|
+
|
144
|
+
data[1].split(/\s+/).each do |user|
|
145
|
+
user.sub! /^(#{prefixes.join '|'})/, ''
|
146
|
+
@users.push [$1, user]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Numeric352 < Numeric
|
152
|
+
# @return [String] The target of the who reply, either a nickname or
|
153
|
+
# a channel name.
|
154
|
+
attr_accessor :target
|
155
|
+
|
156
|
+
# @return [String] The username.
|
157
|
+
attr_accessor :username
|
158
|
+
|
159
|
+
# @return [String] The host name.
|
160
|
+
attr_accessor :hostname
|
161
|
+
|
162
|
+
# @return [String] The server name.
|
163
|
+
attr_accessor :server
|
164
|
+
|
165
|
+
# @return [String] The nickname.
|
166
|
+
attr_accessor :nickname
|
167
|
+
|
168
|
+
# @return [Array] The user's prefixes.
|
169
|
+
attr_accessor :prefixes
|
170
|
+
|
171
|
+
# @return [Boolean] The away status.
|
172
|
+
attr_accessor :away
|
173
|
+
|
174
|
+
# @return [Fixnum] The user's hop count.
|
175
|
+
attr_accessor :hops
|
176
|
+
|
177
|
+
# @return [String] The user's realname.
|
178
|
+
attr_accessor :realname
|
179
|
+
|
180
|
+
# @private
|
181
|
+
def initialize(args)
|
182
|
+
super(args)
|
183
|
+
@target, @username, @hostname, @server, @nickname, status, rest =
|
184
|
+
@args.last(@args.size - 1)
|
185
|
+
status.sub! /[GH]/, ''
|
186
|
+
@away = $1 == 'G' ? true : false
|
187
|
+
@prefixes = status.split ''
|
188
|
+
@hops, @realname = rest.split /\s/, 2
|
189
|
+
@hops = @hops.to_i
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class DCC < Message
|
194
|
+
# @return [String] The sender of the DCC message.
|
195
|
+
attr_accessor :sender
|
196
|
+
|
197
|
+
# @return [String] The argument string to the DCC message.
|
198
|
+
attr_accessor :dcc_args
|
199
|
+
|
200
|
+
# @private
|
201
|
+
def initialize(args)
|
202
|
+
super(args)
|
203
|
+
@sender = args[:prefix]
|
204
|
+
@dcc_args = args[:args][1]
|
205
|
+
@dcc_type = args[:dcc_type]
|
206
|
+
@type = "dcc_#{@dcc_type.downcase}"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
class DCC::Chat < DCC
|
211
|
+
# @return [IPAddr] The sender's IP address.
|
212
|
+
attr_accessor :address
|
213
|
+
|
214
|
+
# @return [Fixnum] The sender's port number.
|
215
|
+
attr_accessor :port
|
216
|
+
|
217
|
+
# @private
|
218
|
+
def initialize(args)
|
219
|
+
super(args)
|
220
|
+
return if @dcc_args !~ /^(?:".+"|[^ ]+) +(\d+) +(\d+)/
|
221
|
+
@address = IPAddr.new($1.to_i, Socket::AF_INET)
|
222
|
+
@port = $2.to_i
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
class DCC::Send < DCC
|
227
|
+
# @return [IPAddr] The sender's IP address.
|
228
|
+
attr_accessor :address
|
229
|
+
|
230
|
+
# @return [Fixnum] The sender's port number.
|
231
|
+
attr_accessor :port
|
232
|
+
|
233
|
+
# @return [Pathname] The source filename.
|
234
|
+
attr_accessor :filename
|
235
|
+
|
236
|
+
# @return [Fixnum] The size of the source file, in bytes.
|
237
|
+
attr_accessor :size
|
238
|
+
|
239
|
+
# @private
|
240
|
+
def initialize(args)
|
241
|
+
super(args)
|
242
|
+
return if @dcc_args !~ /^(".+"|[^ ]+) +(\d+) +(\d+)(?: +(\d+))?/
|
243
|
+
@filename = $1
|
244
|
+
@address = IPAddr.new($2.to_i, Socket::AF_INET)
|
245
|
+
@port = $3.to_i
|
246
|
+
@size = $4.to_i
|
247
|
+
|
248
|
+
if @filename =~ /^"/
|
249
|
+
@filename.gsub!(/^"|"$/, '')
|
250
|
+
@filename.gsub!(/\\"/, '"');
|
251
|
+
end
|
252
|
+
|
253
|
+
@filename = Pathname.new(@filename).basename
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class DCC::Accept < DCC
|
258
|
+
# @return [Pathname] The source filename.
|
259
|
+
attr_accessor :filename
|
260
|
+
|
261
|
+
# @return [Fixnum] The sender's port number.
|
262
|
+
attr_accessor :port
|
263
|
+
|
264
|
+
# @return [Fixnum] The byte position in the file.
|
265
|
+
attr_accessor :position
|
266
|
+
|
267
|
+
# @private
|
268
|
+
def initialize(args)
|
269
|
+
super(args)
|
270
|
+
return if @dcc_args !~ /^(".+"|[^ ]+) +(\d+) +(\d+)/
|
271
|
+
@filename = $1
|
272
|
+
@port = $2.to_i
|
273
|
+
@position = $3.to_i
|
274
|
+
|
275
|
+
if @filename =~ /^"/
|
276
|
+
@filename.gsub!(/^"|"$/, '')
|
277
|
+
@filename.gsub!(/\\"/, '"');
|
278
|
+
end
|
279
|
+
|
280
|
+
@filename = Pathname.new(@filename).basename
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
class DCC::Resume < DCC::Accept; end
|
285
|
+
|
286
|
+
class Error < Message
|
287
|
+
# @return [String] The error message.
|
288
|
+
attr_accessor :error
|
289
|
+
|
290
|
+
# @private
|
291
|
+
def initialize(args)
|
292
|
+
super(args)
|
293
|
+
@error = args[:args][0]
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class Invite < Message
|
298
|
+
# @return [String] The user who sent the invite.
|
299
|
+
attr_accessor :inviter
|
300
|
+
|
301
|
+
# @return [String] The name of the channel you're being invited to.
|
302
|
+
attr_accessor :channel
|
303
|
+
|
304
|
+
# @private
|
305
|
+
def initialize(args)
|
306
|
+
super(args)
|
307
|
+
@inviter = args[:prefix]
|
308
|
+
@channel = args[:args][1]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
class Join < Message
|
313
|
+
# @return [String] The user who is joining.
|
314
|
+
attr_accessor :joiner
|
315
|
+
|
316
|
+
# @return [String] The name of the channel being joined.
|
317
|
+
attr_accessor :channel
|
318
|
+
|
319
|
+
# @private
|
320
|
+
def initialize(args)
|
321
|
+
super(args)
|
322
|
+
@joiner = args[:prefix]
|
323
|
+
@channel = args[:args][0]
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class Part < Message
|
328
|
+
# @return [String] The user who is parting.
|
329
|
+
attr_accessor :parter
|
330
|
+
|
331
|
+
# @return [String] The name of the channel being parted.
|
332
|
+
attr_accessor :channel
|
333
|
+
|
334
|
+
# @return [String] The part message, if any.
|
335
|
+
attr_accessor :message
|
336
|
+
|
337
|
+
# @private
|
338
|
+
def initialize(args)
|
339
|
+
super(args)
|
340
|
+
@parter = args[:prefix]
|
341
|
+
@channel = args[:args][0]
|
342
|
+
@message = args[:args][1]
|
343
|
+
@message = nil if @message && @message.empty?
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
class Kick < Message
|
348
|
+
# @return [String] The user who is doing the kicking.
|
349
|
+
attr_accessor :kicker
|
350
|
+
|
351
|
+
# @return [String] The name of the channel.
|
352
|
+
attr_accessor :channel
|
353
|
+
|
354
|
+
# @return [String] The user being kicked.
|
355
|
+
attr_accessor :kickee
|
356
|
+
|
357
|
+
# @return [String] The kick message, if any.
|
358
|
+
attr_accessor :message
|
359
|
+
|
360
|
+
# @private
|
361
|
+
def initialize(args)
|
362
|
+
super(args)
|
363
|
+
@kicker = args[:prefix]
|
364
|
+
@channel = args[:args][0]
|
365
|
+
@kickee = args[:args][1]
|
366
|
+
@message = args[:args][2]
|
367
|
+
@message = nil if @message && @message.empty?
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
class UserModeChange < Message
|
372
|
+
# @return [Array] The mode changes as returned by
|
373
|
+
# {IRCSupport::Modes#parse_modes}.
|
374
|
+
attr_accessor :mode_changes
|
375
|
+
|
376
|
+
# @private
|
377
|
+
def initialize(args)
|
378
|
+
super(args)
|
379
|
+
@mode_changes = IRCSupport::Modes.parse_modes(args[:args][0])
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
class ChannelModeChange < Message
|
384
|
+
# @return [String] The user or server doing the mode change(s).
|
385
|
+
attr_accessor :changer
|
386
|
+
|
387
|
+
# @return [String] The channel name.
|
388
|
+
attr_accessor :channel
|
389
|
+
|
390
|
+
# @return [Array] The mode changes as returned by
|
391
|
+
# {IRCSupport::Modes#parse_modes}.
|
392
|
+
attr_accessor :mode_changes
|
393
|
+
|
394
|
+
# @private
|
395
|
+
def initialize(args)
|
396
|
+
super(args)
|
397
|
+
@changer = args[:prefix]
|
398
|
+
@channel = args[:args][0]
|
399
|
+
@mode_changes = IRCSupport::Modes.parse_channel_modes(
|
400
|
+
args[:args].last(args[:args].size - 1),
|
401
|
+
chanmodes: args[:isupport]["CHANMODES"],
|
402
|
+
statmodes: args[:isupport]["PREFIX"].keys,
|
403
|
+
)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class Nick < Message
|
408
|
+
# @return [String] The user who is changing their nick.
|
409
|
+
attr_accessor :changer
|
410
|
+
|
411
|
+
# @return [String] The new nickname.
|
412
|
+
attr_accessor :nickname
|
413
|
+
|
414
|
+
# @private
|
415
|
+
def initialize(args)
|
416
|
+
super(args)
|
417
|
+
@changer = args[:prefix]
|
418
|
+
@nickname = args[:args][0]
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
class Topic < Message
|
423
|
+
# @return [String] The user or server which is changing the topic.
|
424
|
+
attr_accessor :changer
|
425
|
+
|
426
|
+
# @return [String] The name of the channel.
|
427
|
+
attr_accessor :channel
|
428
|
+
|
429
|
+
# @return [String] The new topic.
|
430
|
+
attr_accessor :topic
|
431
|
+
|
432
|
+
# @private
|
433
|
+
def initialize(args)
|
434
|
+
super(args)
|
435
|
+
@changer = args[:prefix]
|
436
|
+
@channel = args[:args][0]
|
437
|
+
@topic = args[:args][1]
|
438
|
+
@topic = nil if @topic && @topic.empty?
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
class Quit < Message
|
443
|
+
# @return [String] The user who is quitting.
|
444
|
+
attr_accessor :quitter
|
445
|
+
|
446
|
+
# @return [String] The quit message, if any.
|
447
|
+
attr_accessor :message
|
448
|
+
|
449
|
+
# @private
|
450
|
+
def initialize(args)
|
451
|
+
super(args)
|
452
|
+
@quitter = args[:prefix]
|
453
|
+
@message = args[:args][0]
|
454
|
+
@message = nil if @message && @message.empty?
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class Ping < Message
|
459
|
+
# @return [String] The ping message, if any.
|
460
|
+
attr_accessor :message
|
461
|
+
|
462
|
+
# @private
|
463
|
+
def initialize(args)
|
464
|
+
super(args)
|
465
|
+
@message = args[:args][0]
|
466
|
+
@message = nil if @message && @message.empty?
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
class CAP < Message
|
471
|
+
# @return [String] The CAP subcommand.
|
472
|
+
attr_accessor :subcommand
|
473
|
+
|
474
|
+
# @return [Boolean] Will be true if this is a multipart reply.
|
475
|
+
attr_accessor :multipart
|
476
|
+
|
477
|
+
# @return [String] The text of the CAP reply.
|
478
|
+
attr_accessor :reply
|
479
|
+
|
480
|
+
# @private
|
481
|
+
def initialize(args)
|
482
|
+
super(args)
|
483
|
+
@subcommand = args[:args][0]
|
484
|
+
@type = "cap_#{@subcommand.downcase}"
|
485
|
+
if args[:args][1] == '*'
|
486
|
+
@multipart = true
|
487
|
+
@reply = args[:args][2]
|
488
|
+
else
|
489
|
+
@multipart = false
|
490
|
+
@reply = args[:args][1]
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
class CAP::LS < CAP
|
496
|
+
# @return [Hash] The capabilities referenced in the CAP reply. The keys
|
497
|
+
# are the capability names, and the values are arrays of modifiers
|
498
|
+
# (`:enable`, `:disable`, `:sticky`).
|
499
|
+
attr_accessor :capabilities
|
500
|
+
|
501
|
+
# @private
|
502
|
+
@@modifiers = {
|
503
|
+
'-' => :disable,
|
504
|
+
'~' => :enable,
|
505
|
+
'=' => :sticky,
|
506
|
+
}
|
507
|
+
|
508
|
+
# @private
|
509
|
+
def initialize(args)
|
510
|
+
super(args)
|
511
|
+
@capabilities = {}
|
512
|
+
|
513
|
+
reply.split.each do |chunk|
|
514
|
+
mods, capability = chunk.match(/\A([-=~]*)(.*)/).captures
|
515
|
+
modifiers = []
|
516
|
+
mods.split('').each do |modifier|
|
517
|
+
modifiers << @@modifiers[modifier] if @@modifiers[modifier]
|
518
|
+
end
|
519
|
+
modifiers << :enable if mods.empty?
|
520
|
+
@capabilities[capability] = modifiers
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
class CAP::LIST < CAP::LS; end
|
526
|
+
class CAP::ACK < CAP::LS; end
|
527
|
+
|
528
|
+
class ServerNotice < Message
|
529
|
+
# @return [String] The sender of the notice. Could be a server name,
|
530
|
+
# a service, or nothing at all.
|
531
|
+
attr_accessor :sender
|
532
|
+
|
533
|
+
# @return [String] The target of the server notice. Could be '*' or
|
534
|
+
# 'AUTH' or something else entirely.
|
535
|
+
attr_accessor :target
|
536
|
+
|
537
|
+
# @return [String] The text of the notice.
|
538
|
+
attr_accessor :message
|
539
|
+
|
540
|
+
# @private
|
541
|
+
def initialize(args)
|
542
|
+
super(args)
|
543
|
+
@sender = args[:prefix]
|
544
|
+
if args[:args].size == 2
|
545
|
+
@target = args[:args][0]
|
546
|
+
@message = args[:args][1]
|
547
|
+
else
|
548
|
+
@message = args[:args][0]
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
class Message < Message
|
554
|
+
# @return [String] The user who sent the message.
|
555
|
+
attr_accessor :sender
|
556
|
+
|
557
|
+
# @return [String] The text of the message.
|
558
|
+
attr_accessor :message
|
559
|
+
|
560
|
+
# @return [String] The name of the channel this message was sent to,
|
561
|
+
# if any.
|
562
|
+
attr_accessor :channel
|
563
|
+
|
564
|
+
# @private
|
565
|
+
def initialize(args)
|
566
|
+
super(args)
|
567
|
+
@sender = args[:prefix]
|
568
|
+
@message = args[:args][1]
|
569
|
+
@is_action = args[:is_action] || false
|
570
|
+
@is_notice = args[:is_notice] || false
|
571
|
+
|
572
|
+
if args[:is_public]
|
573
|
+
# broadcast messages are so 90s
|
574
|
+
@channel = args[:args][0].split(/,/).first
|
575
|
+
end
|
576
|
+
|
577
|
+
if args[:capabilities].include?('identify-msg')
|
578
|
+
@identified = args[:identified]
|
579
|
+
def self.identified?; @identified; end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
# @return [Boolean] Will be true if this message is an action.
|
584
|
+
def is_action?; @is_action; end
|
585
|
+
|
586
|
+
# @return [Boolean] Will be true if this message is a notice.
|
587
|
+
def is_notice?; @is_notice; end
|
588
|
+
end
|
589
|
+
|
590
|
+
class CTCP < Message
|
591
|
+
# @return [String] The arguments to the CTCP.
|
592
|
+
attr_accessor :ctcp_args
|
593
|
+
|
594
|
+
# @private
|
595
|
+
def initialize(args)
|
596
|
+
super(args)
|
597
|
+
@sender = args[:prefix]
|
598
|
+
@ctcp_args = args[:args][1]
|
599
|
+
@ctcp_type = args[:ctcp_type]
|
600
|
+
@type = "ctcp_#{@ctcp_type.downcase}"
|
601
|
+
|
602
|
+
if args[:is_public]
|
603
|
+
@channel = args[:args][0].split(/,/).first
|
604
|
+
end
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
class CTCPReply < CTCP
|
609
|
+
# @private
|
610
|
+
def initialize(args)
|
611
|
+
super(args)
|
612
|
+
@type = "ctcpreply_#{@ctcp_type.downcase}"
|
613
|
+
end
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|