extern crate serde; extern crate tokio; extern crate chrono; extern crate reqwest; extern crate serde_json; use std::env; use std::path::Path; use std::io::Cursor; use std::process::exit; use reqwest::Client; use serde_json::json; use quick_xml::Writer; use serde::Deserialize; use chrono::{Utc, DateTime, NaiveDateTime}; use quick_xml::events::{Event, BytesStart, BytesText, BytesEnd}; const QUERY: &str = r#"query ($id: Int) { Media (id: $id) { title { romaji english } airingSchedule { nodes { id airingAt timeUntilAiring episode } } episodes siteUrl } }"#; #[derive(Deserialize)] struct Root { pub data: Data } #[derive(Deserialize)] #[serde(rename_all="PascalCase")] struct Data { pub media: Media } #[derive(Deserialize)] #[serde(rename_all="camelCase")] struct Media { pub title: MediaTitle, pub airing_schedule: AiringScheduleConnection, pub episodes: Option, pub site_url: String } #[derive(Deserialize)] struct MediaTitle { pub romaji: String, pub english: Option } #[derive(Deserialize)] struct AiringScheduleConnection { pub nodes: Vec } #[derive(Deserialize)] #[serde(rename_all="camelCase")] struct AiringSchedule { pub id: i32, pub airing_at: i64, pub time_until_airing: i64, pub episode: i32 } async fn get_data(anime_id: i32) -> Root { let client = Client::new(); let json = json!({"query": QUERY, "variables": {"id": anime_id}}); serde_json::from_str(&client.post("https://graphql.anilist.co/") .header("Content-Type", "application/json") .header("Accept", "application/json") .body(json.to_string()) .send() .await .unwrap() .text() .await .unwrap()).unwrap() } fn main() { let mut args = env::args(); let path = args.next().expect("Cannot get binary path"); let path = Path::new(&path).file_stem().unwrap().to_str().unwrap(); let anime_id = match args.next() { Some(anime_id) => anime_id.parse::().unwrap(), None => { eprintln!("Usage: {} ", path); exit(1); } }; let resp = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(get_data(anime_id)); let mut pub_date = Utc::now().to_rfc2822(); let mut last_build_date = pub_date.clone(); let resp = &resp.data.media; let title = resp.title.english.as_ref().unwrap_or(&resp.title.romaji); let mut items: Vec<(i32, String, String)> = Vec::with_capacity(resp.airing_schedule.nodes.len()); for i in &resp.airing_schedule.nodes { let i_date = DateTime::::from_utc(NaiveDateTime::from_timestamp(i.airing_at, 0), Utc).to_rfc2822(); pub_date = i_date.clone(); if i.time_until_airing > 0 { break; } last_build_date = i_date.clone(); let mut title = format!("{} - {}", title, i.episode); if Some(i.episode) == resp.episodes { title.push_str(" END"); } items.push((i.id, i_date, title)); } items.reverse(); let mut writer = Writer::new(Cursor::new(Vec::new())); { let mut elem = BytesStart::owned(b"rss".to_vec(), 3); elem.push_attribute(("version", "2.0")); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesStart::owned(b"channel".to_vec(), 7); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesStart::owned(b"title".to_vec(), 5); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&title).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"title".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"link".to_vec(), 4); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&resp.site_url).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"link".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"description".to_vec(), 11); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&format!("Aired episodes of {}", &title)).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"description".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"pubDate".to_vec(), 7); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&pub_date).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"pubDate".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"lastBuildDate".to_vec(), 13); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&last_build_date).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"lastBuildDate".to_vec()); writer.write_event(Event::End(elem)).unwrap(); } for (id, date, title) in items { let elem = BytesStart::owned(b"item".to_vec(), 4); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesStart::owned(b"title".to_vec(), 5); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&title).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"title".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"link".to_vec(), 4); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&resp.site_url).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"link".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"guid".to_vec(), 4); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&format!("ran-{}-{}", &anime_id, id)).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"guid".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesStart::owned(b"pubDate".to_vec(), 7); writer.write_event(Event::Start(elem)).unwrap(); let elem = BytesText::from_plain_str(&date).into_owned(); writer.write_event(Event::Text(elem)).unwrap(); let elem = BytesEnd::owned(b"pubDate".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesEnd::owned(b"item".to_vec()); writer.write_event(Event::End(elem)).unwrap(); } let elem = BytesEnd::owned(b"channel".to_vec()); writer.write_event(Event::End(elem)).unwrap(); let elem = BytesEnd::owned(b"rss".to_vec()); writer.write_event(Event::End(elem)).unwrap(); println!("{}", String::from_utf8(writer.into_inner().into_inner()).unwrap()); }