xget 2.1.5 → 3.0.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.
- checksums.yaml +5 -5
- data/xget.rb +757 -0
- metadata +11 -11
- data/bin/xget +0 -710
data/bin/xget
DELETED
|
@@ -1,710 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
# xget.rb - xget
|
|
4
|
-
# Created by Rusty Shackleford on 2013/05/19
|
|
5
|
-
# Copyright (c) 2013, Rusty Shackleford
|
|
6
|
-
# All rights reserved.
|
|
7
|
-
|
|
8
|
-
begin
|
|
9
|
-
%w(socket thread slop timeout).each { |r| require r }
|
|
10
|
-
rescue LoadError
|
|
11
|
-
abort "#{$0} Requires slop - Please run 'gem install slop'"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Why isn't this enabled by default?
|
|
15
|
-
Thread.abort_on_exception = true
|
|
16
|
-
# Put standard output into syncronised mode
|
|
17
|
-
$stdout.sync = true
|
|
18
|
-
|
|
19
|
-
# Version values
|
|
20
|
-
$ver_maj, $ver_min, $ver_rev = 2, 1, 1
|
|
21
|
-
$ver_str = "#{$ver_maj}.#{$ver_min}.#{$ver_rev}"
|
|
22
|
-
|
|
23
|
-
config = {
|
|
24
|
-
"out-dir" => './',
|
|
25
|
-
"skip-existing" => false,
|
|
26
|
-
"servers" => {},
|
|
27
|
-
"sleep-interval" => 5,
|
|
28
|
-
"allow-queueing" => false
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
def puts_error msg
|
|
32
|
-
puts "! \e[31mERROR\e[0m: #{msg}"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def puts_abort msg
|
|
36
|
-
abort "! \e[31mERROR\e[0m: #{msg}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def puts_warning msg
|
|
40
|
-
puts "! \e[33mWARNING:\e[0m: #{msg}"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Extend IO to readlines without blocking
|
|
44
|
-
class IO
|
|
45
|
-
def gets_nonblock
|
|
46
|
-
@rlnb_buffer ||= ""
|
|
47
|
-
ch = nil
|
|
48
|
-
while ch = self.read_nonblock(1)
|
|
49
|
-
@rlnb_buffer += ch
|
|
50
|
-
if ch == "\n" then
|
|
51
|
-
res = @rlnb_buffer
|
|
52
|
-
@rlnb_buffer = ""
|
|
53
|
-
return res
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Extend Array to get averages
|
|
60
|
-
class Array
|
|
61
|
-
def average
|
|
62
|
-
inject(:+) / count
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Class to hold XDCC requests
|
|
67
|
-
class XDCC_REQ
|
|
68
|
-
attr_accessor :serv, :chan, :bot, :pack, :info
|
|
69
|
-
|
|
70
|
-
def initialize serv, chan, bot, pack, info = "*"
|
|
71
|
-
@serv = serv
|
|
72
|
-
@chan = chan
|
|
73
|
-
@bot = bot
|
|
74
|
-
@pack = pack
|
|
75
|
-
@info = info
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def eql? other
|
|
79
|
-
self.serv == other.serv and self.chan == other.chan and self.bot == other.bot and self.pack == other.pack
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def to_s
|
|
83
|
-
"[ #{self.serv}, #{self.chan}, #{self.bot}, #{self.pack}, #{self.info} ]"
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Class to hold DCC SEND info for when waiting for DCC ACCEPT
|
|
88
|
-
class XDCC_SEND
|
|
89
|
-
attr_accessor :fname, :fsize, :ip, :port
|
|
90
|
-
|
|
91
|
-
def initialize fname, fsize, ip, port
|
|
92
|
-
@fname = fname
|
|
93
|
-
@fsize = fsize
|
|
94
|
-
@ip = ip
|
|
95
|
-
@port = port
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def to_s
|
|
99
|
-
"[ #{self.fname}, #{self.fsize}, #{self.ip}, #{self.port} ]"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Class to emit events
|
|
104
|
-
module Emitter
|
|
105
|
-
def callbacks
|
|
106
|
-
@callbacks ||= Hash.new { |h, k| h[k] = [] }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def on type, &block
|
|
110
|
-
callbacks[type] << block
|
|
111
|
-
self
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def emit type, *args
|
|
115
|
-
callbacks[type].each do |block|
|
|
116
|
-
block.call(*args)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Class to handle IRC stream and emit events
|
|
122
|
-
class Stream
|
|
123
|
-
include Emitter
|
|
124
|
-
attr_accessor :io, :buf
|
|
125
|
-
|
|
126
|
-
def initialize serv
|
|
127
|
-
@buf = []
|
|
128
|
-
Timeout.timeout(5) { @io = TCPSocket.new serv, 6667 }
|
|
129
|
-
rescue SocketError => e
|
|
130
|
-
puts_abort "Failed to connect to #{serv}! #{e.message}"
|
|
131
|
-
rescue Timeout::Error
|
|
132
|
-
puts_abort "Connection to #{serv} timed out!"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def disconnect
|
|
136
|
-
@io.puts 'QUIT'
|
|
137
|
-
rescue Errno::EPIPE
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def << data
|
|
141
|
-
@buf << data
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def write
|
|
145
|
-
@buf.each do |x|
|
|
146
|
-
@io.puts x
|
|
147
|
-
emit :WROTE, x
|
|
148
|
-
end
|
|
149
|
-
@buf = []
|
|
150
|
-
rescue EOFError, Errno::ECONNRESET
|
|
151
|
-
emit :CLOSED
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def read
|
|
155
|
-
read = @io.gets_nonblock
|
|
156
|
-
emit :READ, read
|
|
157
|
-
rescue IO::WaitReadable
|
|
158
|
-
emit :WAITING
|
|
159
|
-
rescue EOFError, Errno::ECONNRESET
|
|
160
|
-
emit :CLOSED
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Class to handle IRC stream
|
|
165
|
-
class Bot
|
|
166
|
-
attr_reader :stream
|
|
167
|
-
|
|
168
|
-
def initialize stream
|
|
169
|
-
@stream = stream
|
|
170
|
-
stream.on :CLOSED do stop; end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def start
|
|
174
|
-
@running = true
|
|
175
|
-
tick while @running
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def stop
|
|
179
|
-
@running = false
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def tick
|
|
183
|
-
stream.read
|
|
184
|
-
stream.write
|
|
185
|
-
sleep 0.001
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
# Get relative size from bytes
|
|
190
|
-
def bytes_to_closest bytes
|
|
191
|
-
fsize_arr = [ 'B', 'KB', 'MB', 'GB', 'TB' ]
|
|
192
|
-
exp = (Math.log(bytes) / Math.log(1024)).to_i
|
|
193
|
-
exp = fsize_arr.length if exp > fsize_arr.length
|
|
194
|
-
bytes /= 1024.0 ** exp
|
|
195
|
-
return "#{bytes.round(2)}#{fsize_arr[exp]}"
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Loop until there is no file with the same name
|
|
199
|
-
def safe_fname fname
|
|
200
|
-
return fname unless File.exists? fname
|
|
201
|
-
|
|
202
|
-
ext = File.extname fname
|
|
203
|
-
base = File.basename fname, ext
|
|
204
|
-
dir = File.dirname fname
|
|
205
|
-
|
|
206
|
-
cur = 2
|
|
207
|
-
while true
|
|
208
|
-
test = "#{dir}/#{base} (#{cur})#{ext}"
|
|
209
|
-
return test unless File.exists? test
|
|
210
|
-
cur += 1
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Get a close relative time remaining, in words
|
|
215
|
-
def time_distance t
|
|
216
|
-
if t < 60
|
|
217
|
-
case t
|
|
218
|
-
when 0 then "- nevermind, done!"
|
|
219
|
-
when 1..4 then "in a moment!"
|
|
220
|
-
when 5..9 then "less than 10 seconds"
|
|
221
|
-
when 10..19 then "less than 20 seconds"
|
|
222
|
-
when 20..39 then "half a minute"
|
|
223
|
-
else "less than a minute"
|
|
224
|
-
end
|
|
225
|
-
else # Use minutes, to aovid big numbers
|
|
226
|
-
t = t / 60.0
|
|
227
|
-
case t.to_i
|
|
228
|
-
when 1 then "about a minute"
|
|
229
|
-
when 2..45 then "#{t.round} minutes"
|
|
230
|
-
when 45..90 then "about an hour"
|
|
231
|
-
when 91..1440 then "about #{(t / 60.0).round} hours"
|
|
232
|
-
when 1441..2520 then "about a day"
|
|
233
|
-
when 2521..86400 then "about #{(t / 1440.0).round} days"
|
|
234
|
-
else "about #{(t/ 43200.0).round} months"
|
|
235
|
-
end
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Get elapsed time in words
|
|
240
|
-
def time_elapsed t
|
|
241
|
-
return "instantly!" if t <= 0
|
|
242
|
-
|
|
243
|
-
# Get the GMTime from seconds and split
|
|
244
|
-
ta = Time.at(t).gmtime.strftime('%S|%M|%H|%-d|%-m|%Y').split('|', 6).collect { |i| i.to_i }
|
|
245
|
-
ta[-1] -= 1970 # fuck the police
|
|
246
|
-
ta[-2] -= 1 # fuck, fuck
|
|
247
|
-
ta[-3] -= 1 # fuck the police
|
|
248
|
-
|
|
249
|
-
# Remove the 0 digets
|
|
250
|
-
i = 0
|
|
251
|
-
ta.reverse.each do |x|
|
|
252
|
-
break if x != 0
|
|
253
|
-
i += 1
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Unit suffixes
|
|
257
|
-
suffix = [ "seconds", "minutes", "hours", "days", "months", "years" ]
|
|
258
|
-
# Don't use plural if x is 1
|
|
259
|
-
plural = ->(x, y) { x == 1 ? y[0..-2] : y }
|
|
260
|
-
# Format string to "value unit"
|
|
261
|
-
format_str = ->(x) { "#{ta[x]} #{plural[ta[x], suffix[x]]}, " }
|
|
262
|
-
|
|
263
|
-
# Form the string
|
|
264
|
-
ta = ta.take(ta.length - i)
|
|
265
|
-
str = ""
|
|
266
|
-
(ta.length - 1).downto(0) { |x| str += format_str[x] }
|
|
267
|
-
"in #{str[0..-3]}"
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# DCC download handler
|
|
271
|
-
def dcc_download ip, port, fname, fsize, read = 0
|
|
272
|
-
sock = nil
|
|
273
|
-
begin
|
|
274
|
-
Timeout.timeout(5) { sock = TCPSocket.new ip, port }
|
|
275
|
-
rescue Timeout::Error
|
|
276
|
-
puts_abort "Connection to #{ip} timed out!"
|
|
277
|
-
end
|
|
278
|
-
puts_abort "Failed to connect to \"#{ip}:#{port}\": #{e}" if sock.nil?
|
|
279
|
-
|
|
280
|
-
fsize_clean = bytes_to_closest fsize
|
|
281
|
-
avgs, last_check, start_time = [], Time.now - 2, Time.now
|
|
282
|
-
fh = File.open fname, (read == 0 ? "w" : "a") # Write or append
|
|
283
|
-
|
|
284
|
-
# Form the status bar
|
|
285
|
-
print_bar = ->() {
|
|
286
|
-
print "\r\e[0K> [ \e[1;37m"
|
|
287
|
-
pc = read.to_f / fsize.to_f * 100.0
|
|
288
|
-
bars = (pc / 10).to_i
|
|
289
|
-
bars.times { print "#" }
|
|
290
|
-
(10 - bars).times { print " " }
|
|
291
|
-
avg = avgs.average * 1024.0
|
|
292
|
-
time_rem = time_distance ((fsize - read) / avg) * 8.0
|
|
293
|
-
print "\e[0m ] #{pc.round(2)}% #{bytes_to_closest read}/#{fsize_clean} \e[1;37m@\e[0m #{bytes_to_closest avg}/s \e[1;37min\e[0m #{time_rem}"
|
|
294
|
-
|
|
295
|
-
last_check = Time.now
|
|
296
|
-
avgs.clear
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
while buf = sock.readpartial(8192)
|
|
300
|
-
read += buf.bytesize
|
|
301
|
-
avgs << buf.bytesize
|
|
302
|
-
print_bar[] if (Time.now - last_check) > 1 and not avgs.empty?
|
|
303
|
-
|
|
304
|
-
begin
|
|
305
|
-
sock.write_nonblock [read].pack('N')
|
|
306
|
-
rescue Errno::EWOULDBLOCK
|
|
307
|
-
rescue Errno::EAGAIN => e
|
|
308
|
-
puts_error "#{File.basename fname} timed out! #{e}"
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
fh << buf
|
|
312
|
-
break if read >= fsize
|
|
313
|
-
end
|
|
314
|
-
print_bar.call unless avgs.empty?
|
|
315
|
-
elapsed_time = time_elapsed (Time.now - start_time).to_i
|
|
316
|
-
|
|
317
|
-
sock.close
|
|
318
|
-
fh.close
|
|
319
|
-
|
|
320
|
-
puts "\n! \e[1;32mSUCCESS\e[0m: downloaded #{File.basename fname} #{elapsed_time}"
|
|
321
|
-
rescue EOFError, SocketError => e
|
|
322
|
-
puts "\n! ERROR: #{File.basename fname} failed to download! #{e}"
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
opts = Slop.parse do |o|
|
|
326
|
-
o.banner = " Usage: #{$0} [options] [value] [links] [--files] [file1:file2:file3]\n"
|
|
327
|
-
o.bool '-h', '--help', 'Prints help'
|
|
328
|
-
|
|
329
|
-
o.on '-v', '--version', 'Print version' do
|
|
330
|
-
puts "#{$0}: v#{$ver_str}"
|
|
331
|
-
exit
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
o.string '--config', 'Config file location'
|
|
335
|
-
o.string '--user', 'IRC \'USER\' for Ident'
|
|
336
|
-
o.string '--nick', 'IRC nick'
|
|
337
|
-
o.string '--pass', 'IRC \'PASS\' for Ident'
|
|
338
|
-
o.string '--realname', 'Realname for \'USER\' Ident'
|
|
339
|
-
o.string '--nickserv', 'Password for Nickserv'
|
|
340
|
-
o.array '--files', 'Pass list of files to parse for links', as: Array, delimiter: ':'
|
|
341
|
-
o.string '--out-dir', 'Output directory to save fiels to', :default => "./"
|
|
342
|
-
o.bool '--skip-existing', 'Don\' download files that already exist'
|
|
343
|
-
o.bool '--allow-queueing', 'Wait for pack to start downloading rather than fail immediately when queued'
|
|
344
|
-
o.int '--sleep-interval', 'Time in seconds to sleep before requesting next pack. Zero for no sleep.'
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
if opts.help?
|
|
348
|
-
puts opts
|
|
349
|
-
puts "\n Examples"
|
|
350
|
-
puts " \txget.rb --config config.conf --nick test"
|
|
351
|
-
puts " \txget.rb --files test1.txt:test2.txt:test3.txt"
|
|
352
|
-
puts " \txget.rb #news@irc.rizon.net/ginpachi-sensei/1"
|
|
353
|
-
puts " \txget.rb #news@irc.rizon.net/ginpachi-sensei/41..46"
|
|
354
|
-
puts " \txget.rb #news@irc.rizon.net/ginpachi-sensei/41..46|2"
|
|
355
|
-
puts " \txget.rb #news@irc.rizon.net/ginpachi-sensei/41..46&49..52|2&30"
|
|
356
|
-
exit
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
# Get the config location
|
|
360
|
-
config_loc = opts["config"]
|
|
361
|
-
config_loc = File.expand_path config_loc unless config_loc.nil?
|
|
362
|
-
if config_loc.nil? or not File.exists? config_loc
|
|
363
|
-
config_loc = File.expand_path "~/.xget.conf"
|
|
364
|
-
config_loc = ".xget.conf" unless File.exists? config_loc
|
|
365
|
-
|
|
366
|
-
unless File.exists? config_loc
|
|
367
|
-
puts "ERROR! Invalid config path '#{config_loc}''. Exiting!"
|
|
368
|
-
exit
|
|
369
|
-
end
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
# Insert config settings from arguments into config hash
|
|
373
|
-
cur_block = "*"
|
|
374
|
-
config["servers"][cur_block] = {}
|
|
375
|
-
%w(user nick pass real nserv).each do |x|
|
|
376
|
-
config["servers"][cur_block][x.to_sym] = opts[x] unless opts[x].nil?
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
# Check if specified output directory actually exists
|
|
380
|
-
puts_abort "Out directory, \"#{opts["out-dir"]}\" doesn't exist!" unless Dir.exists? opts["out-dir"]
|
|
381
|
-
config["out-dir"] = opts["out-dir"].dup
|
|
382
|
-
config["out-dir"] << "/" unless config["out-dir"][-1] == "/"
|
|
383
|
-
|
|
384
|
-
# Parse config
|
|
385
|
-
config_copies = {}
|
|
386
|
-
File.open(config_loc, "r").each_line do |line|
|
|
387
|
-
next if line.length <= 1 or line[0] == '#'
|
|
388
|
-
|
|
389
|
-
if line =~ /^\[(\S+)\]$/ # Check if header
|
|
390
|
-
cur_block = $1
|
|
391
|
-
if cur_block.include? ',' # Check if header contains more than one server
|
|
392
|
-
tmp_split = cur_block.split(",")
|
|
393
|
-
next unless tmp_split[0] =~ /^(\w+?).(\w+?).(\w+?)$/
|
|
394
|
-
config_copies[tmp_split[0]] = []
|
|
395
|
-
tmp_split.each do |x| # Add all copies to copies hash
|
|
396
|
-
next if x == tmp_split[0] or not x =~ /^(\w+?).(\w+?).(\w+?)$/
|
|
397
|
-
config_copies[tmp_split[0]].push x unless config_copies[tmp_split[0]].include? x
|
|
398
|
-
end
|
|
399
|
-
cur_block = tmp_split[0]
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
# Set current block to the new header
|
|
403
|
-
config["servers"][cur_block] = {} unless config["servers"].has_key? cur_block
|
|
404
|
-
elsif line =~ /^(\S+)=(.*+?)$/
|
|
405
|
-
# Check if current line is specifying out directory
|
|
406
|
-
case $1
|
|
407
|
-
when "out-dir"
|
|
408
|
-
t_out_dir = File.expand_path $2
|
|
409
|
-
puts_abort "Out directory, \"#{t_out_dir}\" doesn't exist!" unless Dir.exists? t_out_dir
|
|
410
|
-
config[$1] = t_out_dir
|
|
411
|
-
config[$1] << "/" unless config[$1][-1] == "/"
|
|
412
|
-
next
|
|
413
|
-
when "sleep-interval" then config[$1] = $2.to_i
|
|
414
|
-
when "skip-existing" then config[$1] = ($2 == "true")
|
|
415
|
-
when "allow-queueing" then config[$1] = ($2 == "true")
|
|
416
|
-
else
|
|
417
|
-
# Add value to current header, default is *
|
|
418
|
-
t_sym = $1.downcase.to_sym
|
|
419
|
-
config["servers"][cur_block][t_sym] = $2 unless config["servers"][cur_block].has_key? t_sym
|
|
420
|
-
end
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
# Go through each and make copies of the original
|
|
425
|
-
unless config_copies.empty?
|
|
426
|
-
config_copies.each do |k,v|
|
|
427
|
-
v.each { |x| config["servers"][x] = config["servers"][k] }
|
|
428
|
-
end
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
# Set the set the command line config options if specified
|
|
432
|
-
config["skip-existing"] = opts["skip-existing"] if opts["skip-existing"]
|
|
433
|
-
config["allow-queueing"] = opts["allow-queueing"] if opts["allow-queueing"]
|
|
434
|
-
config["sleep-interval"] = opts["sleep-interval"] unless opts["sleep-interval"].nil?
|
|
435
|
-
|
|
436
|
-
# Take remaining arguments and all lines from --files arg and put into array
|
|
437
|
-
to_check = opts.arguments
|
|
438
|
-
if opts['files'] != nil and not opts['files'].empty?
|
|
439
|
-
opts['files'].each do |x|
|
|
440
|
-
File.open(x, "r").each_line { |y| to_check << y.chomp } if File.exists? x
|
|
441
|
-
end
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
if to_check.empty?
|
|
445
|
-
puts opts
|
|
446
|
-
abort "\n No jobs, nothing to do!"
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
# Parse to_check array for valid XDCC links, irc.serv.org/#chan/bot/pack
|
|
450
|
-
tmp_requests = []
|
|
451
|
-
to_check.each do |x|
|
|
452
|
-
if x =~ /^(#\S+)@(irc.\w+.\w+{2,3})\/(\S+)\/([\.&\|\d]+)$/
|
|
453
|
-
chan = $1
|
|
454
|
-
serv = $2
|
|
455
|
-
bot = $3
|
|
456
|
-
info = config["servers"].has_key?(serv) ? serv : "*"
|
|
457
|
-
$4.split('&').each do |y|
|
|
458
|
-
if y =~ /^(\d+)(\.\.\d+(\|\d+)?)?$/
|
|
459
|
-
pack = $1.to_i
|
|
460
|
-
if $2.nil?
|
|
461
|
-
tmp_requests.push XDCC_REQ.new serv, chan, bot, pack, info
|
|
462
|
-
else
|
|
463
|
-
step = $3.nil? ? 1 : $3[1..-1].to_i
|
|
464
|
-
range = $2[2..-1].to_i
|
|
465
|
-
|
|
466
|
-
puts_abort "Invalid range #{pack} to #{range} in \"#{x}\"" if pack > range or pack == range
|
|
467
|
-
|
|
468
|
-
(pack..range).step(step).each do |z|
|
|
469
|
-
tmp_requests.push XDCC_REQ.new serv, chan, bot, z, info
|
|
470
|
-
end
|
|
471
|
-
end
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
else
|
|
475
|
-
puts_abort "#{x} is not a valid XDCC address\n XDCC Address format: #chan@irc.serv.com/bot/pack(s) or ^\/msg irc.serv.com bot xdcc send #id$"
|
|
476
|
-
end
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
# Remove duplicate entries from requests
|
|
480
|
-
i = j = 0
|
|
481
|
-
to_pop = []
|
|
482
|
-
tmp_requests.each do |x|
|
|
483
|
-
tmp_requests.each do |y|
|
|
484
|
-
to_pop << j if x.eql? y if i != j
|
|
485
|
-
j += 1
|
|
486
|
-
end
|
|
487
|
-
i += 1
|
|
488
|
-
end
|
|
489
|
-
to_pop.each { |x| tmp_requests.delete_at(x) }
|
|
490
|
-
|
|
491
|
-
# Sort requests array to hash, serv {} -> chan {} -> requests []
|
|
492
|
-
requests = {}
|
|
493
|
-
tmp_requests.each do |x|
|
|
494
|
-
requests[x.serv] = [] unless requests.has_key? x.serv
|
|
495
|
-
requests[x.serv] << x
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
if requests.empty?
|
|
499
|
-
puts opts
|
|
500
|
-
abort "\n No jobs, nothing to do!"
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
# Sort requests by pack
|
|
504
|
-
requests.each do |k,v|
|
|
505
|
-
puts "#{k} \e[1;37m->\e[0m"
|
|
506
|
-
v.sort_by { |x| [x.chan, x.bot, x.pack] }.each { |x| puts " #{x}" }
|
|
507
|
-
end
|
|
508
|
-
puts
|
|
509
|
-
|
|
510
|
-
# H-h-here we g-go...
|
|
511
|
-
requests.each do |k, v|
|
|
512
|
-
req, info = v[0], config["servers"][v[0].info]
|
|
513
|
-
last_chan, cur_req, motd = "", -1, false
|
|
514
|
-
nick_sent, nick_check, nick_valid = false, false, false
|
|
515
|
-
xdcc_sent, xdcc_accepted, xdcc_queued = false, false, false
|
|
516
|
-
xdcc_accept_time, xdcc_ret, req_send_time = nil, nil, nil
|
|
517
|
-
|
|
518
|
-
stream = Stream.new req.serv
|
|
519
|
-
bot = Bot.new stream
|
|
520
|
-
stream << "NICK #{info[:nick]}"
|
|
521
|
-
stream << "USER #{info[:user]} 0 * #{info[:real]}"
|
|
522
|
-
stream << "PASS #{info[:pass]}" unless info[:pass].nil?
|
|
523
|
-
|
|
524
|
-
# Handle read data
|
|
525
|
-
stream.on :READ do |data|
|
|
526
|
-
/^(?:[:](?<prefix>\S+) )?(?<type>\S+)(?: (?!:)(?<dest>.+?))?(?: [:](?<msg>.+))?$/ =~ data
|
|
527
|
-
#puts "\e[1;37m>>\e[0m #{prefix} | #{type} | #{dest} | #{msg}"
|
|
528
|
-
|
|
529
|
-
case type
|
|
530
|
-
when 'NOTICE'
|
|
531
|
-
if dest == 'AUTH'
|
|
532
|
-
if msg =~ /erroneous nickname/i
|
|
533
|
-
puts_error 'Login failed'
|
|
534
|
-
stream.disconnect
|
|
535
|
-
end
|
|
536
|
-
#puts "> \e[1;32m#{msg}\e[0m"
|
|
537
|
-
else
|
|
538
|
-
if prefix =~ /^NickServ!/
|
|
539
|
-
if not nick_sent and info[:nserv] != nil
|
|
540
|
-
stream << "PRIVMSG NickServ :IDENTIFY #{info[:nserv]}"
|
|
541
|
-
nick_sent = true
|
|
542
|
-
elsif nick_sent and not nick_check
|
|
543
|
-
case msg
|
|
544
|
-
when /password incorrect/i
|
|
545
|
-
nick_valid = false
|
|
546
|
-
nick_check = true
|
|
547
|
-
when /password accepted/i
|
|
548
|
-
nick_valid = true
|
|
549
|
-
nick_check = true
|
|
550
|
-
end
|
|
551
|
-
end
|
|
552
|
-
#puts "> \e[1;33m#{msg}\e[0m"
|
|
553
|
-
elsif prefix =~ /^#{Regexp.escape req.bot}!(.*)$/i
|
|
554
|
-
case msg
|
|
555
|
-
when /already requested that pack/i, /closing connection/i, /you have a dcc pending/i
|
|
556
|
-
puts_error msg
|
|
557
|
-
stream << "PRIVMSG #{req.bot} :XDCC CANCEL"
|
|
558
|
-
stream << 'QUIT'
|
|
559
|
-
when /you can only have (\d+?) transfer at a time/i
|
|
560
|
-
if config["allow-queueing"]
|
|
561
|
-
puts "! #{prefix}: #{msg}"
|
|
562
|
-
puts_warning "Pack queued, waiting for transfer to start..."
|
|
563
|
-
xdcc_queued = true
|
|
564
|
-
else
|
|
565
|
-
puts_error msg
|
|
566
|
-
stream << "PRIVMSG #{req.bot} :XDCC CANCEL"
|
|
567
|
-
stream << 'QUIT'
|
|
568
|
-
end
|
|
569
|
-
else
|
|
570
|
-
puts "! #{prefix}: #{msg}"
|
|
571
|
-
end
|
|
572
|
-
end
|
|
573
|
-
end
|
|
574
|
-
when 'PRIVMSG'
|
|
575
|
-
if xdcc_sent and not xdcc_accepted and prefix =~ /#{Regexp.escape req.bot}!(.*)$/i
|
|
576
|
-
/^\001DCC SEND (?<fname>((".*?").*?|(\S+))) (?<ip>\d+) (?<port>\d+) (?<fsize>\d+)\001\015$/ =~ msg
|
|
577
|
-
unless $~.nil?
|
|
578
|
-
req_send_time = nil
|
|
579
|
-
|
|
580
|
-
tmp_fname = fname
|
|
581
|
-
fname = $1 if tmp_fname =~ /^"(.*)"$/
|
|
582
|
-
puts "Preparing to download: \e[36m#{fname}\e[0m"
|
|
583
|
-
fname = (config["out-dir"].dup << fname)
|
|
584
|
-
xdcc_ret = XDCC_SEND.new fname, fsize.to_i, [ip.to_i].pack('N').unpack('C4') * '.', port.to_i
|
|
585
|
-
|
|
586
|
-
# Check if the for unfinished download amd try to resume
|
|
587
|
-
if File.exists? xdcc_ret.fname and File.stat(xdcc_ret.fname).size < xdcc_ret.fsize
|
|
588
|
-
stream << "PRIVMSG #{req.bot} :\001DCC RESUME #{tmp_fname} #{xdcc_ret.port} #{File.stat(xdcc_ret.fname).size}\001"
|
|
589
|
-
xdcc_accepted = true
|
|
590
|
-
print "! Incomplete file detected. Attempting to resume..."
|
|
591
|
-
next # Skip and wait for "DCC ACCEPT"
|
|
592
|
-
elsif File.exists? xdcc_ret.fname
|
|
593
|
-
if config["skip-existing"]
|
|
594
|
-
puts_warning "File already exists, skipping..."
|
|
595
|
-
stream << "PRIVMSG #{req.bot} :XDCC CANCEL"
|
|
596
|
-
|
|
597
|
-
xdcc_sent, xdcc_accepted, xdcc_queued = false, false, false
|
|
598
|
-
xdcc_accept_time, xdcc_ret = nil, nil
|
|
599
|
-
next
|
|
600
|
-
else
|
|
601
|
-
puts_warnings "File already existing, using a safe name..."
|
|
602
|
-
xdcc_ret.fname = safe_fname xdcc_ret.fname
|
|
603
|
-
end
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
# It's a new download, start from beginning
|
|
607
|
-
Thread.new do
|
|
608
|
-
pid = fork do
|
|
609
|
-
puts "Connecting to: #{req.bot} @ #{xdcc_ret.ip}:#{xdcc_ret.port}"
|
|
610
|
-
dcc_download xdcc_ret.ip, xdcc_ret.port, xdcc_ret.fname, xdcc_ret.fsize
|
|
611
|
-
end
|
|
612
|
-
|
|
613
|
-
Process.wait pid
|
|
614
|
-
xdcc_sent, xdcc_accepted, xdcc_queued = false, false, false
|
|
615
|
-
xdcc_accept_time, xdcc_ret = nil, nil
|
|
616
|
-
end
|
|
617
|
-
end
|
|
618
|
-
elsif xdcc_accepted and xdcc_ret != nil and msg =~ /^\001DCC ACCEPT ((".*?").*?|(\S+)) (\d+) (\d+)\001\015$/
|
|
619
|
-
# DCC RESUME request accepted, continue the download!
|
|
620
|
-
xdcc_accept_time = nil
|
|
621
|
-
xdcc_accepted = false
|
|
622
|
-
puts "\e[1;32mSUCCESS\e[0m!"
|
|
623
|
-
|
|
624
|
-
Thread.new do
|
|
625
|
-
pid = fork do
|
|
626
|
-
puts "Connecting to: #{req.bot} @ #{xdcc_ret.ip}:#{xdcc_ret.port}"
|
|
627
|
-
dcc_download xdcc_ret.ip, xdcc_ret.port, xdcc_ret.fname, xdcc_ret.fsize, File.stat(xdcc_ret.fname).size
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
Process.wait pid
|
|
631
|
-
xdcc_sent, xdcc_accepted, xdcc_queued = false, false, false
|
|
632
|
-
xdcc_accept_time, xdcc_ret = nil, nil
|
|
633
|
-
end
|
|
634
|
-
end
|
|
635
|
-
when /^\d+?$/
|
|
636
|
-
type_i = type.to_i
|
|
637
|
-
case type_i
|
|
638
|
-
# when 1 # Print welcome message, because it's nice
|
|
639
|
-
# msg.sub!(/#{Regexp.escape info[:nick]}/, "\e[34m#{info[:nick]}\e[0m")
|
|
640
|
-
# puts "! #{msg}"
|
|
641
|
-
when 400..533 # Handle errors, except a few
|
|
642
|
-
next if [439, 462, 477].include? type_i
|
|
643
|
-
puts_error "#{msg}"
|
|
644
|
-
stream.disconnect
|
|
645
|
-
when 376 then motd = true # Mark the end of the MOTD
|
|
646
|
-
end
|
|
647
|
-
when 'PING' then stream << "PONG :#{msg}"
|
|
648
|
-
when 'ERROR' then (msg =~ /closing link/i ? puts(msg) : puts_error(msg))
|
|
649
|
-
end
|
|
650
|
-
end
|
|
651
|
-
|
|
652
|
-
# Handle things while waiting for data
|
|
653
|
-
stream.on :WAITING do
|
|
654
|
-
unless xdcc_accepted
|
|
655
|
-
if motd and not xdcc_sent
|
|
656
|
-
cur_req += 1
|
|
657
|
-
if cur_req >= v.length
|
|
658
|
-
stream.disconnect
|
|
659
|
-
next
|
|
660
|
-
end
|
|
661
|
-
req = v[cur_req]
|
|
662
|
-
|
|
663
|
-
if req.chan != last_chan
|
|
664
|
-
stream << "PART #{last_chan}" unless last_chan == ""
|
|
665
|
-
last_chan = req.chan
|
|
666
|
-
stream << "JOIN #{req.chan}"
|
|
667
|
-
end
|
|
668
|
-
|
|
669
|
-
# Cooldown between downloads
|
|
670
|
-
if cur_req > 0
|
|
671
|
-
puts "Sleeping for #{config["sleep-interval"]} seconds before requesting the next pack"
|
|
672
|
-
sleep(config["sleep-interval"])
|
|
673
|
-
end
|
|
674
|
-
|
|
675
|
-
stream << "PRIVMSG #{req.bot} :XDCC SEND #{req.pack}"
|
|
676
|
-
req_send_time = Time.now
|
|
677
|
-
xdcc_sent = true
|
|
678
|
-
end
|
|
679
|
-
|
|
680
|
-
# Wait 3 seconds for DCC SEND response, if there isn't one, abort
|
|
681
|
-
if xdcc_sent and not req_send_time.nil? and not xdcc_accepted
|
|
682
|
-
if config["allow-queueing"] and xdcc_queued
|
|
683
|
-
next
|
|
684
|
-
end
|
|
685
|
-
if (Time.now - req_send_time).floor > 10
|
|
686
|
-
puts_error "#{req.bot} took too long to respond, are you sure it's a bot?"
|
|
687
|
-
stream.disconnect
|
|
688
|
-
bot.stop
|
|
689
|
-
end
|
|
690
|
-
end
|
|
691
|
-
|
|
692
|
-
# Wait 3 seconds for a DCC ACCEPT response, if there isn't one, don't resume
|
|
693
|
-
if xdcc_sent and xdcc_accepted and not xdcc_accept_time.nil?
|
|
694
|
-
if (Time.now - xdcc_accept_time).floor > 10
|
|
695
|
-
puts "FAILED! Bot client doesn't support resume!"
|
|
696
|
-
puts "Connecting to: #{req.bot} @ #{xdcc_ret.ip}:#{xdcc_ret.port}"
|
|
697
|
-
dcc_download xdcc_ret.ip, xdcc_ret.port, xdcc_ret.fname, xdcc_ret.fsize
|
|
698
|
-
end
|
|
699
|
-
end
|
|
700
|
-
end
|
|
701
|
-
end
|
|
702
|
-
|
|
703
|
-
# Print sent data, for debugging only really
|
|
704
|
-
stream.on :WROTE do |data|
|
|
705
|
-
#puts "\e[1;37m<<\e[0m #{data}"
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
# Start the bot
|
|
709
|
-
bot.start
|
|
710
|
-
end
|