ph-dl 1.1.2

Get highest quality direct link to download PH videos, or download them automatically.
Documentation
use indicatif::{ProgressBar, ProgressStyle};
use regex::Regex;
use reqwest::blocking::Client;
use reqwest::header;
use reqwest::Url;
use std::fs;
use std::io;
use std::io::{copy, Read};
use std::path::Path;
use std::process;

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) {
    let is_video = Regex::new("playlist").unwrap();

    if !is_video.is_match(&url) {
        find_video(url, download);
    } else {
        // It's playlist
        let content = get_html(&url, false);
        let re_not_found = Regex::new("Error Page Not Found").unwrap();
        if re_not_found.is_match(&content) {
            eprintln!("Playlist is private, cannot access it. Try making it public");
            process::exit(1);
        }
        find_playlist_videos(url, download, content);
    }
}

fn find_video(url: String, download: bool) {
    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("&amp;", "&").replace("&#039;", "'")
    };

    let url = get_direct_url(&content);

    println!("Direct URL: {}", &url);
    if download {
        download_video(&url, &title);
        println!("Saved");
    }
}

fn find_playlist_videos(playlist_url: String, download: bool, content: String) {
    let re_urls = Regex::new(r#"<a href="/view_video\.php\?viewkey=(ph[0-9a-รถ]+)&pkey=161041812" title=".*".*class="fade"#).unwrap();

    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);
    }
}

fn download_video(url: &String, name: &String) {
    // Get file size in bytes
    let total_size: u64 = {
        let resp = ureq::head(url).call();
        resp.header("content-length").unwrap().parse().unwrap()
    };

    let name = format!("{}.mp4", name);
    println!("Saving as: {}", 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 file = Path::new(&name);

    if file.exists() {
        let size = file.metadata().unwrap().len() - 1;
        request = request.header(header::RANGE, format!("bytes={}-", size));
        pb.inc(size);
    }

    let mut source = DownloadProgress {
        progress_bar: pb,
        inner: request.send().unwrap(),
    };

    let mut dest = fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&file)
        .unwrap();

    let _ = copy(&mut source, &mut dest).unwrap();
}

fn get_html(url: &String, 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();
        let result = resp.into_string().unwrap();
        return result;
    } else {
        let resp = ureq::request("GET", &url).call();
        let result = resp.into_string().unwrap();
        return result;
    }
}

fn get_direct_url(html: &String) -> String {
    let re_not_found = Regex::new("Error Page Not Found").unwrap();
    let re_1080p = Regex::new("\"quality_1080p\":\"(.*)\",\"quality_720p\"").unwrap();
    let re_720p = Regex::new("\"quality_720p\":\"(.*)\",\"quality_240p\"").unwrap();
    let re_480p = Regex::new("\"quality_480p\":\"(.*)\",\"media").unwrap();
    let re_240p = Regex::new("\"quality_240p\":\"(.*)\",\"quality_480p\"").unwrap();

    // Check that video exists
    if re_not_found.is_match(&html) {
        eprintln!("Video is probably deleted from the ph");
        process::exit(1);
    }

    // Find highest quality link
    let mut result = match re_1080p.captures(&html) {
        Some(i) => i.get(1).unwrap().as_str().to_string().replace("\\/", "/"),
        None => "Error".to_string(),
    };
    if result == "Error" {
        result = match re_720p.captures(&html) {
            Some(i) => i.get(1).unwrap().as_str().to_string().replace("\\/", "/"),
            None => "Error".to_string(),
        };
    }
    if result == "Error" {
        result = match re_480p.captures(&html) {
            Some(i) => i.get(1).unwrap().as_str().to_string().replace("\\/", "/"),
            None => "Error".to_string(),
        };
    }
    if result == "Error" {
        result = match re_240p.captures(&html) {
            Some(i) => i.get(1).unwrap().as_str().to_string().replace("\\/", "/"),
            None => "Error".to_string(),
        };
    }

    // If still error, throw error
    if result == "Error" {
        eprintln!("Couldn't find direct download link");
        process::exit(1);
    }
    result
}