use super::Result; use crate::{ models, paseto, schema, web::{DiscordWebhook, Error as WebError, Mastodon, Result as WebResult, Twitter}, MainDatabase, }; use diesel::prelude::*; use rocket::State; use rocket_contrib::json::Json; use serde::Deserialize; use std::fmt::Write; #[derive(Deserialize)] pub struct Jsonfeed { pub version: String, pub home_page_url: String, pub items: Vec, } #[derive(Deserialize, Clone, Debug)] pub struct Item { pub url: String, pub title: String, pub tags: Option>, pub content_html: String, } impl Item { fn render(self) -> String { let mut result = String::new(); write!(result, "{}\n\n{}", self.title, self.url).unwrap(); if let Some(tags) = self.tags { write!(result, "\n\n").unwrap(); for tag in tags.iter() { write!(result, "#{} ", tag).unwrap(); } } result } } impl Into for Item { fn into(self) -> models::Blogpost { models::Blogpost { url: self.url, title: self.title, } } } pub fn read_jsonfeed(url: String) -> WebResult { let resp = ureq::get(&url) .set("User-Agent", crate::APPLICATION_NAME) .call(); if resp.ok() { Ok(resp.into_json_deserialize()?) } else { Err(match resp.synthetic_error() { Some(why) => WebError::UReq(why.to_string()), None => WebError::HttpStatus(resp.status()), }) } } #[instrument(skip(dw, tw, ma), err)] fn posse(item: Item, dw: &DiscordWebhook, tw: &Twitter, ma: &Mastodon) -> WebResult { let message = item.render(); dw.send(message.clone())?; tw.tweet(message.clone())?; ma.toot(message.clone())?; Ok(()) } pub static BLOG_FEED_URL: &'static str = "https://christine.website/blog.json"; #[post("/posse", format = "json", data = "")] #[instrument(skip(dw, tw, ma), err)] pub fn notify( item: Json, tok: paseto::Token, dw: State, tw: State, ma: State, ) -> Result { posse(item.into_inner(), &dw, &tw, &ma)?; Ok(()) } #[post("/blog/refresh")] #[instrument(skip(conn, dw, tw, ma), err)] pub fn refresh_blog( tok: paseto::Token, conn: MainDatabase, dw: State, tw: State, ma: State, ) -> Result { use schema::blogposts::dsl::blogposts; let feed = read_jsonfeed(BLOG_FEED_URL.to_string())?; for item in feed.items.into_iter() { match blogposts .find(item.url.clone()) .get_result::(&*conn) { Ok(_) => continue, Err(_) => { diesel::insert_into(schema::blogposts::table) .values(&{ let post: models::Blogpost = item.clone().into(); post }) .execute(&*conn)?; posse(item, &dw, &tw, &ma)? } } } Ok(()) } #[cfg(test)] mod tests { use super::{read_jsonfeed, BLOG_FEED_URL}; #[test] fn valid_jsonfeed() { read_jsonfeed(BLOG_FEED_URL.to_string()).unwrap(); } }