nanny 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 922e91e738bb2e20f7dd0e2b775c2c5dabc2d4b9
4
+ data.tar.gz: 5f044c48c7107dc31639b6a4ed744d75b935b2f2
5
+ SHA512:
6
+ metadata.gz: d8afcb6ea3007e32a0813b20de87215029ccab54011e11398f5f1c2ee63119d7febcaa7f0351d1e5d4d2468d7de455d60dc0a3e7b9b819b22b7a9b4d41a371d0
7
+ data.tar.gz: 05ac6fb0577555586e8eeb433b8f73daa6c439d389e249d8528af594f8863f5f973bf2976e8b9ae5f2a7b55606bc0f2e9929559c20e956188e115cd1d283d031
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - "1.9.3"
5
+
6
+ script: "bundle exec rspec spec"
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in torrent.gemspec
4
+ gemspec
5
+
6
+ gem 'vcr'
7
+ gem 'rspec'
8
+ gem 'webmock'
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nanny (0.0.1)
5
+ active_support
6
+ addressable
7
+ colored
8
+ highline
9
+ nokogiri
10
+ rest-client
11
+ ruby-progressbar
12
+ terminal-table
13
+ trollop
14
+
15
+ GEM
16
+ remote: https://rubygems.org/
17
+ specs:
18
+ active_support (3.0.0)
19
+ activesupport (= 3.0.0)
20
+ activesupport (3.0.0)
21
+ addressable (2.3.2)
22
+ colored (1.2)
23
+ crack (0.3.2)
24
+ diff-lcs (1.1.3)
25
+ highline (1.6.15)
26
+ mime-types (1.20.1)
27
+ nokogiri (1.5.6)
28
+ rake (10.0.4)
29
+ rest-client (1.6.7)
30
+ mime-types (>= 1.16)
31
+ rspec (2.12.0)
32
+ rspec-core (~> 2.12.0)
33
+ rspec-expectations (~> 2.12.0)
34
+ rspec-mocks (~> 2.12.0)
35
+ rspec-core (2.12.2)
36
+ rspec-expectations (2.12.1)
37
+ diff-lcs (~> 1.1.3)
38
+ rspec-mocks (2.12.2)
39
+ ruby-progressbar (1.0.2)
40
+ terminal-table (1.4.5)
41
+ trollop (2.0)
42
+ vcr (2.4.0)
43
+ webmock (1.9.0)
44
+ addressable (>= 2.2.7)
45
+ crack (>= 0.1.7)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ nanny!
52
+ rake
53
+ rspec
54
+ vcr
55
+ webmock
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stefano Verna
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,46 @@
1
+ # Nanny [![Build Status](https://travis-ci.org/stefanoverna/nanny.png?branch=master)](https://travis-ci.org/stefanoverna/nanny)
2
+
3
+ Nanny is a library that uses multiple torrent search engine to find a valid direct
4
+ link to a Torrent. It currently supports Torrentz.eu, Torcache.net and Torrage.com
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'nanny'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install nanny
19
+
20
+ ## Usage
21
+
22
+ You can use `nanny` programmatically:
23
+
24
+ ```ruby
25
+ require 'nanny'
26
+
27
+ torrents = Nanny::Search.new.search_torrents("Ubuntu")
28
+
29
+ torrent.first
30
+ # => <Nanny::Torrent @title="Ubuntu 12 10 Desktop i386", @url="http://torrentz.eu/335990d615594b9be409ccfeb95864e24ec702c7", @seeds=2158, @peers=32, @size=789577728, @hash="335990d615594b9be409ccfeb95864e24ec702c7">
31
+
32
+ torrent.first.torrent_url
33
+ # => "http://torcache.net/torrent/335990D615594B9BE409CCFEB95864E24EC702C7.torrent"
34
+ ```
35
+
36
+ or as a CLI tool:
37
+
38
+ ![cli usage](https://raw.github.com/stefanoverna/nanny/master/doc/cli.png)
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'nanny'
3
+ cli = Nanny::CLI.new
4
+ cli.run!
5
+
Binary file
@@ -0,0 +1,13 @@
1
+ module Nanny
2
+ autoload :AttrInitializer, 'nanny/attr_initializer'
3
+ autoload :Search, 'nanny/search'
4
+ autoload :Torrent, 'nanny/torrent'
5
+ autoload :Torcache, 'nanny/torcache'
6
+ autoload :Tracker, 'nanny/tracker'
7
+ autoload :MagnetURI, 'nanny/magnet_uri'
8
+ autoload :VERSION, 'nanny/version'
9
+ autoload :CLI, 'nanny/cli'
10
+ autoload :HTTPClient, 'nanny/http_client'
11
+ autoload :Progress, 'nanny/progress'
12
+ end
13
+
@@ -0,0 +1,13 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/hash/keys'
3
+
4
+ module Nanny
5
+ module AttrInitializer
6
+ def initialize(opts = {})
7
+ opts.each do |opt, val|
8
+ instance_variable_set("@#{opt}", val) if respond_to? opt
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,90 @@
1
+ require 'active_support/core_ext/string/strip'
2
+ require 'trollop'
3
+ require 'terminal-table'
4
+ require 'colored'
5
+ require 'highline'
6
+ require 'ruby-progressbar'
7
+
8
+ module Nanny
9
+ class CLI
10
+
11
+ def run!
12
+ options = process_options!
13
+
14
+ if query.size.zero?
15
+ Trollop::die "A query is required"
16
+ end
17
+
18
+ torrents = Nanny::Search.new.search_torrents(query)
19
+ results_range = 0 .. options[:limit] - 1
20
+
21
+ puts formatted_torrents(torrents[results_range])
22
+ puts
23
+ torrent_index = highline.ask("Choose a torrent to download", Integer) do |q|
24
+ q.default = 0
25
+ q.in = results_range
26
+ end
27
+ torrent = torrents[torrent_index]
28
+
29
+ puts
30
+
31
+ bar = ProgressBar.create(title: "Searching")
32
+ progress = Progress.new do |p|
33
+ bar.progress = 100.0 * p.total_done / p.total_todo
34
+ end
35
+
36
+ highline.say torrent.torrent_url(progress)
37
+ end
38
+
39
+ def formatted_torrents(torrents)
40
+ rows = torrents.each_with_index.map do |torrent, i|
41
+ [
42
+ i,
43
+ truncate_string(torrent.title),
44
+ torrent.human_size,
45
+ torrent.seeds.to_s.red,
46
+ torrent.peers.to_s.green
47
+ ]
48
+ end
49
+ rows = [
50
+ [ "#", "Title", "Size", "Seeds", "Peers" ],
51
+ :separator
52
+ ] + rows
53
+ Terminal::Table.new(rows: rows).to_s
54
+ end
55
+
56
+ def process_options!
57
+ Trollop::options do
58
+ banner <<-RAW.strip_heredoc
59
+
60
+ nanny is your friendly and lovely torrent finder
61
+
62
+ Usage:
63
+ nanny [options] <query>
64
+
65
+ Options:
66
+ RAW
67
+ version "Nanny #{Nanny::VERSION} (c) Stefano Verna"
68
+ opt :limit, "The number of results", default: 5
69
+ end
70
+ end
71
+
72
+ def query
73
+ ARGV.join(" ")
74
+ end
75
+
76
+ def truncate_string(text)
77
+ if text.size > 50
78
+ text[0..50] + "..."
79
+ else
80
+ text
81
+ end
82
+ end
83
+
84
+ def highline
85
+ @highline ||= HighLine.new
86
+ end
87
+
88
+ end
89
+ end
90
+
@@ -0,0 +1,26 @@
1
+ require 'rest-client'
2
+
3
+ module Nanny
4
+ class HTTPClient
5
+
6
+ class Exception < RuntimeError; end
7
+
8
+ def self.headers(url)
9
+ RestClient.head(url).headers
10
+ rescue URI::InvalidURIError
11
+ rescue SocketError
12
+ rescue RestClient::Exception
13
+ raise Exception
14
+ end
15
+
16
+ def self.get(url)
17
+ RestClient.get(url).body.force_encoding("utf-8")
18
+ rescue URI::InvalidURIError
19
+ rescue SocketError
20
+ rescue RestClient::Exception
21
+ raise Exception
22
+ end
23
+
24
+ end
25
+ end
26
+
@@ -0,0 +1,15 @@
1
+ module Nanny
2
+ class MagnetURI
3
+ attr_reader :uri
4
+
5
+ def initialize(uri)
6
+ @uri = uri
7
+ end
8
+
9
+ def hash
10
+ uri.match(/xt=urn:btih:(?<hash>[a-z0-9]+)/i)["hash"]
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,65 @@
1
+ require 'active_support/core_ext/enumerable'
2
+
3
+ module Nanny
4
+ class Progress
5
+
6
+ attr_reader :childs
7
+ attr_reader :my_todo
8
+ attr_reader :my_done
9
+ attr_reader :parent
10
+
11
+ def initialize(parent = nil, &block)
12
+ @listener = block
13
+ @parent = parent
14
+ @my_todo = 0
15
+ @my_done = 0
16
+ @childs = []
17
+ end
18
+
19
+ def step
20
+ todo(1)
21
+ result = yield
22
+ done!(1)
23
+ result
24
+ end
25
+
26
+ def todo(n)
27
+ @my_todo += n
28
+ notify!
29
+ end
30
+
31
+ def done!(n)
32
+ @my_done += n
33
+ notify!
34
+ end
35
+
36
+ def complete!
37
+ if @my_done != my_todo
38
+ @my_done = my_todo
39
+ childs.each(&:complete!)
40
+ notify!
41
+ end
42
+ end
43
+
44
+ def child
45
+ Progress.new(self).tap do |child|
46
+ @childs << child
47
+ end
48
+ end
49
+
50
+ def total_todo
51
+ my_todo + childs.sum(&:total_todo)
52
+ end
53
+
54
+ def total_done
55
+ my_done + childs.sum(&:total_done)
56
+ end
57
+
58
+ def notify!
59
+ parent.notify! if parent
60
+ @listener.call(self) if @listener
61
+ end
62
+
63
+ end
64
+ end
65
+
@@ -0,0 +1,49 @@
1
+ require 'nokogiri'
2
+ require 'addressable/template'
3
+
4
+ module Nanny
5
+ class Search
6
+
7
+ SIZE_UNITS = %w(b kb mb gb tb)
8
+ FEED_ITEMS_XPATH = '//rss/channel/item'
9
+ FEED_TEMPLATE_URL = Addressable::Template.new("http://torrentz.eu/feed?q={query}")
10
+ FEED_DESC_REGEXP = /
11
+ Size: \s* (?<size>\d+) \s* (?<size_unit>[KMGT]?B)
12
+ .*
13
+ Seeds: \s* (?<seeds>[\d,]+)
14
+ \s+
15
+ Peers: \s* (?<peers>[\d,]+)
16
+ \s+
17
+ Hash: \s* (?<hash>[a-f0-9]+)
18
+ /xi
19
+
20
+ def search_torrents(query)
21
+ doc = document_for_query(query)
22
+ doc.xpath(FEED_ITEMS_XPATH).map do |item|
23
+ torrent_from_item(item)
24
+ end
25
+ end
26
+
27
+ def torrent_from_item(item)
28
+ desc = item.at_xpath('.//description').text
29
+ matches = desc.match(FEED_DESC_REGEXP)
30
+ exp = SIZE_UNITS.index(matches['size_unit'].downcase)
31
+ size = matches['size'].to_i * (1024 ** exp)
32
+ Torrent.new(
33
+ title: item.at_xpath('.//title').text,
34
+ url: item.at_xpath('.//link').text,
35
+ seeds: matches['seeds'].gsub(",", "").to_i,
36
+ peers: matches['peers'].gsub(",", "").to_i,
37
+ size: size,
38
+ hash: matches['hash']
39
+ )
40
+ end
41
+
42
+ def document_for_query(query)
43
+ url = FEED_TEMPLATE_URL.expand(query: query).to_s
44
+ Nokogiri::XML HTTPClient.get(url)
45
+ end
46
+
47
+ end
48
+ end
49
+
@@ -0,0 +1,24 @@
1
+ require 'addressable/template'
2
+
3
+ module Nanny
4
+ class Torcache
5
+ TORCACHE_URL = Addressable::Template.new("http://torcache.net/torrent/{hash}.torrent")
6
+ TORRAGE_URL = Addressable::Template.new("http://torrage.com/torrent/{hash}.torrent")
7
+
8
+ class HashNotFound < RuntimeError; end
9
+
10
+ def self.url_for(hash)
11
+ [ TORCACHE_URL, TORRAGE_URL ].each do |template|
12
+ begin
13
+ url = template.expand(hash: hash.upcase).to_s
14
+ HTTPClient.headers(url)
15
+ return url
16
+ rescue HTTPClient::Exception
17
+ end
18
+ end
19
+ raise Torcache::HashNotFound
20
+ end
21
+
22
+ end
23
+ end
24
+