use indicatif::{ProgressBar, ProgressStyle};
use regex::Regex;
use reqwest::{blocking::Client, header, Url};
use std::{
fs,
io::{self, copy, Read},
path::Path,
process,
};
use serde_json::Value;
struct DownloadProgress<R> {
inner: R,
progress_bar: ProgressBar,
}
impl<R: Read> Read for DownloadProgress<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf).map(|n| {
self.progress_bar.inc(n as u64);
n
})
}
}
pub fn run(url: String, download: bool, show_quality: bool, quality: String) {
if !url.contains("playlist") {
find_video(url, download, &show_quality, &quality);
} else {
let content = get_html(&url, false);
if content.contains("Error Page Not Found") {
eprintln!("Playlist is private, cannot access it. Try making it public");
process::exit(1);
}
find_playlist_videos(download, content);
}
}
fn find_video(url: String, download: bool, show_quality: &bool, quality: &str) {
let content = get_html(&url, true);
let title = {
let re = Regex::new("<title>(.*) - Pornhub.com</title>").unwrap();
let i = match re.captures(&content) {
Some(i) => i.get(1).unwrap().as_str().to_string(),
None => "video".to_string(),
};
i.replace("&", "&")
.replace("'", "'")
.replace("/", "╱") };
let url = get_direct_url(&content, &show_quality, quality);
if !show_quality {
if download {
download_video(&url, &title);
println!("Saved");
}
else {
println!("Direct URL: {}", &url);
}
}
}
fn find_playlist_videos(download: bool, content: String) {
let re_urls = Regex::new(
r#"<a href="/view_video\.php\?viewkey=(ph[0-9a-ö]+)&pkey=[0-9]+" title=".*".*class="fade"#,
)
.unwrap();
let show_quality = false;
let quality = "none".to_string();
for url in re_urls.captures_iter(&content) {
let url = format!(
"https://www.pornhub.com/view_video.php?viewkey={}",
url.get(1).unwrap().as_str().to_string()
);
find_video(url, download, &show_quality, &quality);
}
}
fn download_video(url: &str, name: &str) {
let total_size: u64 = {
let resp = ureq::head(url).call();
resp.header("content-length").unwrap().parse().unwrap()
};
let name = format!("{}.mp4", name);
let url = Url::parse(url).unwrap();
let client = Client::new();
let mut request = client.get(url.as_str());
let pb = ProgressBar::new(total_size);
pb.set_style(ProgressStyle::default_bar()
.template("{spinner:.yellow} [{elapsed_precise}] [{bar:40.yellow/blue}] {bytes}/{total_bytes} ({eta})")
.progress_chars("#>-"));
let mut file = Path::new(&name);
if file.exists() {
let size = file.metadata().unwrap().len();
request = request.header(header::RANGE, format!("bytes={}-", size));
pb.inc(size);
}
let mut source = DownloadProgress {
progress_bar: pb,
inner: request.send().unwrap(),
};
let dest_bool = match fs::OpenOptions::new().create(true).append(true).open(&file) {
Err(_e) => "err",
Ok(_o) => "ok",
};
let mut dest: std::fs::File;
if dest_bool == "ok" {
println!("Saving as: {}", name);
dest = fs::OpenOptions::new()
.create(true)
.append(true)
.open(&file)
.unwrap();
} else {
let filename: String;
if Path::new("video.mp4").exists() {
let mut count = 0;
loop {
if !Path::new(format!("video_{:02}.mp4", count).as_str()).exists() {
break;
}
count += 1;
}
filename = format!("video_{:02}.mp4", count);
} else {
filename = "video.mp4".to_string();
}
file = Path::new(filename.as_str());
dest = fs::OpenOptions::new()
.create(true)
.append(true)
.open(&file)
.unwrap();
println!(
"Error occurred, when using video's title. Saving as {}",
filename
);
}
let _ = copy(&mut source, &mut dest).unwrap();
}
fn get_html(url: &str, nokia: bool) -> String {
if nokia {
let user_agent = "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 635) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537";
let resp = ureq::request("GET", &url)
.set("User-Agent", user_agent)
.call();
resp.into_string().unwrap()
} else {
let resp = ureq::request("GET", &url).call();
resp.into_string().unwrap()
}
}
fn get_direct_url(html: &str, show_quality: &bool, quality: &str) -> String {
let parsed: Value = {
let re = Regex::new("var qualityItems_.* = (\\[.*\\}\\]);").unwrap();
let i = match re.captures(&html) {
Some(i) => i.get(1).unwrap().as_str(),
None => "error",
};
if i == "error" {
eprintln!("Download button is probably missing for this video");
process::exit(1);
}
serde_json::from_str(i).unwrap()
};
let parsed_len: usize = {
let mut counter = 0;
loop {
match parsed[counter]["id"] {
Value::String(_) => {counter += 1;},
_ => { break; },
};
}
counter
};
let mut qualities: Vec<String> = vec![
"".to_string(), "".to_string(), "".to_string(), "".to_string(), ];
let mut counter = 0;
while counter < parsed_len {
if parsed[counter]["id"] == "quality720p" {
qualities[1] = parsed[counter]["url"].to_string().replace("\"", "");
counter += 1;
continue;
}
if parsed[counter]["id"] == "quality480p" {
qualities[2] = parsed[counter]["url"].to_string().replace("\"", "");
counter += 1;
continue;
}
if parsed[counter]["id"] == "quality240p" {
qualities[3] = parsed[counter]["url"].to_string().replace("\"", "");
counter += 1;
continue;
}
counter += 1;
}
let re_1080p = Regex::new("\"quality\":\"720\"\\},\\{\"defaultQuality\":false,\"format\":\"mp4\",\"videoUrl\":\"(.*)\",\"quality\":\"1080\"\\}\\]").unwrap();
qualities[0] = match re_1080p.captures(html) {
Some(i) => i.get(1).unwrap().as_str().to_string().replace("\\/", "/"),
None => "".to_string(),
};
if *show_quality {
let mut available_qualities: Vec<String> = Vec::new();
if qualities[0] != "" {
available_qualities.push("1 - 1080p".to_string());
}
if qualities[1] != "" {
available_qualities.push("2 - 720p".to_string());
}
if qualities[2] != "" {
available_qualities.push("3 - 480p".to_string());
}
if qualities[3] != "" {
available_qualities.push("4 - 240p".to_string());
}
for quality in available_qualities {
println!("{}", quality);
}
}
if quality != "none" {
return if quality == "1" && qualities[0] != "" {
qualities[0].to_string()
}
else if quality == "2" && qualities[1] != "" {
qualities[1].to_string()
}
else if quality == "3" && qualities[2] != "" {
qualities[2].to_string()
}
else if quality == "4" && qualities[3] != "" {
qualities[3].to_string()
}
else {
eprintln!("Couldn't find selected quality");
"Error".to_string()
}
}
if html.contains("Error Page Not Found") {
eprintln!("Video is probably deleted from the ph");
process::exit(1);
}
return if qualities[0] != "" {
qualities[0].to_string()
}
else if qualities[1] != "" {
qualities[1].to_string()
}
else if qualities[2] != "" {
qualities[2].to_string()
}
else if qualities[3] != "" {
qualities[3].to_string()
}
else {
eprintln!("Couldn't find direct download link");
process::exit(1);
}
}