Class: LeechtopDownloader::Client

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/leechtop_downloader/client.rb

Overview

Client handles the network requests to Leechtop.

Defined Under Namespace

Classes: DownloadError

Class Method Summary collapse

Class Method Details

.download(url) ⇒ Object



23
24
25
26
27
28
29
30
31
32
# File 'lib/leechtop_downloader/client.rb', line 23

def self.download(url)
  direct_url = extract_direct_url(url)
  Down.download(direct_url, open_timeout: 10, read_timeout: 60)
rescue Down::NotFound
  raise DownloadError, 'The file could not be found on the host server (404 Not Found). It may have been deleted.'
rescue Down::ClientError, Down::ServerError => e
  raise DownloadError, "The host server rejected the download: #{e.message}"
rescue StandardError => e
  raise DownloadError, "Network or extraction failure: #{e.message}"
end

.download_from_html(html) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/leechtop_downloader/client.rb', line 38

def self.download_from_html(html)
  tokens = parse_tokens(html)
  direct_url = fetch_ajax_direct_link(tokens)
  Down.download(direct_url, open_timeout: 10, read_timeout: 60)
rescue Down::NotFound
  raise DownloadError, 'The file could not be found on the host server (404 Not Found). It may have been deleted.'
rescue Down::ClientError, Down::ServerError => e
  raise DownloadError, "The host server rejected the download: #{e.message}"
rescue StandardError => e
  raise DownloadError, "Network or extraction failure: #{e.message}"
end

.extract_direct_url(url) ⇒ Object



67
68
69
70
71
# File 'lib/leechtop_downloader/client.rb', line 67

def self.extract_direct_url(url)
  html = fetch_html(url)
  tokens = parse_tokens(html)
  fetch_ajax_direct_link(tokens)
end


135
136
137
138
139
140
141
142
143
# File 'lib/leechtop_downloader/client.rb', line 135

def self.extract_page_links(url)
  html = fetch_html(url)
  document = Nokogiri::HTML(html, nil, 'UTF-8')

  document.css('a').filter_map do |a|
    href = a['href']
    href if href&.match?(%r{^https?://(?:www\.)?leechtop\.com/})
  end
end


108
109
110
111
112
113
114
115
116
# File 'lib/leechtop_downloader/client.rb', line 108

def self.fetch_ajax_direct_link(tokens)
  ajax_url = 'https://leechtop.com/wp-admin/admin-ajax.php'
  body = URI.encode_www_form({ action: 'z_do_ajax', _action: 'directDownload' }.merge(tokens))
  post_response = Faraday.new(request: { open_timeout: 10, timeout: 60 }).post(ajax_url, body)

  raise "AJAX request failed: HTTP #{post_response.status}" unless post_response.status == 200

  parse_ajax_response(post_response.body)
end

.fetch_html(url) ⇒ Object



77
78
79
80
81
82
# File 'lib/leechtop_downloader/client.rb', line 77

def self.fetch_html(url)
  response = Faraday.new(request: { open_timeout: 10, timeout: 60 }).get(url)
  raise "Failed to load page: HTTP #{response.status}" unless response.status == 200

  response.body
end

.fetch_metadata(url) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/leechtop_downloader/client.rb', line 54

def self.(url)
  html = fetch_html(url)
  document = Nokogiri::HTML(html, nil, 'UTF-8')
  h4 = document.at_css('h4.mb-2')
  filename = h4&.text&.strip || ''

  { html: html, filename: filename }
end

.parse_ajax_response(body) ⇒ Object



122
123
124
125
126
127
128
129
# File 'lib/leechtop_downloader/client.rb', line 122

def self.parse_ajax_response(body)
  json = JSON.parse(body)
  direct_link = json['mes']

  raise "Server rejected download request: #{json}" if direct_link == 'no' || direct_link.nil?

  direct_link
end

.parse_tokens(html) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/leechtop_downloader/client.rb', line 88

def self.parse_tokens(html)
  document = Nokogiri::HTML(html, nil, 'UTF-8')

  button = document.at_css('.go-download-direct')
  raise 'Could not find download button in HTML' unless button

  nonce_match = html.match(/"nonce":"([^"]+)"/)
  raise 'Could not extract nonce from page' unless nonce_match

  {
    p: T.must(button['data-p']),
    mb: T.must(button['data-mb']),
    nonce: T.must(nonce_match[1])
  }
end