torckapi 0.0.1 → 0.0.2
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/Gemfile +2 -0
- data/Gemfile.lock +2 -0
- data/lib/torckapi/response/announce.rb +21 -5
- data/lib/torckapi/response/scrape.rb +4 -4
- data/lib/torckapi/tracker/base.rb +3 -3
- data/lib/torckapi/tracker/http.rb +20 -2
- data/lib/torckapi/tracker/udp.rb +12 -20
- data/lib/torckapi/version.rb +1 -1
- data/lib/torckapi.rb +0 -10
- data/torckapi.gemspec +3 -1
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2132ac0c587ca136fb4e0069018f02973d6d5e6
|
4
|
+
data.tar.gz: d72518b11c78f359da5857dfdc6491e96a636604
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cdd4f816b4e1526dc975cc8439a9e64cf17897fd1406343b5c202076bb724c38ca44e487d5a43aabe1f0158c68172b9b7df6b3caf52de1b07d6ad03b3dc7d099
|
7
|
+
data.tar.gz: 89779cb521dfa7e3497909e11c9713dcb7ef650d44a85e17cc318a4e820be1db8243b8960262d554757c0f3077a5734bb8fe3039cc23116683696e33d94f4ef3
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'ipaddr'
|
2
|
+
require 'bencode'
|
2
3
|
|
3
4
|
module Torckapi
|
4
5
|
module Response
|
@@ -19,19 +20,34 @@ module Torckapi
|
|
19
20
|
# @return [Torckapi::Response::Announce] response
|
20
21
|
def self.from_udp info_hash, data
|
21
22
|
leechers, seeders = data[0..7].unpack('L>2')
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
|
24
|
+
new info_hash, leechers, seeders, peers_from_compact(data[8..-1], leechers + seeders)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Construct response object from http response data
|
28
|
+
# @param info_hash [String] 40-char hexadecimal string
|
29
|
+
# @param data [String] HTTP response data (bencoded)
|
30
|
+
# @param compact [true, false] is peer data in compact format?
|
31
|
+
# @return [Torckapi::Response::Announce] response
|
32
|
+
def self.from_http info_hash, data, compact=true
|
33
|
+
bdecoded_data = BEncode.load(data)
|
34
|
+
leechers, seeders = bdecoded_data.values_at("incomplete", "complete")
|
35
|
+
|
36
|
+
new info_hash, leechers, seeders, peers_from_compact(bdecoded_data["peers"], [50, leechers + seeders].min)
|
25
37
|
end
|
26
|
-
|
38
|
+
|
27
39
|
private
|
28
|
-
|
40
|
+
|
29
41
|
def initialize info_hash, leechers, seeders, peers
|
30
42
|
@info_hash = info_hash
|
31
43
|
@leechers = leechers
|
32
44
|
@seeders = seeders
|
33
45
|
@peers = peers
|
34
46
|
end
|
47
|
+
|
48
|
+
def self.peers_from_compact data, peer_count
|
49
|
+
data.unpack('a6' * peer_count).map { |i| [IPAddr.ntop(i[0..3]), i[4..5].unpack('S>')[0]] }
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module Torckapi
|
2
2
|
module Response
|
3
|
-
|
3
|
+
|
4
4
|
# Scrape response
|
5
5
|
class Scrape
|
6
6
|
# @return [Hash<String, Hash>] scrape data
|
7
7
|
attr_reader :data
|
8
|
-
|
8
|
+
|
9
9
|
# Construct response object from udp response data
|
10
10
|
# @param info_hashes [Array<String>] list of 40-char hexadecimal strings
|
11
11
|
# @param data [String] UDP response data (omit action and transaction_id)
|
@@ -13,9 +13,9 @@ module Torckapi
|
|
13
13
|
def self.from_udp info_hashes, data
|
14
14
|
new Hash[info_hashes.zip(data.unpack('a12' * (info_hashes.count)).map { |i| Hash[[:seeders, :completed, :leechers].zip i.unpack('L>3').map(&:to_i)] })]
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
private
|
18
|
-
|
18
|
+
|
19
19
|
def initialize data
|
20
20
|
@data = data
|
21
21
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Torckapi
|
2
2
|
module Tracker
|
3
|
-
|
3
|
+
|
4
4
|
# Public interface for torrent trackers
|
5
5
|
class Base
|
6
6
|
# Announce Request
|
@@ -16,9 +16,9 @@ module Torckapi
|
|
16
16
|
def scrape info_hashes=[]
|
17
17
|
raise Torckapi::InvalidInfohashError if [*info_hashes].any? { |i| i !~ /\A[0-9a-f]{40}\z/i }
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
private
|
21
|
-
|
21
|
+
|
22
22
|
def initialize url, options={}
|
23
23
|
@url = url
|
24
24
|
@options = {timeout: 15, tries: 3}.merge(options)
|
@@ -1,10 +1,28 @@
|
|
1
|
+
require 'net/http'
|
1
2
|
require_relative 'base'
|
2
3
|
|
3
4
|
module Torckapi
|
4
5
|
module Tracker
|
5
|
-
|
6
|
-
# @todo implement
|
6
|
+
|
7
7
|
class HTTP < Base
|
8
|
+
|
9
|
+
# (see Base#announce)
|
10
|
+
def announce info_hash
|
11
|
+
super info_hash
|
12
|
+
|
13
|
+
@url.query ||= ""
|
14
|
+
@url.query += "info_hash=%s&num_want=500" % URI.encode([info_hash].pack('H*'))
|
15
|
+
|
16
|
+
Torckapi::Response::Announce.from_http info_hash, Net::HTTP.get(@url)
|
17
|
+
end
|
18
|
+
|
19
|
+
def scrape info_hashes=[]
|
20
|
+
super info_hashes
|
21
|
+
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
8
26
|
end
|
9
27
|
end
|
10
28
|
end
|
data/lib/torckapi/tracker/udp.rb
CHANGED
@@ -5,53 +5,47 @@ require_relative 'base'
|
|
5
5
|
|
6
6
|
module Torckapi
|
7
7
|
module Tracker
|
8
|
-
|
8
|
+
|
9
9
|
# Implementation of http://www.bittorrent.org/beps/bep_0015.html
|
10
10
|
class UDP < Base
|
11
|
-
# connection_id is valid for 1 minute as per protocol
|
12
11
|
CONNECTION_TIMEOUT = 60
|
13
|
-
|
12
|
+
|
14
13
|
# @see Base#announce
|
15
14
|
def announce info_hash
|
16
15
|
super info_hash
|
17
|
-
|
18
|
-
data = [[info_hash].pack('H*'), SecureRandom.random_bytes(20), [0, 0, 0, 0, 0, 0, -1, 0].pack('Q>3L>
|
16
|
+
|
17
|
+
data = [[info_hash].pack('H*'), SecureRandom.random_bytes(20), [0, 0, 0, 0, 0, 0, -1, 0].pack('Q>3L>4S>')].join
|
19
18
|
|
20
19
|
connect
|
21
20
|
response_body = communicate(1, data)[0] # announce
|
22
21
|
|
23
22
|
Torckapi::Response::Announce.from_udp info_hash, response_body[12..-1]
|
24
23
|
end
|
25
|
-
|
24
|
+
|
26
25
|
def scrape info_hashes=[]
|
27
26
|
super info_hashes
|
28
27
|
|
29
28
|
data = [*info_hashes].map { |i| [i].pack('H*') }.join
|
30
|
-
|
29
|
+
|
31
30
|
connect
|
32
31
|
response_body = communicate(2, data)[0] # scrape
|
33
|
-
|
32
|
+
|
34
33
|
Torckapi::Response::Scrape.from_udp [*info_hashes], response_body[8..-1]
|
35
34
|
end
|
36
35
|
|
37
36
|
private
|
38
37
|
|
39
|
-
def initialize *args
|
40
|
-
super *args
|
41
|
-
end
|
42
|
-
|
43
38
|
def connect
|
44
39
|
return if @connection_id && @communicated_at.to_i >= Time.now.to_i - CONNECTION_TIMEOUT
|
45
|
-
|
40
|
+
|
46
41
|
@connection_id = [0x41727101980].pack('Q>')
|
47
42
|
response_body = communicate(0)[0] # connect
|
48
43
|
@connection_id = response_body[8..15]
|
49
|
-
puts "connection_id = #{@connection_id.inspect}" if Torckapi::debug
|
50
44
|
end
|
51
|
-
|
45
|
+
|
52
46
|
def communicate action, data=nil
|
53
47
|
@socket ||= UDPSocket.new
|
54
|
-
|
48
|
+
|
55
49
|
transaction_id = SecureRandom.random_bytes(4)
|
56
50
|
packet = [@connection_id, [action].pack('L>'), transaction_id, data].join
|
57
51
|
|
@@ -59,17 +53,15 @@ module Torckapi
|
|
59
53
|
response = nil
|
60
54
|
begin
|
61
55
|
Timeout::timeout(@options[:timeout], CommunicationTimeoutError) do
|
62
|
-
puts "--> #{packet.inspect}" if Torckapi::debug
|
63
56
|
@socket.send(packet, 0, @url.host, @url.port)
|
64
57
|
response = @socket.recvfrom(65536)
|
65
|
-
puts "<-- #{response.inspect}" if Torckapi::debug
|
66
58
|
raise TransactionIdMismatchError if transaction_id != response[0][4..7]
|
67
59
|
@communicated_at = Time.now
|
68
60
|
end
|
69
61
|
rescue CommunicationTimeoutError
|
70
|
-
retry if (tries += 1) <= @options[:tries]
|
62
|
+
retry if (tries += 1) <= @options[:tries]
|
71
63
|
end
|
72
|
-
|
64
|
+
|
73
65
|
response
|
74
66
|
end
|
75
67
|
end
|
data/lib/torckapi/version.rb
CHANGED
data/lib/torckapi.rb
CHANGED
@@ -7,16 +7,6 @@ require 'torckapi/response/announce'
|
|
7
7
|
require 'torckapi/response/scrape'
|
8
8
|
|
9
9
|
module Torckapi
|
10
|
-
@@debug = false
|
11
|
-
|
12
|
-
def self.debug
|
13
|
-
@@debug
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.debug= debug
|
17
|
-
@@debug = !!debug
|
18
|
-
end
|
19
|
-
|
20
10
|
# Creates a tracker interface instance
|
21
11
|
# @param tracker_url [String] tracker announce url
|
22
12
|
# @param options [Hash] defaults to \\{timeout: 15, tries: 3}
|
data/torckapi.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: torckapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dennis Krupenik
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bencode
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
description: torrent tracker api
|
42
56
|
email:
|
43
57
|
- dennis@krupenik.com
|