Initial commit
This commit is contained in:
commit
cd3630525e
|
@ -0,0 +1 @@
|
||||||
|
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "rss-anime-notifier-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["blank X <theblankx@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rss = "1.9.0"
|
||||||
|
warp = "0.2.5"
|
||||||
|
reqwest = "0.10.8"
|
||||||
|
chrono = "0.4.19"
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = "0.2.22", features = ["rt-core", "macros"] }
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 blank X
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,114 @@
|
||||||
|
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<impl warp::Reply, Infallible> {
|
||||||
|
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::<serde_json::Value>(&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<Item> = 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::<Utc>::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::<u16>().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;
|
||||||
|
}
|
Loading…
Reference in New Issue