extern crate rss; extern crate warp; extern crate tokio; extern crate chrono; extern crate reqwest; extern crate serde_json; use std::env; use std::convert::Infallible; use reqwest::Client; use serde_json::json; use warp::{Filter, http::response}; use chrono::{Utc, DateTime, NaiveDateTime}; use rss::{Item, ItemBuilder, ChannelBuilder, GuidBuilder}; const QUERY: &str = r#"query ($id: Int) { Media (id: $id) { title { romaji english } airingSchedule { nodes { id airingAt timeUntilAiring episode } } episodes siteUrl } }"#; async fn give_response(anime_id: usize) -> Result { let json = json!({"query": QUERY, "variables": {"id": anime_id}}); let client = Client::new(); let resp = match client.post("https://graphql.anilist.co/") .header("Content-Type", "application/json") .header("Accept", "application/json") .body(json.to_string()) .send() .await { Ok(resp) => match resp.text().await { Ok(resp) => match serde_json::from_str::(&resp) { Ok(resp) => resp, Err(err) => { eprintln!("ERROR: {}", err); return Ok(response::Builder::new().status(502).header("Content-Type", "type/plain").body(format!("502\n{}", err))); } }, Err(err) => { eprintln!("ERROR: {}", err); return Ok(response::Builder::new().status(502).header("Content-Type", "type/plain").body(format!("502\n{}", err))); } }, Err(err) => { eprintln!("ERROR: {}", err); return Ok(response::Builder::new().status(503).header("Content-Type", "type/plain").body(format!("503\n{}", err))); } }; let resp = &resp["data"]["Media"]; let title = resp["title"]["english"].as_str().unwrap_or(resp["title"]["romaji"].as_str().unwrap()); let mut items: Vec = Vec::new(); let mut pub_date = Utc::now().to_rfc2822(); let mut last_build_date = pub_date.clone(); for i in resp["airingSchedule"]["nodes"].as_array().unwrap() { let i_date = DateTime::::from_utc(NaiveDateTime::from_timestamp(i["airingAt"].as_i64().unwrap(), 0), Utc).to_rfc2822(); pub_date = i_date.clone(); if i["timeUntilAiring"].as_i64().unwrap() > 0 { break; } last_build_date = i_date.clone(); let mut title = format!("{} - {}", title, i["episode"].as_u64().unwrap()); if i["episode"].as_u64().unwrap() == resp["episodes"].as_u64().unwrap_or(0) { title.push_str(" END"); } items.push(ItemBuilder::default() .title(title) .link(resp["siteUrl"].as_str().unwrap().to_string()) .pub_date(i_date) .guid(GuidBuilder::default().permalink(false).value(format!("ran-{}-{}", anime_id, i["id"].as_u64().unwrap())).build().unwrap()) .build() .unwrap() ); } items.reverse(); let channel = ChannelBuilder::default() .title(title) .link(resp["siteUrl"].as_str().unwrap()) .description(format!("Aired episodes of {}", title)) .pub_date(pub_date) .last_build_date(last_build_date) .items(items) .build() .unwrap(); eprintln!("INFO: {} ({}) requested", anime_id, title); Ok(response::Builder::new() .status(200) .header("Content-Type", "application/rss+xml") .body(channel.to_string())) } #[tokio::main] async fn main() { let port = match env::var("PORT") { Ok(port) => port.parse::().unwrap(), Err(_) => 8080 }; let bare_path = warp::path!(usize).and_then(give_response); let routes = warp::get().and(bare_path); eprintln!("INFO: Listening on 127.0.0.1:{}", port); warp::serve(routes).run(([127, 0, 0, 1], port)).await; }