ttwatcher 1.0.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.
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module Parsers
5
+ class Zooqle < SimpleParser
6
+ private
7
+
8
+ def new_pages_list # no-doc
9
+ return @links if @links.is_a? Array
10
+
11
+ tmp = structure.css('ul[@class="pagination smaller pull-right margin-top-20"]')
12
+ .css('li')
13
+ return @links = [] if tmp.nil? || tmp.empty?
14
+
15
+ url_part = tmp.css('a').attr('href').to_s.gsub(/pg=\d*/,'pg=+NUMBER+')
16
+ mm = tmp.slice(0..-2).map { |node| node.css('a').text.to_i}.minmax
17
+
18
+ @links = (mm.first..mm.last).map do |page_number|
19
+ 'search' + url_part.gsub(/\+NUMBER\+/, page_number.to_s)
20
+ end.slice!(2..-1) || []
21
+ end
22
+
23
+ def torrents_unparsed # no-doc
24
+ structure.css('table[@class="table table-condensed table-torrents vmiddle"]')
25
+ .css('tr').drop(1)
26
+ end
27
+
28
+ # @param [Nokogiri::Node] unparsed_data
29
+ #
30
+ # Surface scan for +zooqle+ gives next information about single torrent
31
+ #
32
+ # ++ hsh[:name] ==> ex. "Cats swimming in pool 2016 BDRIP"
33
+ # -- hsh[:description] ==> ex. "Hot CATS. Summer 2016"
34
+ # ++ hsh[:url] ==> ex. "example.torrent.side/12345"
35
+ # ++ hsh[:tracker] ==> ex. :super_cool_tracker
36
+ # -- hsh[:author] ==> ex. 'Bit kitty fun'
37
+ # -- hsh[:added_date] ==> ex. '2016-06-15'
38
+ # ++ hsh[:seeders] ==> ex. 50042
39
+ # ++ hsh[:leeches] ==> ex. 1
40
+ # ++ hsh[:size] ==> ex. "20000 mb"
41
+ # ++ hsh[:magnet_url] ==> ex. "magnet:?xt=urn....................."
42
+ # ++ hsh[:download_url] ==> ex. "example.torrent.side/12345/download"
43
+ #
44
+ # Where '++' means that field is present.
45
+ #
46
+ # @return [Torrent]
47
+
48
+ def extract_torrent(unparsed_data)
49
+ hsh = Hash.new
50
+
51
+ hsh[:name] = unparsed_data.css('td')[1].text
52
+
53
+ tmp_td1 = unparsed_data.css('td')[1].css('a').attr('href').to_s
54
+ hsh[:url] = assigned_site.address tmp_td1
55
+
56
+ tmp_td2 = unparsed_data.css('td')[2].css('li')
57
+ hsh[:magnet_url] = tmp_td2[0].css('a').attr('href')
58
+
59
+ if tmp_td2[1]
60
+ tmp = tmp_td2[1].css('a').attr('href').to_s
61
+ hsh[:download_url] = assigned_site.address tmp
62
+ end
63
+
64
+ hsh[:size] = unparsed_data.css('td')[3].text
65
+
66
+ tmp_td5 = unparsed_data.css('td')[5]
67
+ .css('div[@class="progress prog trans90"]')
68
+ .css('div')
69
+ if tmp_td5[1]
70
+ hsh[:seeders] = tmp_td5[1].text.to_i
71
+ hsh[:leeches] = tmp_td5[2].text.to_i
72
+ end
73
+
74
+ hsh[:tracker] = assigned_site.name
75
+
76
+ return Torrent.new(hsh)
77
+ end
78
+ end # class Zooqle
79
+ end # module Parsers
80
+ end # module TTWatcher
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module Sites
5
+ class Rutor < TorrentSite # > ex Rutor.org
6
+ private
7
+
8
+ def search_url(name) # no-doc
9
+ domain_name + '/search/0/0/000/0/%s' % name
10
+ end
11
+ end # class Rutor
12
+ end # module Sites
13
+ end # module TTWatcher
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module Sites
5
+ class Site
6
+
7
+ # @return [Symbol]
8
+ # Site name (autogenerated, depends from class name).
9
+
10
+ attr_reader :name
11
+
12
+ # @return [TTWatcher::Connection]
13
+
14
+ attr_reader :connection
15
+
16
+ # @return [String]
17
+ # Returns full domain name.
18
+
19
+ def domain_name
20
+ @domain_name ||=
21
+ if S[name][:domain_name]
22
+ S[name][:domain_name]
23
+ else
24
+ raise DomainNameNotFound, self
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Creates new Site object.
30
+ #
31
+ # @return [Site]
32
+
33
+ def initialize
34
+ @name = H::class_name self
35
+ @connection = Connection.new self
36
+ end
37
+
38
+ ##
39
+ # Generates an url that includes scheme + domain name + +path+ (optional)
40
+ #
41
+ # example: input ==> @domain_name = "some.site.com", path = 'hello/world'
42
+ # output ==> "http://some.site.com/hello/world"
43
+ #
44
+ # if path not selected it returns scheme + +@domain_name+
45
+
46
+ def address(path='')
47
+ scheme = connection.url.scheme
48
+ if !path.empty? && domain_name_included?(path)
49
+ InternetConnection::Scheme.add_scheme!(path, scheme)
50
+ else
51
+ hn = domain_name.dup # prevent unnecessary +domain_name+ mutation
52
+ InternetConnection::Scheme.add_scheme!(hn, scheme)
53
+ path = '/' + path unless path[0] == '/'
54
+ hn + path
55
+ end
56
+ end
57
+
58
+ # @param [String] url
59
+ #
60
+ # @param [Hash] request_params
61
+ #
62
+ # @return [String, NilClass]
63
+
64
+ def download_page(url, request_params = {})
65
+ url = address(url) unless domain_name_included? url
66
+ page = connection.download url, request_params
67
+
68
+ return page
69
+ end
70
+
71
+ private
72
+
73
+ # @param [String] url
74
+ # Url or an url part.
75
+ #
76
+ # @return [TrueClass, FalseClass]
77
+ # +true+ when url includes domain name
78
+ # +false+ otherwise.
79
+
80
+ def domain_name_included?(url)
81
+ url[domain_name.to_s]
82
+ end
83
+
84
+ ##
85
+ # Raised when param +domain name+ not found.
86
+
87
+ class DomainNameNotFound < TTWError
88
+ def initialize(klass) "Param ('domain_name') not found for #{klass.class} in config.yml." end
89
+ end
90
+ end # class Site
91
+ end # module Sites
92
+ end # module TTWatcher
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module Sites
5
+ class TorrentSite < Site # no-doc
6
+
7
+ # @param [String] name
8
+ #
9
+ # @return [TrueClass, FalseClass]
10
+ # Returns +false+ when +name+ too short or not exists.
11
+
12
+ def self.search_request_valid?(name)
13
+ name && name.length >= 3
14
+ end
15
+
16
+ ##
17
+ # Searches torrents.
18
+ #
19
+ # @param [String] name
20
+ # Torrent name.
21
+ #
22
+ # @return [TorrentList, NilClass]
23
+
24
+ def find_torrent(name, request_params = {})
25
+ return nil unless self.class.search_request_valid? name
26
+
27
+ html_body = download_page search_url(name), request_params
28
+ torrents = parser.parse html_body
29
+
30
+ return torrents
31
+ end
32
+
33
+ private
34
+
35
+ ##
36
+ # Url for torrent searching.
37
+ # see Sites#search_url for an example.
38
+ #
39
+ # @param [String] name
40
+ # Used to extend url with +name+ when it need.
41
+ # For an example check Rutor#search_url
42
+
43
+ def search_url(name=nil)
44
+ raise NotImplementedError, "Abstract method called!"
45
+ end
46
+
47
+ ##
48
+ # Returns a parser associated with Site instance throw class names.
49
+ #
50
+ # @return [Parsers::Base]
51
+
52
+ def parser
53
+ @parser ||=
54
+ begin
55
+ class_name = self.class.name.split('::').last
56
+ parser = TTWatcher::Parsers.const_get class_name
57
+ parser.new self
58
+ end
59
+ rescue NameError => exception
60
+ notificate_about_parser_crash! exception
61
+
62
+ return nil
63
+ end
64
+
65
+ def notificate_about_parser_crash!(exception)
66
+ Logger.with_backtrace "Parser #{self} crashed with message: *** #{exception.message} ***"
67
+ end
68
+ end # class TorrentSite
69
+ end # module Sites
70
+ end # module TTWatcher
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module Sites
5
+ class Unionpeer < TorrentSite
6
+
7
+ def find_torrent(name) # no-doc
8
+ super name, { url: { query_params: { 'nm' => name } } }
9
+ end
10
+
11
+ private
12
+
13
+ def search_url(name = nil) # no-doc
14
+ domain_name + '/tracker.php'
15
+ end
16
+ end # class Unionpeer
17
+ end # module Sites
18
+ end # module TTWatcher
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module Sites
5
+ class Zooqle < TorrentSite
6
+
7
+ def find_torrent(name) # no-doc
8
+ super name, { url: { query_params: { 'q' => name } } }
9
+ end
10
+
11
+ private
12
+
13
+ def search_url(name = nil) # no-doc
14
+ domain_name + '/search'
15
+ end
16
+ end # class Zooqle
17
+ end # module Sites
18
+ end # module TTWatcher
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ class Torrent
5
+
6
+ # @return [String]
7
+ # Returns torrent name.
8
+
9
+ attr_reader :name
10
+
11
+ # @return [String]
12
+ # Returns torrent description.
13
+
14
+ attr_reader :description
15
+
16
+ # @return [String]
17
+ # Returns url to an page with additional info about torrent.
18
+
19
+ attr_reader :url
20
+
21
+ # @return [String]
22
+ # Returns torrent size.
23
+
24
+ attr_reader :size
25
+
26
+ # @return [String]
27
+ # Returns torrent creator.
28
+
29
+ attr_reader :author
30
+
31
+ # @return [String]
32
+ # Returns leeches count.
33
+
34
+ attr_reader :leeches
35
+
36
+ # @return [String]
37
+ # Returns seeders count.
38
+
39
+ attr_reader :seeders
40
+
41
+ # @return [String]
42
+ # Returns date (as string) when torrent was added.
43
+
44
+ attr_reader :added_date
45
+
46
+ # @return [String]
47
+ # Returns magnet url to the torrent.
48
+
49
+ attr_reader :magnet_url
50
+
51
+ # @return [String]
52
+ # Returns direct url for torrent download.
53
+
54
+ attr_reader :download_url
55
+
56
+ # @return [Symbol]
57
+ # Returns tracker for the torrent.
58
+
59
+ attr_reader :tracker
60
+
61
+ ##
62
+ # Creates new Torrent instance.
63
+ #
64
+ # @param [Hash] params
65
+ # @option params [String] :name
66
+ # @option params [String] :description
67
+ # @option params [String] :url.
68
+ # @option params [Symbol] :tracker
69
+ # @option params [String] :author
70
+ # @option params [String] :added_date
71
+ # @option params [Integer] :seeders
72
+ # @option params [Integer] :leeches
73
+ # @option params [String] :size
74
+ # @option params [String] :magnet_url
75
+ # @option params [String] :download_url
76
+ #
77
+ # @return [Torrent]
78
+
79
+ def initialize(params = {})
80
+ %i(name description url tracker author added_date seeders
81
+ leeches size magnet_url download_url).each do |word|
82
+
83
+ instance_variable_set "@#{word}", params[word]
84
+ end
85
+ end
86
+ end # class Torrent
87
+ end # module TTWatcher
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ module TorrentAgent
5
+
6
+ ##
7
+ # TorrentAgent#search
8
+ # Resolves where exactly to search torrents.
9
+ #
10
+ # @param [String] torrent_name
11
+ # Torrent name (minimal length: 3 chars).
12
+ #
13
+ # @param [Hash] params
14
+ # @option params [Array<Symbol, String>, Symbol, String] :sites
15
+ # List of sites where we looking for torrent. By default
16
+ # it search everywhere ("rutor", "unionpeer", "zooqle", "megashara").
17
+ #
18
+ # @return [Array<Torrent>]
19
+ # Array (homogeneous) with torrents. Can be empty if nothing was found.
20
+
21
+ def self.search(torrent_name, params = {})
22
+ list = TorrentList.new
23
+
24
+ site_names = params[:from].nil? ? Sites.list : Array(params[:from])
25
+ site_names.each do |name|
26
+ site = Sites.fetch_torrent_site name
27
+ new_torrents = site.new.find_torrent torrent_name
28
+ list += new_torrents if new_torrents
29
+ end
30
+
31
+ return list.to_a
32
+ end
33
+ end # module TorrentsAgent
34
+ end # module TTWatcher
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module TTWatcher
4
+ class TorrentList
5
+ include Enumerable
6
+
7
+ ##
8
+ # Pushes new torrents into +TorrentList+.
9
+ #
10
+ # @param [Torrent, TorrentList] other
11
+ #
12
+ # @exception UnexpectedClass
13
+ # Raised when +other+ param class is not +Torrent+ or +TorrentList+
14
+ #
15
+ # @return [TorrentList<Torrent>]
16
+
17
+ def push(other)
18
+ case other
19
+ when Torrent
20
+ @torrents.push other
21
+ when TorrentList
22
+ @torrents += other.to_a
23
+ else
24
+ raise UnexpectedClass, other
25
+ end
26
+
27
+ return self
28
+ end
29
+ alias :<< :push
30
+ alias :+ :push
31
+
32
+ ##
33
+ # Creates new TorrentList instance.
34
+ #
35
+ # @return [TorrentList]
36
+
37
+ def initialize
38
+ @torrents = []
39
+ end
40
+
41
+ def each(&block) # for Enumerable mixin
42
+ @torrents.each { |obj| block.call obj }
43
+ end
44
+
45
+ ##
46
+ # See TorrentList#push description.
47
+
48
+ class UnexpectedClass < TTWError
49
+ def initialize(obj); super "Object #{obj} cannot being pushed into TorrentList, since it's '#{obj.class}' class when only TorrentList/Torrent instances can be pushed"; end end
50
+ end # class TorrentList
51
+ end # module TTWatcher