diff --git a/src/anime/player.rs b/src/anime/player.rs index 665d777..0a3b6ce 100644 --- a/src/anime/player.rs +++ b/src/anime/player.rs @@ -8,16 +8,28 @@ use rust_cast::{ }; use std::str::FromStr; -pub fn open_video(link: (String, String)) { - let title = link.1; - let title = title.replace("-", " "); - let arg: String = format!("--force-media-title={}", title); - let _ = std::process::Command::new("mpv") - .arg(link.0) - .arg(arg) - .output() - .expect("failed to open mpv"); - +pub fn open_video(link: (String, String, String)) { + if link.2 == "null" { + let title = link.1; + let title = title.replace("-", " "); + let arg: String = format!("--force-media-title={}", title); + let _ = std::process::Command::new("mpv") + .arg(link.0) + .arg(arg) + .output() + .expect("failed to open mpv"); + } else { + let title = link.1; + let title = title.replace("-", " "); + let arg1: String = format!("--force-media-title={}", title); + let arg2: String = format!("--sub-files={}", link.2); + let _ = std::process::Command::new("mpv") + .arg(link.0) + .arg(arg1) + .arg(arg2) + .output() + .expect("failed to open mpv"); + } // clear terminal } diff --git a/src/anime/scraper.rs b/src/anime/scraper.rs index 2f43f8d..6cc4577 100644 --- a/src/anime/scraper.rs +++ b/src/anime/scraper.rs @@ -1,14 +1,21 @@ use isahc::config::Configurable; use isahc::{ReadResponseExt, Request, RequestExt}; -use regex::Regex; use std::fs::File; use std::io::prelude::*; //use serde_json::json; -pub fn get_anime_html(url: &str) -> String { +pub fn search_anime(query: String) -> (Vec, Vec, Vec) { let req = Request::builder() - .uri(url) + .uri(format!( + "https://api.consumet.org/meta/anilist/{}", + query + .replace(" ", "%20") + .replace(":", "%3A") + .replace("!", "%21") + .replace("?", "%3F") + .replace("'", "%27") + )) .redirect_policy(isahc::config::RedirectPolicy::Follow) .header( "user-agent", @@ -16,74 +23,95 @@ pub fn get_anime_html(url: &str) -> String { ) .body(()) .unwrap(); - req.send().unwrap().text().unwrap() -} + let json = req.send().unwrap().text().unwrap(); + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + let mut titles = Vec::new(); + let mut ids = Vec::new(); + let mut images = Vec::new(); + for i in 0..json["results"].as_array().unwrap().len() { + titles.push( + json["results"][i]["title"]["userPreferred"] + .as_str() + .unwrap() + .to_string(), + ); -pub fn get_post(id: &str) -> String { - let resp = Request::builder() - .method("POST") - .uri("https://yugen.to/api/embed/") - .header("x-requested-with", "XMLHttpRequest") - .body(id) - .unwrap() - .send() - .unwrap() - .text(); - let resp: String = resp.as_ref().unwrap().to_string(); - resp -} - -pub fn get_animes(query: String) -> (Vec, Vec, Vec) { - let query = query.replace(" ", "+"); - let html = get_anime_html(&format!("https://yugen.to/search/?q={}", query)); - let re = Regex::new(r#"href="(/anime[^"]*)""#).unwrap(); - let mut animes_links = Vec::new(); - for cap in re.captures_iter(&html) { - animes_links.push(cap[1].to_string()); + ids.push(json["results"][i]["id"].as_str().unwrap().to_string()); + //convert ids to i32 + images.push(json["results"][i]["image"].as_str().unwrap().to_string()); } - let re = Regex::new(r#"/" title="([^"]*)""#).unwrap(); - let mut animes_names = Vec::new(); - for cap in re.captures_iter(&html) { - animes_names.push(cap[1].to_string()); - } - let re = Regex::new(r#"data-src="([^"]*)"#).unwrap(); - let mut animes_images = Vec::new(); - for cap in re.captures_iter(&html) { - animes_images.push(cap[1].to_string()); - } - (animes_links, animes_names, animes_images) + (ids, titles, images) } -pub fn get_anime_info(url: &str) -> (i32, u16) { - let url = format!("https://yugen.to{}watch", url); - let html = get_anime_html(&url); - //print html and exit - let re = Regex::new(r#""mal_id":(\d*)"#).unwrap(); - let mal_id = re.captures(&html).unwrap()[1].parse().unwrap(); - let re = - Regex::new(r#"Episodes(\d*)"#) - .unwrap(); - let episodes = re.captures(&html).unwrap()[1].parse().unwrap(); - (mal_id, episodes) +pub fn get_episodes(id: &i32, provider: &str) -> (Vec, Vec) { + let req = Request::builder() + .uri(format!( + "https://api.consumet.org/meta/anilist/info/{}?provider={}", + id, provider + )) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header( + "user-agent", + "Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/100.0", + ) + .body(()) + .unwrap(); + let json = req.send().unwrap().text().unwrap(); + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + let mut titles = Vec::new(); + let mut ids = Vec::new(); + for i in 0..json["episodes"].as_array().unwrap().len() { + titles.push(json["episodes"][i]["title"].as_str().unwrap().to_string()); + ids.push(json["episodes"][i]["id"].as_str().unwrap().to_string()); + } + (titles, ids) } -pub fn get_anime_link(url: &str, episode: u64) -> String { - let url = &format!( - "https://yugen.to/watch{}{}/", - url.replace("/anime", ""), - episode - ); - let html = get_anime_html(url); - let re = Regex::new(r#"/e/([^/]*)"#).unwrap(); - let capture = re.captures(&html).unwrap(); - let id = &capture[1]; - let id = format!("id={}%3D&ac=0", id); - let json = get_post(&id); - let re = Regex::new(r#"hls": \["(.*)","#).unwrap(); - let capture = re.captures(&json).unwrap(); - let link = &capture[1]; - //return the link - link.to_string() +pub fn get_episode_link(ep_id: &str, provider: &str) -> (String, String) { + let req = Request::builder() + .uri(format!( + "https://api.consumet.org/meta/anilist/watch/{}?provider={}", + ep_id, provider + )) + .redirect_policy(isahc::config::RedirectPolicy::Follow) + .header( + "user-agent", + "Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/100.0", + ) + .body(()) + .unwrap(); + let json = req.send().unwrap().text().unwrap(); + let json: serde_json::Value = serde_json::from_str(&json).unwrap(); + let mut url = String::new(); + std::fs::write("test.json", json.to_string()).unwrap(); + let mut subtitle = String::new(); + let _error_vec = Vec::new(); + let sub_array = json["subtitles"].as_array().unwrap_or(&_error_vec); + for i in 0..sub_array.len() { + //set subtitle to lang = English + if json["subtitles"][i]["lang"].as_str().unwrap_or("null") == "English" { + subtitle = json["subtitles"][i]["url"] + .as_str() + .unwrap_or("null") + .to_string(); + // add \ before the first : in the url + subtitle = subtitle.replace(":", "\\:"); + } + } + let mut highest_quality = 0; + for i in 0..json["sources"].as_array().unwrap().len() { + let quality = json["sources"][i]["quality"] + .as_str() + .unwrap() + .replace("p", "") + .parse::() + .unwrap_or(0); + if quality > highest_quality { + highest_quality = quality; + url = json["sources"][i]["url"].as_str().unwrap().to_string(); + } + } + (url.to_string(), subtitle) } pub fn get_image(url: &str, path: &str) { diff --git a/src/anime/trackers.rs b/src/anime/trackers.rs index 15f8577..33eb954 100644 --- a/src/anime/trackers.rs +++ b/src/anime/trackers.rs @@ -40,54 +40,6 @@ pub fn get_token() -> String { token } -pub fn get_anime_id(mal_id: i32) -> i32 { - const QUERY: &str = " -query ($id: Int, $search: Int) { - Media (id: $id, idMal: $search, type: ANIME) { - id - title { - native - romaji - english - } - } -} -"; - let json = json!({ - "query": QUERY, - "variables": { - "search": mal_id - } - }); - let resp = Request::builder() - .method("POST") - .uri("https://graphql.anilist.co/") - .header("Content-Type", "application/json") - .header("Accept", "application/json") - .body(json.to_string()) - .unwrap() - .send() - .unwrap() - .text(); - let regex = regex::Regex::new(r#"id":(.*?),"#).unwrap(); - let resp: String = resp.as_ref().unwrap().to_string(); - //if error let id = 0 - let id = match regex.captures(&resp) { - Some(captures) => captures[1].parse::().unwrap(), - None => 0, - }; - - // let id = regex - // .captures(&resp) - // .unwrap() - // .get(1) - // .unwrap() - // .as_str() - // .parse::() - // .unwrap(); - id -} - //get the user id from the token fn get_user_id(token: &str) -> i32 { const QUERY: &str = "query { diff --git a/src/main.rs b/src/main.rs index 15ab1ff..43959e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,14 +29,14 @@ fn main() { if let Some(arg) = std::env::args().nth(count + 1) { //get the next argument and see if it is = to gogo of vrv match arg.as_str() { - "vrv" | "gogo" => { + "zoro" | "gogoanime" => { provider = arg; count += 1; } - &_ => provider = "gogo".to_string(), + &_ => provider = "gogoanime".to_string(), } } else { - provider = "vrv".to_string(); + provider = "zoro".to_string(); } } "--reader" | "-R" => { @@ -49,9 +49,14 @@ fn main() { } &_ => reader = "bat".to_string(), } + } else { provider = "glow".to_string(); } + + } else { + provider = "gogo".to_string(); + } "--cast" | "-C" => { if let Some(arg) = std::env::args().nth(count + 1) { @@ -132,13 +137,13 @@ fn print_help() { ); println!( "if no provider is entered it will default to {}", - "vrv".green() + "gogo".green() ); println!( "if the -r argument is not used it will default to {}", - "gogo".green() + "zoro".green() ); - println!("the providers are {} or {}", "gogo".green(), "vrv".green()); + println!("the providers are {} or {}", "gogoanime".green(), "zoro".green()); println!(""); println!("reader:\t\t{}", format_args!("{}", "-R --reader".red())); println!( diff --git a/src/ui/app/anime.rs b/src/ui/app/anime.rs index 4a4dc7d..9329a85 100644 --- a/src/ui/app/anime.rs +++ b/src/ui/app/anime.rs @@ -1,9 +1,9 @@ use crate::ui::{app::app::KamiApp, input::InputMode, list::StatefulList}; use crate::anime::player::{open_cast, open_video}; -use crate::anime::scraper::{get_anime_info, get_anime_link, get_animes, get_image}; +use crate::anime::scraper::{get_episode_link, get_episodes, get_image, search_anime}; use crate::anime::trackers::{ - get_an_history, get_an_progress, get_anime_id, get_user_anime_progress, update_anime_progress, + get_an_history, get_an_progress, get_user_anime_progress, update_anime_progress, write_an_progress, }; @@ -29,6 +29,7 @@ pub struct App { input_mode: InputMode, /// History of recorded messages messages: StatefulList, + episodes: (Vec, Vec), title: String, link: String, ep: u64, @@ -39,6 +40,39 @@ pub struct App { pub cast: (bool, String), } +impl<'a> App { + fn change_image(&mut self) { + //save as f32 + let (width, height) = terminal_size().to_owned(); + let width = width as f32; + let height = height as f32; + let sixel_support = viuer::is_sixel_supported(); + let config = match sixel_support { + true => Config { + x: ((width / 2.0) + 1.0).round() as u16, + y: 2, + width: Some((width / 1.3).round() as u32), + height: Some((height * 1.5) as u32), + restore_cursor: true, + ..Default::default() + }, + false => Config { + x: ((width / 2.0) + 1.0).round() as u16, + y: 2, + width: Some(((width / 2.0) - 4.0).round() as u32), + height: Some((height / 1.3).round() as u32), + restore_cursor: true, + ..Default::default() + }, + }; + + let config_path = dirs::config_dir().unwrap().join("kami"); + let image_path = config_path.join("tmp.jpg"); + get_image(&self.image, &image_path.to_str().unwrap()); + print_from_file(image_path, &config).expect("Image printing failed."); + } +} + impl<'a> KamiApp for App { fn new() -> Self { App { @@ -47,6 +81,7 @@ impl<'a> KamiApp for App { image: String::new(), input_mode: InputMode::Normal, messages: StatefulList::with_items(Vec::new()), + episodes: (Vec::new(), Vec::new()), title: String::new(), link: String::new(), ep: 0, @@ -60,36 +95,6 @@ impl<'a> KamiApp for App { fn run(&mut self, terminal: &mut Terminal) -> io::Result<()> { let mut ep_select = false; - fn change_image(app: &App) { - //save as f32 - let (width, height) = terminal_size().to_owned(); - let width = width as f32; - let height = height as f32; - let sixel_support = viuer::is_sixel_supported(); - let config = match sixel_support { - true => Config { - x: ((width / 2.0) + 1.0).round() as u16, - y: 2, - width: Some((width / 1.3).round() as u32), - height: Some((height * 1.5) as u32), - restore_cursor: true, - ..Default::default() - }, - false => Config { - x: ((width / 2.0) + 1.0).round() as u16, - y: 2, - width: Some(((width / 2.0) - 4.0).round() as u32), - height: Some((height / 1.3).round() as u32), - restore_cursor: true, - ..Default::default() - }, - }; - - let config_path = dirs::config_dir().unwrap().join("kami"); - let image_path = config_path.join("tmp.jpg"); - get_image(&app.image, &image_path.to_str().unwrap()); - print_from_file(image_path, &config).expect("Image printing failed."); - } self.messages.items.clear(); for anime in &self.animes.1 { self.messages.push(anime.to_string()); @@ -110,8 +115,6 @@ impl<'a> KamiApp for App { } KeyCode::Left => self.messages.unselect(), KeyCode::Char('h') => self.messages.unselect(), - KeyCode::Char('g') => self.messages.begin(), - KeyCode::Char('G') => self.messages.end(), KeyCode::Down => match ep_select { true => { self.messages.next(); @@ -120,7 +123,7 @@ impl<'a> KamiApp for App { self.messages.next(); let selected = self.messages.state.selected(); self.image = self.animes.2[selected.unwrap()].clone(); - change_image(&self); + self.change_image(); } }, KeyCode::Char('j') => match ep_select { @@ -131,7 +134,7 @@ impl<'a> KamiApp for App { self.messages.next(); let selected = self.messages.state.selected(); self.image = self.animes.2[selected.unwrap()].clone(); - change_image(&self); + self.change_image(); } }, KeyCode::Up => match ep_select { @@ -142,7 +145,7 @@ impl<'a> KamiApp for App { self.messages.previous(); let selected = self.messages.state.selected(); self.image = self.animes.2[selected.unwrap()].clone(); - change_image(&self); + self.change_image(); } }, KeyCode::Char('k') => match ep_select { @@ -153,7 +156,7 @@ impl<'a> KamiApp for App { self.messages.previous(); let selected = self.messages.state.selected(); self.image = self.animes.2[selected.unwrap()].clone(); - change_image(&self); + self.change_image(); } }, //if KeyCode::Enter => { @@ -162,9 +165,14 @@ impl<'a> KamiApp for App { self.progress = 0; let selected = self.messages.state.selected(); self.title = self.messages.items[selected.unwrap()].clone(); - self.link = self.animes.0[selected.unwrap()].clone(); - let anime_info = get_anime_info(&self.animes.0[selected.unwrap()]); - self.anime_id = get_anime_id(anime_info.0); + self.anime_id = self.animes.0[selected.unwrap()] + .clone() + .parse::() + .unwrap(); + self.episodes = get_episodes( + &self.animes.0[selected.unwrap()].parse::().unwrap(), + &self.provider, + ); self.messages.items.clear(); if self.token == "local" || self.anime_id == 0 { self.progress = get_an_progress(&self.title) as i32; @@ -174,13 +182,18 @@ impl<'a> KamiApp for App { get_user_anime_progress(self.anime_id, self.token.as_str()); self.messages.state.select(Some(self.progress as usize)); } - if anime_info.1 == 1 { - let link = get_anime_link(&self.link, 1); + if self.episodes.0.len() == 1 { + let link = + get_episode_link(&self.episodes.1[0], &self.provider); if !self.cast.0 { - open_video((link, format!("{} Episode 1", &self.title))); + open_video(( + link.0, + format!("{} Episode 1", &self.title), + link.1, + )); } else { open_cast( - (link, format!("{} Episode 1", &self.title)), + (link.1, format!("{} Episode 1", &self.title)), &self.cast.1, ) } @@ -188,7 +201,7 @@ impl<'a> KamiApp for App { let image_url = self.animes.2[selected.unwrap()].clone(); if self.token == "local" || self.anime_id == 0 { write_an_progress( - (&self.title, &self.link, &image_url), + (&self.title, &self.anime_id.to_string(), &image_url), &1, ); } else { @@ -198,13 +211,17 @@ impl<'a> KamiApp for App { self.token.as_str(), ); write_an_progress( - (&self.title, &self.link, &image_url), + (&self.title, &self.anime_id.to_string(), &image_url), &1, ); } } else { - for ep in 1..anime_info.1 + 1 { - self.messages.push(format!("Episode {}", ep)); + for ep in 1..self.episodes.1.len() + 1 { + self.messages.push(format!( + "Episode {}: {}", + ep, + self.episodes.0[ep - 1] + )); } ep_select = true; } @@ -216,17 +233,23 @@ impl<'a> KamiApp for App { .nth(selected.unwrap()) .unwrap() .replace("Episode ", "") + .split(":") + .collect::>()[0] .parse::() .unwrap(); - let link = get_anime_link(&self.link, self.ep); + let link = get_episode_link( + &self.episodes.1[self.ep as usize - 1], + &self.provider, + ); if !self.cast.0 { open_video(( - link, + link.0, format!("{} Episode {}", &self.title, self.ep), + link.1, )); } else { open_cast( - (link, format!("{} Episode {}", &self.title, self.ep)), + (link.0, format!("{} Episode {}", &self.title, self.ep)), &self.cast.1, ) } @@ -234,7 +257,7 @@ impl<'a> KamiApp for App { if self.ep > self.progress as u64 { if self.token == "local" || self.anime_id == 0 { write_an_progress( - (&self.title, &self.link, &image_url), + (&self.title, &self.anime_id.to_string(), &image_url), &self.ep, ); } else { @@ -244,7 +267,7 @@ impl<'a> KamiApp for App { self.token.as_str(), ); write_an_progress( - (&self.title, &self.link, &image_url), + (&self.title, &self.anime_id.to_string(), &image_url), &self.ep, ); } @@ -257,7 +280,7 @@ impl<'a> KamiApp for App { InputMode::Editing => match key.code { KeyCode::Enter => { //push self.input into self.messages with ' - self.animes = get_animes(self.input.drain(..).collect()); + self.animes = search_anime(self.input.drain(..).collect()); self.messages.items.clear(); for anime in &self.animes.1 { self.messages.push(anime.to_string());