added anilist tracking

This commit is contained in:
Zastian Pretorius
2022-08-08 14:49:30 +01:00
parent f57e3f2df6
commit 62de278e8d
6 changed files with 246 additions and 16 deletions

24
Cargo.lock generated
View File

@@ -322,6 +322,7 @@ dependencies = [
"dirs", "dirs",
"isahc", "isahc",
"regex", "regex",
"serde_json",
"termsize", "termsize",
] ]
@@ -548,6 +549,12 @@ version = "0.6.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.20" version = "0.1.20"
@@ -558,6 +565,23 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "serde"
version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
[[package]]
name = "serde_json"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.6" version = "0.4.6"

View File

@@ -14,3 +14,4 @@ isahc = "1.7.2"
base64 = "0.13" base64 = "0.13"
termsize = "0.1.6" termsize = "0.1.6"
dirs = "4.0" dirs = "4.0"
serde_json = "1.0.83"

View File

@@ -1,10 +1,12 @@
use crate::main; use crate::main;
use crate::open_video; use crate::open_video;
use crate::{anime_ep_range, anime_link, anime_names}; use crate::{anime_ep_range, anime_link, anime_names};
use crate::{get_anime_id, get_token, get_user_anime_progress, update_anime_progress};
use crate::{int_input, string_input}; use crate::{int_input, string_input};
use colored::Colorize; use colored::Colorize;
//use crate //use crate
pub fn anime_stream(search: String, episode: u32) { pub fn anime_stream(search: String, episode: u32) {
let token = get_token();
let query = if search != "" { let query = if search != "" {
search search
} else { } else {
@@ -40,9 +42,11 @@ pub fn anime_stream(search: String, episode: u32) {
let title = &anime_list[anime_num]; let title = &anime_list[anime_num];
let ep_range = anime_ep_range(title); let ep_range = anime_ep_range(title);
// if there is only one episode, then don't ask user to choose episode // if there is only one episode, then don't ask user to choose episode
let id = get_anime_id(&title.replace("-", " "));
if ep_range == 1 { if ep_range == 1 {
let link = anime_link(title, 1); let link = anime_link(title, 1);
open_video(link); open_video(link);
update_anime_progress(id, &title.replace("-", " "), 1, &token);
main(); main();
} else { } else {
let mut ep_num: usize = usize::MAX; let mut ep_num: usize = usize::MAX;
@@ -52,6 +56,8 @@ pub fn anime_stream(search: String, episode: u32) {
} else if episode != 0 { } else if episode != 0 {
ep_num = episode as usize; ep_num = episode as usize;
} else { } else {
let current_progress = get_user_anime_progress(id, &token);
println!("you are currently on episode: {}", current_progress);
println!("select episode 1-{}: ", ep_range); println!("select episode 1-{}: ", ep_range);
while ep_num == usize::max_value() || ep_num > ep_range as usize { while ep_num == usize::max_value() || ep_num > ep_range as usize {
ep_num = int_input("Enter episode number: "); ep_num = int_input("Enter episode number: ");
@@ -63,6 +69,9 @@ pub fn anime_stream(search: String, episode: u32) {
loop { loop {
let link = anime_link(title, ep_num as u64); let link = anime_link(title, ep_num as u64);
open_video(link); open_video(link);
let id = get_anime_id(&title.replace("-", " "));
println!("{}", get_user_anime_progress(id, &token));
update_anime_progress(id, &title.replace("-", " "), ep_num, &token);
println!("{}", "n: next episode".green()); println!("{}", "n: next episode".green());
println!("{}", "p: previous episode".yellow()); println!("{}", "p: previous episode".yellow());
println!("{}", "s: search another anime".green()); println!("{}", "s: search another anime".green());

View File

@@ -1,3 +1,4 @@
pub mod anime;
pub mod player; pub mod player;
pub mod scraper; pub mod scraper;
pub mod anime; pub mod trackers;

183
src/anime/trackers.rs Normal file
View File

@@ -0,0 +1,183 @@
use crate::string_input;
use isahc::{ReadResponseExt, Request, RequestExt};
use serde_json::json;
use std::fs;
pub fn get_token() -> String {
//if not on windows create folder ~/.config/kami
let config_path = dirs::config_dir().unwrap().join("kami");
if !config_path.exists() {
fs::create_dir_all(&config_path).unwrap();
}
let token_path = config_path.join("token.txt");
if !token_path.exists() {
//create empty file
fs::File::create(&token_path).unwrap();
} else {
//read token from file
let token = fs::read_to_string(&token_path).unwrap();
if token.is_empty() {
println!("please go to the below link and copy and past the token below");
println!(
"https://anilist.co/api/v2/oauth/authorize?client_id=9121&response_type=token"
);
let token = string_input("token: ");
fs::write(&token_path, token).unwrap();
}
}
let token = fs::read_to_string(&token_path).unwrap();
token
}
pub fn get_anime_id(anime: &str) -> i32 {
const QUERY: &str = "
query ($id: Int, $search: String) {
Media (id: $id, search: $search, type: ANIME) {
id
title {
native
romaji
english
}
}
}
";
let json = json!({
"query": QUERY,
"variables": {
"search": anime,
}
});
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();
//println!("{}", resp);
let regex = regex::Regex::new(r#"id":(.*?),"#).unwrap();
let resp: String = resp.as_ref().unwrap().to_string();
let id = regex
.captures(&resp)
.unwrap()
.get(1)
.unwrap()
.as_str()
.parse::<i32>()
.unwrap();
id
}
//get the user id from the token
fn get_user_id(token: &str) -> i32 {
const QUERY: &str = "query {
Viewer {
id
}
}";
let json = json!({ "query": QUERY });
let resp = Request::builder()
.method("POST")
.uri("https://graphql.anilist.co/")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", format!("Bearer {}", token))
.body(json.to_string())
.unwrap()
.send()
.unwrap()
.text();
//println!("{}", resp);
let regex = regex::Regex::new(r#"id":(.*?)}"#).unwrap();
let resp: String = resp.as_ref().unwrap().to_string();
let id = regex
.captures(&resp)
.unwrap()
.get(1)
.unwrap()
.as_str()
.parse::<i32>()
.unwrap();
id
}
pub fn get_user_anime_progress(anime_id: i32, token: &str) -> i32 {
let user_id = get_user_id(&token);
const QUERY: &str = "query ($user_id: Int, $media_id: Int) {
MediaList (userId: $user_id, mediaId: $media_id, type: ANIME) {
progress
}
}";
let json = json!({
"query": QUERY,
"variables": {
"user_id": user_id,
"media_id": anime_id,
}
});
let resp = Request::builder()
.method("POST")
.uri("https://graphql.anilist.co/")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", format!("Bearer {}", token))
.body(json.to_string())
.unwrap()
.send()
.unwrap()
.text();
//println!("{}", resp);
let regex = regex::Regex::new(r#"progress":(.*?)}"#).unwrap();
let resp: String = resp.as_ref().unwrap().to_string();
//if resp contains "404"set progress to 1
// else set progress to the number in the regex
if resp.contains("404") {
1
} else {
let progress = regex
.captures(&resp)
.unwrap()
.get(1)
.unwrap()
.as_str()
.parse::<i32>()
.unwrap();
progress
}
}
pub fn update_anime_progress(anime_id: i32, anime: &str, progress: usize, token: &str) {
const UPDATE: &str = "
mutation ($mediaId: Int, $status: MediaListStatus, $progress: Int) {
SaveMediaListEntry (mediaId: $mediaId, status: $status, progress: $progress) {
id
status
progress
}
}
";
let json = json!({
"query": UPDATE,
"variables": {
"mediaId": anime_id,
"status": "CURRENT",
"progress": progress
}
});
let _resp = Request::builder()
.method("POST")
.uri("https://graphql.anilist.co/")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", format!("Bearer {}", token))
.body(json.to_string())
.unwrap()
.send()
.unwrap()
.text();
println!("updated progress of {} to episode {}", anime, progress);
}

View File

@@ -4,12 +4,13 @@ mod ln;
use anime::anime::anime_stream; use anime::anime::anime_stream;
use colored::Colorize; use colored::Colorize;
use ln::{scraper::get_ln_next_page, ln::ln_read};
use ln::search::search_ln; use ln::search::search_ln;
use ln::{ln::ln_read, scraper::get_ln_next_page};
use crate::anime::{ use crate::anime::{
player::open_video, player::open_video,
scraper::{anime_ep_range, anime_link, anime_names}, scraper::{anime_ep_range, anime_link, anime_names},
trackers::*,
}; };
use crate::helpers::take_input::{int_input, string_input}; use crate::helpers::take_input::{int_input, string_input};
use crate::ln::{menu::chapter_selector, open_text::open_bat, scraper::get_full_text}; use crate::ln::{menu::chapter_selector, open_text::open_bat, scraper::get_full_text};
@@ -35,7 +36,6 @@ fn main() {
search = arg; search = arg;
} }
} }
} }
if arg == "--ln" || arg == "-l" { if arg == "--ln" || arg == "-l" {
ln = true; ln = true;
@@ -74,7 +74,7 @@ fn main() {
match a { match a {
1 => anime = true, 1 => anime = true,
2 => ln = true, 2 => ln = true,
_=>println!("invalid option. ") _ => println!("invalid option. "),
}; };
} }
if anime == true && ln == true { if anime == true && ln == true {
@@ -96,22 +96,34 @@ fn page_selector(ln_id: &str, selected_page: u32) -> String {
fn print_help() { fn print_help() {
println!("anime:\t\t{}", format_args!("{}", "-a --anime".red())); println!("anime:\t\t{}", format_args!("{}", "-a --anime".red()));
println!("{}", "after this^^^ argument you can enter a search term".green()); println!(
"{}",
"after this^^^ argument you can enter a search term".green()
);
println!("{}", "for exaple kami -a \"one piece\""); println!("{}", "for exaple kami -a \"one piece\"");
//print blank line //print blank line
println!(""); println!("");
println!("episode:\t{}", format_args!("{}", "-e --episode".red())); println!("episode:\t{}", format_args!("{}", "-e --episode".red()));
println!("{}", "after this^^^ argument you can enter a chapter number".green()); println!(
"{}",
"after this^^^ argument you can enter a chapter number".green()
);
println!("{}", "for exaple kami -c 200"); println!("{}", "for exaple kami -c 200");
//print blank line //print blank line
println!(""); println!("");
println!("light novel:\t{}", format_args!("{}", "-l --ln".red())); println!("light novel:\t{}", format_args!("{}", "-l --ln".red()));
println!("{}", "after this^^^ argument you can enter a search term".green()); println!(
"{}",
"after this^^^ argument you can enter a search term".green()
);
println!("{}", "for exaple kami -l \"one piece\""); println!("{}", "for exaple kami -l \"one piece\"");
//print blank line //print blank line
println!(""); println!("");
println!("chapter:\t{}", format_args!("{}", "-c --chapter".red())); println!("chapter:\t{}", format_args!("{}", "-c --chapter".red()));
println!("{}", "after this^^^ argument you can enter a chapter number".green()); println!(
"{}",
"after this^^^ argument you can enter a chapter number".green()
);
println!("{}", "for exaple kami -c 200"); println!("{}", "for exaple kami -c 200");
//print blank line //print blank line
println!(""); println!("");