234 lines
7.3 KiB
Rust
234 lines
7.3 KiB
Rust
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<i32>,
|
|
pub site_url: String
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct MediaTitle {
|
|
pub romaji: String,
|
|
pub english: Option<String>
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct AiringScheduleConnection {
|
|
pub nodes: Vec<AiringSchedule>
|
|
}
|
|
|
|
#[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::<i32>().unwrap(),
|
|
None => {
|
|
eprintln!("Usage: {} <anime id>", 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::<Utc>::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());
|
|
}
|