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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/xget +61 -62
  3. data/xget.rb +711 -0
  4. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fea40eb92d2b04847b8db0ebf07540c6474bebc6
4
- data.tar.gz: fa51ff8c1c38761aee63f47119a18ecdd2de0089
3
+ metadata.gz: d53971aeea75422201037489b8cba4f9842b8a6e
4
+ data.tar.gz: 2424da3f8132b7f3986c0c8b9dc3518450c01949
5
5
  SHA512:
6
- metadata.gz: 3fbe201554779a3dd35a48b2d42ee6af743825716030d30677741639c7d6163cfbd387b3c75b8e89821e59c03501d2e5001ca7051d3fb8727813784a734556b3
7
- data.tar.gz: dd8ec00178cd2f3e622ec1bed83ccb669cdfcca289c756b674edaa855cf68324e9a052841966480b3fd21c022f7377a015bfe8f7eb55b0a8f4efa2b5b2a4000d
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, 0
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 = ip
96
- @port = 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 then "- nevermind, done!"
218
- when 1..4 then "in a moment!"
219
- when 5..9 then "less than 10 seconds"
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 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"
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 = [ "seconds", "minutes", "hours", "days", "months", "years" ];
256
+ suffix = [ "seconds", "minutes", "hours", "days", "months", "years" ]
257
257
  # Don't use plural if x is 1
258
- plural = ->(x, y) { x == 1 ? y[0..-2] : y }
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 = read.to_f / fsize.to_f * 100.0
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/#news/ginpachi-sensei/1"
353
- puts " \txget.rb irc.rizon.net/#news/ginpachi-sensei/41..46"
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" then config[$1] = ($2 == "true")
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, tmp_range = [], []
450
+ tmp_requests = []
449
451
  to_check.each do |x|
450
- if x =~ /^(\w+?).(\w+?).(\w+?)\/#(\S+)\/(\S+)\/(\d+)(..\d+(\|\d+)?)?$/
451
- serv = [$1, $2, $3].join(".")
452
- info = (config["servers"].has_key?(serv) ? serv : "*")
453
- chan = "##{$4}"
454
- bot = $5
455
- pack = $6.to_i
456
-
457
- if $7.nil?
458
- tmp_requests.push XDCC_REQ.new serv, chan, bot, pack, info
459
- else
460
- step = $8.nil? ? 1 : $8[1..-1].to_i
461
- to_range = $7[2..-1].gsub(/(\|\d+)?$/, '').to_i # Clip off the ".." and the interval if present
462
- if pack > to_range or pack == to_range
463
- puts_error "Invalid range #{pack} to #{to_range} in \"#{x}\""
464
- next
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/#chan/bot/pack"
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 = v.sort_by { |x| [x.chan, x.pack] }.each { |x| puts "\t#{x}" }
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 = "", -1, false
514
- nick_sent, nick_check, nick_valid = false, false, false
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 = $1 if tmp_fname =~ /^"(.*)"$/
581
+ fname = $1 if tmp_fname =~ /^"(.*)"$/
583
582
  puts "Preparing to download: \e[36m#{fname}\e[0m"
584
- fname = (config["out-dir"].dup << fname)
585
- xdcc_ret = XDCC_SEND.new fname, fsize.to_i, [ip.to_i].pack('N').unpack('C4') * '.', port.to_i
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 = nil, nil
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
- msg.sub!(/#{Regexp.escape info[:nick]}/, "\e[34m#{info[:nick]}\e[0m")
641
- puts "! #{msg}"
642
- when 400..533 # Handle errors, except 439
643
- next if type_i == 439 # Skip 439
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 = true
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 > 3
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 > 3
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.0.2
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: 2015-10-15 00:00:00.000000000 Z
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
- homepage: http://rubygems.org/gems/xget
21
+ - xget.rb
22
+ homepage: http://rubygems.org/gems/hola
22
23
  licenses:
23
- - BSD-3-Clause
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 CLI download client
45
+ summary: XDCC Download Client
45
46
  test_files: []