ttwatcher 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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