xget 1.0.2 → 2.1.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.
- checksums.yaml +4 -4
- data/bin/xget +61 -62
- data/xget.rb +711 -0
- metadata +7 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d53971aeea75422201037489b8cba4f9842b8a6e
|
|
4
|
+
data.tar.gz: 2424da3f8132b7f3986c0c8b9dc3518450c01949
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39d5e569ad048bc81adc6c5629c94da2cce2ad23c00be5dacfc0aad70161a11be90431f8ca5db5f4b3c6fa288db4c0c2220d1ec66651cf850bbf1121a65ac4d0
|
|
7
|
+
data.tar.gz: dcacfcf3d877d04c7090b7ba8844df6ca9fc6ccf622260afde11225ebf0dc02a4b25a8ad42f4014e6e6f6f6e0e22c0514e8c6957b40ec7fee06cca2b2131731c
|
data/bin/xget
CHANGED
|
@@ -18,7 +18,7 @@ Thread.abort_on_exception = true
|
|
|
18
18
|
$stdout.sync = true
|
|
19
19
|
|
|
20
20
|
# Version values
|
|
21
|
-
$ver_maj, $ver_min, $ver_rev = 2, 1,
|
|
21
|
+
$ver_maj, $ver_min, $ver_rev = 2, 1, 1
|
|
22
22
|
$ver_str = "#{$ver_maj}.#{$ver_min}.#{$ver_rev}"
|
|
23
23
|
|
|
24
24
|
config = {
|
|
@@ -92,8 +92,8 @@ class XDCC_SEND
|
|
|
92
92
|
def initialize fname, fsize, ip, port
|
|
93
93
|
@fname = fname
|
|
94
94
|
@fsize = fsize
|
|
95
|
-
@ip
|
|
96
|
-
@port
|
|
95
|
+
@ip = ip
|
|
96
|
+
@port = port
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def to_s
|
|
@@ -126,7 +126,7 @@ class Stream
|
|
|
126
126
|
|
|
127
127
|
def initialize serv
|
|
128
128
|
@buf = []
|
|
129
|
-
timeout(5) { @io = TCPSocket.new serv, 6667 }
|
|
129
|
+
Timeout.timeout(5) { @io = TCPSocket.new serv, 6667 }
|
|
130
130
|
rescue SocketError => e
|
|
131
131
|
puts_abort "Failed to connect to #{serv}! #{e.message}"
|
|
132
132
|
rescue Timeout::Error
|
|
@@ -214,9 +214,9 @@ end
|
|
|
214
214
|
def time_distance t
|
|
215
215
|
if t < 60
|
|
216
216
|
case t
|
|
217
|
-
when 0
|
|
218
|
-
when 1..4
|
|
219
|
-
when 5..9
|
|
217
|
+
when 0 then "- nevermind, done!"
|
|
218
|
+
when 1..4 then "in a moment!"
|
|
219
|
+
when 5..9 then "less than 10 seconds"
|
|
220
220
|
when 10..19 then "less than 20 seconds"
|
|
221
221
|
when 20..39 then "half a minute"
|
|
222
222
|
else "less than a minute"
|
|
@@ -224,11 +224,11 @@ def time_distance t
|
|
|
224
224
|
else # Use minutes, to aovid big numbers
|
|
225
225
|
t = t / 60.0
|
|
226
226
|
case t.to_i
|
|
227
|
-
when 1
|
|
228
|
-
when 2..45
|
|
229
|
-
when 45..90
|
|
230
|
-
when 91..1440
|
|
231
|
-
when 1441..2520
|
|
227
|
+
when 1 then "about a minute"
|
|
228
|
+
when 2..45 then "#{t.round} minutes"
|
|
229
|
+
when 45..90 then "about an hour"
|
|
230
|
+
when 91..1440 then "about #{(t / 60.0).round} hours"
|
|
231
|
+
when 1441..2520 then "about a day"
|
|
232
232
|
when 2521..86400 then "about #{(t / 1440.0).round} days"
|
|
233
233
|
else "about #{(t/ 43200.0).round} months"
|
|
234
234
|
end
|
|
@@ -253,9 +253,9 @@ def time_elapsed t
|
|
|
253
253
|
end
|
|
254
254
|
|
|
255
255
|
# Unit suffixes
|
|
256
|
-
suffix
|
|
256
|
+
suffix = [ "seconds", "minutes", "hours", "days", "months", "years" ]
|
|
257
257
|
# Don't use plural if x is 1
|
|
258
|
-
plural
|
|
258
|
+
plural = ->(x, y) { x == 1 ? y[0..-2] : y }
|
|
259
259
|
# Format string to "value unit"
|
|
260
260
|
format_str = ->(x) { "#{ta[x]} #{plural[ta[x], suffix[x]]}, " }
|
|
261
261
|
|
|
@@ -270,7 +270,7 @@ end
|
|
|
270
270
|
def dcc_download ip, port, fname, fsize, read = 0
|
|
271
271
|
sock = nil
|
|
272
272
|
begin
|
|
273
|
-
timeout(5) { sock = TCPSocket.new ip, port }
|
|
273
|
+
Timeout.timeout(5) { sock = TCPSocket.new ip, port }
|
|
274
274
|
rescue Timeout::Error
|
|
275
275
|
puts_abort "Connection to #{ip} timed out!"
|
|
276
276
|
end
|
|
@@ -283,7 +283,7 @@ def dcc_download ip, port, fname, fsize, read = 0
|
|
|
283
283
|
# Form the status bar
|
|
284
284
|
print_bar = ->() {
|
|
285
285
|
print "\r\e[0K> [ \e[1;37m"
|
|
286
|
-
pc
|
|
286
|
+
pc = read.to_f / fsize.to_f * 100.0
|
|
287
287
|
bars = (pc / 10).to_i
|
|
288
288
|
bars.times { print "#" }
|
|
289
289
|
(10 - bars).times { print " " }
|
|
@@ -349,8 +349,10 @@ if __FILE__ == $0 then
|
|
|
349
349
|
puts "\n Examples"
|
|
350
350
|
puts " \txget.rb --config config.conf --nick test"
|
|
351
351
|
puts " \txget.rb --files test1.txt:test2.txt:test3.txt"
|
|
352
|
-
puts " \txget.rb irc.rizon.net
|
|
353
|
-
puts " \txget.rb irc.rizon.net
|
|
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"
|
|
354
356
|
exit
|
|
355
357
|
end
|
|
356
358
|
|
|
@@ -409,7 +411,7 @@ if __FILE__ == $0 then
|
|
|
409
411
|
config[$1] << "/" unless config[$1][-1] == "/"
|
|
410
412
|
next
|
|
411
413
|
when "sleep-interval" then config[$1] = $2.to_i
|
|
412
|
-
when "skip-existing"
|
|
414
|
+
when "skip-existing" then config[$1] = ($2 == "true")
|
|
413
415
|
when "allow-queueing" then config[$1] = ($2 == "true")
|
|
414
416
|
else
|
|
415
417
|
# Add value to current header, default is *
|
|
@@ -445,34 +447,32 @@ if __FILE__ == $0 then
|
|
|
445
447
|
end
|
|
446
448
|
|
|
447
449
|
# Parse to_check array for valid XDCC links, irc.serv.org/#chan/bot/pack
|
|
448
|
-
tmp_requests
|
|
450
|
+
tmp_requests = []
|
|
449
451
|
to_check.each do |x|
|
|
450
|
-
if x =~ /^(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
465
472
|
end
|
|
466
|
-
tmp_range =* (pack..to_range).step(step)
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Convert range array to new requests
|
|
470
|
-
unless tmp_range.empty?
|
|
471
|
-
tmp_range.each { |y| tmp_requests.push XDCC_REQ.new serv, chan, bot, y, info }
|
|
472
|
-
tmp_range.clear
|
|
473
473
|
end
|
|
474
474
|
else
|
|
475
|
-
puts_abort "#{x} is not a valid XDCC address\n XDCC Address format: irc.serv.com
|
|
475
|
+
puts_abort "#{x} is not a valid XDCC address\n XDCC Address format: #chan@irc.serv.com/bot/pack(s)"
|
|
476
476
|
end
|
|
477
477
|
end
|
|
478
478
|
|
|
@@ -503,17 +503,16 @@ if __FILE__ == $0 then
|
|
|
503
503
|
# Sort requests by pack
|
|
504
504
|
requests.each do |k,v|
|
|
505
505
|
puts "#{k} \e[1;37m->\e[0m"
|
|
506
|
-
v
|
|
506
|
+
v.sort_by { |x| [x.chan, x.bot, x.pack] }.each { |x| puts " #{x}" }
|
|
507
507
|
end
|
|
508
508
|
puts
|
|
509
509
|
|
|
510
510
|
# H-h-here we g-go...
|
|
511
511
|
requests.each do |k, v|
|
|
512
512
|
req, info = v[0], config["servers"][v[0].info]
|
|
513
|
-
last_chan, cur_req, motd
|
|
514
|
-
nick_sent, nick_check, nick_valid
|
|
515
|
-
|
|
516
|
-
xdcc_sent, xdcc_accepted, xdcc_queued = false, false, false
|
|
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
|
|
517
516
|
xdcc_accept_time, xdcc_ret, req_send_time = nil, nil, nil
|
|
518
517
|
|
|
519
518
|
stream = Stream.new req.serv
|
|
@@ -534,7 +533,7 @@ if __FILE__ == $0 then
|
|
|
534
533
|
puts_error 'Login failed'
|
|
535
534
|
stream.disconnect
|
|
536
535
|
end
|
|
537
|
-
puts "> \e[1;32m#{msg}\e[0m"
|
|
536
|
+
#puts "> \e[1;32m#{msg}\e[0m"
|
|
538
537
|
else
|
|
539
538
|
if prefix =~ /^NickServ!/
|
|
540
539
|
if not nick_sent and info[:nserv] != nil
|
|
@@ -550,7 +549,7 @@ if __FILE__ == $0 then
|
|
|
550
549
|
nick_check = true
|
|
551
550
|
end
|
|
552
551
|
end
|
|
553
|
-
puts "> \e[1;33m#{msg}\e[0m"
|
|
552
|
+
#puts "> \e[1;33m#{msg}\e[0m"
|
|
554
553
|
elsif prefix =~ /^#{Regexp.escape req.bot}!(.*)$/i
|
|
555
554
|
case msg
|
|
556
555
|
when /already requested that pack/i, /closing connection/i, /you have a dcc pending/i
|
|
@@ -579,10 +578,10 @@ if __FILE__ == $0 then
|
|
|
579
578
|
req_send_time = nil
|
|
580
579
|
|
|
581
580
|
tmp_fname = fname
|
|
582
|
-
fname
|
|
581
|
+
fname = $1 if tmp_fname =~ /^"(.*)"$/
|
|
583
582
|
puts "Preparing to download: \e[36m#{fname}\e[0m"
|
|
584
|
-
fname
|
|
585
|
-
xdcc_ret
|
|
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
|
|
586
585
|
|
|
587
586
|
# Check if the for unfinished download amd try to resume
|
|
588
587
|
if File.exists? xdcc_ret.fname and File.stat(xdcc_ret.fname).size < xdcc_ret.fsize
|
|
@@ -630,17 +629,17 @@ if __FILE__ == $0 then
|
|
|
630
629
|
|
|
631
630
|
Process.wait pid
|
|
632
631
|
xdcc_sent, xdcc_accepted, xdcc_queued = false, false, false
|
|
633
|
-
xdcc_accept_time, xdcc_ret
|
|
632
|
+
xdcc_accept_time, xdcc_ret = nil, nil
|
|
634
633
|
end
|
|
635
634
|
end
|
|
636
635
|
when /^\d+?$/
|
|
637
636
|
type_i = type.to_i
|
|
638
637
|
case type_i
|
|
639
|
-
when 1 # Print welcome message, because it's nice
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
when 400..533 # Handle errors, except
|
|
643
|
-
next if
|
|
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
|
|
644
643
|
puts_error "#{msg}"
|
|
645
644
|
stream.disconnect
|
|
646
645
|
when 376 then motd = true # Mark the end of the MOTD
|
|
@@ -675,7 +674,7 @@ if __FILE__ == $0 then
|
|
|
675
674
|
|
|
676
675
|
stream << "PRIVMSG #{req.bot} :XDCC SEND #{req.pack}"
|
|
677
676
|
req_send_time = Time.now
|
|
678
|
-
xdcc_sent
|
|
677
|
+
xdcc_sent = true
|
|
679
678
|
end
|
|
680
679
|
|
|
681
680
|
# Wait 3 seconds for DCC SEND response, if there isn't one, abort
|
|
@@ -683,7 +682,7 @@ if __FILE__ == $0 then
|
|
|
683
682
|
if config["allow-queueing"] and xdcc_queued
|
|
684
683
|
next
|
|
685
684
|
end
|
|
686
|
-
if (Time.now - req_send_time).floor >
|
|
685
|
+
if (Time.now - req_send_time).floor > 10
|
|
687
686
|
puts_error "#{req.bot} took too long to respond, are you sure it's a bot?"
|
|
688
687
|
stream.disconnect
|
|
689
688
|
bot.stop
|
|
@@ -692,7 +691,7 @@ if __FILE__ == $0 then
|
|
|
692
691
|
|
|
693
692
|
# Wait 3 seconds for a DCC ACCEPT response, if there isn't one, don't resume
|
|
694
693
|
if xdcc_sent and xdcc_accepted and not xdcc_accept_time.nil?
|
|
695
|
-
if (Time.now - xdcc_accept_time).floor >
|
|
694
|
+
if (Time.now - xdcc_accept_time).floor > 10
|
|
696
695
|
puts "FAILED! Bot client doesn't support resume!"
|
|
697
696
|
puts "Connecting to: #{req.bot} @ #{xdcc_ret.ip}:#{xdcc_ret.port}"
|
|
698
697
|
dcc_download xdcc_ret.ip, xdcc_ret.port, xdcc_ret.fname, xdcc_ret.fsize
|
data/xget.rb
ADDED
|
@@ -0,0 +1,711 @@
|
|
|
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
|
+
require 'Win32/Console/ANSI' if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
|
10
|
+
%w(socket thread slop timeout).each { |r| require r }
|
|
11
|
+
rescue LoadError
|
|
12
|
+
abort "#{$0} requires slop and, if you're on Windows, win32console\nPlease run 'gem install slop win32console'"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Why isn't this enabled by default?
|
|
16
|
+
Thread.abort_on_exception = true
|
|
17
|
+
# Put standard output into syncronised mode
|
|
18
|
+
$stdout.sync = true
|
|
19
|
+
|
|
20
|
+
# Version values
|
|
21
|
+
$ver_maj, $ver_min, $ver_rev = 2, 1, 1
|
|
22
|
+
$ver_str = "#{$ver_maj}.#{$ver_min}.#{$ver_rev}"
|
|
23
|
+
|
|
24
|
+
config = {
|
|
25
|
+
"out-dir" => './',
|
|
26
|
+
"skip-existing" => false,
|
|
27
|
+
"servers" => {},
|
|
28
|
+
"sleep-interval" => 5,
|
|
29
|
+
"allow-queueing" => false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def puts_error msg
|
|
33
|
+
puts "! \e[31mERROR\e[0m: #{msg}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def puts_abort msg
|
|
37
|
+
abort "! \e[31mERROR\e[0m: #{msg}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def puts_warning msg
|
|
41
|
+
puts "! \e[33mWARNING:\e[0m: #{msg}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Extend IO to readlines without blocking
|
|
45
|
+
class IO
|
|
46
|
+
def gets_nonblock
|
|
47
|
+
@rlnb_buffer ||= ""
|
|
48
|
+
ch = nil
|
|
49
|
+
while ch = self.read_nonblock(1)
|
|
50
|
+
@rlnb_buffer += ch
|
|
51
|
+
if ch == "\n" then
|
|
52
|
+
res = @rlnb_buffer
|
|
53
|
+
@rlnb_buffer = ""
|
|
54
|
+
return res
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Extend Array to get averages
|
|
61
|
+
class Array
|
|
62
|
+
def average
|
|
63
|
+
inject(:+) / count
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Class to hold XDCC requests
|
|
68
|
+
class XDCC_REQ
|
|
69
|
+
attr_accessor :serv, :chan, :bot, :pack, :info
|
|
70
|
+
|
|
71
|
+
def initialize serv, chan, bot, pack, info = "*"
|
|
72
|
+
@serv = serv
|
|
73
|
+
@chan = chan
|
|
74
|
+
@bot = bot
|
|
75
|
+
@pack = pack
|
|
76
|
+
@info = info
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def eql? other
|
|
80
|
+
self.serv == other.serv and self.chan == other.chan and self.bot == other.bot and self.pack == other.pack
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_s
|
|
84
|
+
"[ #{self.serv}, #{self.chan}, #{self.bot}, #{self.pack}, #{self.info} ]"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Class to hold DCC SEND info for when waiting for DCC ACCEPT
|
|
89
|
+
class XDCC_SEND
|
|
90
|
+
attr_accessor :fname, :fsize, :ip, :port
|
|
91
|
+
|
|
92
|
+
def initialize fname, fsize, ip, port
|
|
93
|
+
@fname = fname
|
|
94
|
+
@fsize = fsize
|
|
95
|
+
@ip = ip
|
|
96
|
+
@port = port
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def to_s
|
|
100
|
+
"[ #{self.fname}, #{self.fsize}, #{self.ip}, #{self.port} ]"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Class to emit events
|
|
105
|
+
module Emitter
|
|
106
|
+
def callbacks
|
|
107
|
+
@callbacks ||= Hash.new { |h, k| h[k] = [] }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def on type, &block
|
|
111
|
+
callbacks[type] << block
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def emit type, *args
|
|
116
|
+
callbacks[type].each do |block|
|
|
117
|
+
block.call(*args)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Class to handle IRC stream and emit events
|
|
123
|
+
class Stream
|
|
124
|
+
include Emitter
|
|
125
|
+
attr_accessor :io, :buf
|
|
126
|
+
|
|
127
|
+
def initialize serv
|
|
128
|
+
@buf = []
|
|
129
|
+
Timeout.timeout(5) { @io = TCPSocket.new serv, 6667 }
|
|
130
|
+
rescue SocketError => e
|
|
131
|
+
puts_abort "Failed to connect to #{serv}! #{e.message}"
|
|
132
|
+
rescue Timeout::Error
|
|
133
|
+
puts_abort "Connection to #{serv} timed out!"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def disconnect
|
|
137
|
+
@io.puts 'QUIT'
|
|
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
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Get relative size from bytes
|
|
189
|
+
def bytes_to_closest bytes
|
|
190
|
+
fsize_arr = [ 'B', 'KB', 'MB', 'GB', 'TB' ]
|
|
191
|
+
exp = (Math.log(bytes) / Math.log(1024)).to_i
|
|
192
|
+
exp = fsize_arr.length if exp > fsize_arr.length
|
|
193
|
+
bytes /= 1024.0 ** exp
|
|
194
|
+
return "#{bytes.round(2)}#{fsize_arr[exp]}"
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Loop until there is no file with the same name
|
|
198
|
+
def safe_fname fname
|
|
199
|
+
return fname unless File.exists? fname
|
|
200
|
+
|
|
201
|
+
ext = File.extname fname
|
|
202
|
+
base = File.basename fname, ext
|
|
203
|
+
dir = File.dirname fname
|
|
204
|
+
|
|
205
|
+
cur = 2
|
|
206
|
+
while true
|
|
207
|
+
test = "#{dir}/#{base} (#{cur})#{ext}"
|
|
208
|
+
return test unless File.exists? test
|
|
209
|
+
cur += 1
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Get a close relative time remaining, in words
|
|
214
|
+
def time_distance t
|
|
215
|
+
if t < 60
|
|
216
|
+
case t
|
|
217
|
+
when 0 then "- nevermind, done!"
|
|
218
|
+
when 1..4 then "in a moment!"
|
|
219
|
+
when 5..9 then "less than 10 seconds"
|
|
220
|
+
when 10..19 then "less than 20 seconds"
|
|
221
|
+
when 20..39 then "half a minute"
|
|
222
|
+
else "less than a minute"
|
|
223
|
+
end
|
|
224
|
+
else # Use minutes, to aovid big numbers
|
|
225
|
+
t = t / 60.0
|
|
226
|
+
case t.to_i
|
|
227
|
+
when 1 then "about a minute"
|
|
228
|
+
when 2..45 then "#{t.round} minutes"
|
|
229
|
+
when 45..90 then "about an hour"
|
|
230
|
+
when 91..1440 then "about #{(t / 60.0).round} hours"
|
|
231
|
+
when 1441..2520 then "about a day"
|
|
232
|
+
when 2521..86400 then "about #{(t / 1440.0).round} days"
|
|
233
|
+
else "about #{(t/ 43200.0).round} months"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Get elapsed time in words
|
|
239
|
+
def time_elapsed t
|
|
240
|
+
return "instantly!" if t <= 0
|
|
241
|
+
|
|
242
|
+
# Get the GMTime from seconds and split
|
|
243
|
+
ta = Time.at(t).gmtime.strftime('%S|%M|%H|%-d|%-m|%Y').split('|', 6).collect { |i| i.to_i }
|
|
244
|
+
ta[-1] -= 1970 # fuck the police
|
|
245
|
+
ta[-2] -= 1 # fuck, fuck
|
|
246
|
+
ta[-3] -= 1 # fuck the police
|
|
247
|
+
|
|
248
|
+
# Remove the 0 digets
|
|
249
|
+
i = 0
|
|
250
|
+
ta.reverse.each do |x|
|
|
251
|
+
break if x != 0
|
|
252
|
+
i += 1
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Unit suffixes
|
|
256
|
+
suffix = [ "seconds", "minutes", "hours", "days", "months", "years" ]
|
|
257
|
+
# Don't use plural if x is 1
|
|
258
|
+
plural = ->(x, y) { x == 1 ? y[0..-2] : y }
|
|
259
|
+
# Format string to "value unit"
|
|
260
|
+
format_str = ->(x) { "#{ta[x]} #{plural[ta[x], suffix[x]]}, " }
|
|
261
|
+
|
|
262
|
+
# Form the string
|
|
263
|
+
ta = ta.take(ta.length - i)
|
|
264
|
+
str = ""
|
|
265
|
+
(ta.length - 1).downto(0) { |x| str += format_str[x] }
|
|
266
|
+
"in #{str[0..-3]}"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# DCC download handler
|
|
270
|
+
def dcc_download ip, port, fname, fsize, read = 0
|
|
271
|
+
sock = nil
|
|
272
|
+
begin
|
|
273
|
+
Timeout.timeout(5) { sock = TCPSocket.new ip, port }
|
|
274
|
+
rescue Timeout::Error
|
|
275
|
+
puts_abort "Connection to #{ip} timed out!"
|
|
276
|
+
end
|
|
277
|
+
puts_abort "Failed to connect to \"#{ip}:#{port}\": #{e}" if sock.nil?
|
|
278
|
+
|
|
279
|
+
fsize_clean = bytes_to_closest fsize
|
|
280
|
+
avgs, last_check, start_time = [], Time.now - 2, Time.now
|
|
281
|
+
fh = File.open fname, (read == 0 ? "w" : "a") # Write or append
|
|
282
|
+
|
|
283
|
+
# Form the status bar
|
|
284
|
+
print_bar = ->() {
|
|
285
|
+
print "\r\e[0K> [ \e[1;37m"
|
|
286
|
+
pc = read.to_f / fsize.to_f * 100.0
|
|
287
|
+
bars = (pc / 10).to_i
|
|
288
|
+
bars.times { print "#" }
|
|
289
|
+
(10 - bars).times { print " " }
|
|
290
|
+
avg = avgs.average * 1024.0
|
|
291
|
+
time_rem = time_distance ((fsize - read) / avg) * 8.0
|
|
292
|
+
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}"
|
|
293
|
+
|
|
294
|
+
last_check = Time.now
|
|
295
|
+
avgs.clear
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
while buf = sock.readpartial(8192)
|
|
299
|
+
read += buf.bytesize
|
|
300
|
+
avgs << buf.bytesize
|
|
301
|
+
print_bar[] if (Time.now - last_check) > 1 and not avgs.empty?
|
|
302
|
+
|
|
303
|
+
begin
|
|
304
|
+
sock.write_nonblock [read].pack('N')
|
|
305
|
+
rescue Errno::EWOULDBLOCK
|
|
306
|
+
rescue Errno::EAGAIN => e
|
|
307
|
+
puts_error "#{File.basename fname} timed out! #{e}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
fh << buf
|
|
311
|
+
break if read >= fsize
|
|
312
|
+
end
|
|
313
|
+
print_bar.call unless avgs.empty?
|
|
314
|
+
elapsed_time = time_elapsed (Time.now - start_time).to_i
|
|
315
|
+
|
|
316
|
+
sock.close
|
|
317
|
+
fh.close
|
|
318
|
+
|
|
319
|
+
puts "\n! \e[1;32mSUCCESS\e[0m: downloaded #{File.basename fname} #{elapsed_time}"
|
|
320
|
+
rescue EOFError, SocketError => e
|
|
321
|
+
puts "\n! ERROR: #{File.basename fname} failed to download! #{e}"
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
if __FILE__ == $0 then
|
|
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 = ($*)
|
|
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)"
|
|
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
|
|
711
|
+
end
|
metadata
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xget
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 2.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- George Watson
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2013-04-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description:
|
|
13
|
+
description: XDCC Download Client
|
|
14
14
|
email: gigolo@hotmail.co.uk
|
|
15
15
|
executables:
|
|
16
16
|
- xget
|
|
@@ -18,9 +18,10 @@ extensions: []
|
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
20
|
- bin/xget
|
|
21
|
-
|
|
21
|
+
- xget.rb
|
|
22
|
+
homepage: http://rubygems.org/gems/hola
|
|
22
23
|
licenses:
|
|
23
|
-
-
|
|
24
|
+
- MIT
|
|
24
25
|
metadata: {}
|
|
25
26
|
post_install_message:
|
|
26
27
|
rdoc_options: []
|
|
@@ -41,5 +42,5 @@ rubyforge_project:
|
|
|
41
42
|
rubygems_version: 2.5.1
|
|
42
43
|
signing_key:
|
|
43
44
|
specification_version: 4
|
|
44
|
-
summary: XDCC
|
|
45
|
+
summary: XDCC Download Client
|
|
45
46
|
test_files: []
|