aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorJakob Stendahl <jakob.stendahl@outlook.com>2022-02-14 21:19:14 +0100
committerJakob Stendahl <jakob.stendahl@outlook.com>2022-02-14 21:19:14 +0100
commit78950762a70bffb91255f1c1ce2594a071f0fa84 (patch)
tree3fc173f383a69f5dc544caab091869d0984625d6 /src/main.rs
parentfa01748d66089b8b2c33db3973f14b678361f739 (diff)
downloadRSS-watcher-0.3.0.tar.gz
RSS-watcher-0.3.0.zip
:sparkles: Add many more fields that can be used, improve documentation and improve file structurev0.3.0
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs154
1 files changed, 14 insertions, 140 deletions
diff --git a/src/main.rs b/src/main.rs
index 02b386f..d4f7f1e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,126 +1,16 @@
+mod rss_utils;
+mod notify;
mod database;
use database::FeedConf;
use std::env;
use std::process;
-use std::error::Error;
-use feed_rs::parser;
-use feed_rs::model::Feed;
-use feed_rs::model::Entry;
-use feed_rs::model::Text;
-use chrono::prelude::{Utc,DateTime,NaiveDateTime};
-use std::time::Duration;
-use tokio::{time};
use log::{debug, info, warn, error};
-use html2md;
-extern crate mime;
-
-/**
- * Extract text field from Option
- */
-fn extract_text(text: &Option<Text>) -> String {
- if text.is_none() { return String::from("Text field not found"); }
- let field = text.as_ref().unwrap();
- match (field.content_type.type_(), field.content_type.subtype()) {
- (mime::TEXT, mime::HTML) => return html2md::parse_html(field.content.as_ref()),
- (mime::TEXT, mime::PLAIN) => return field.content.to_owned(),
- _ => return String::from(format!("Unknown field content type {:#?}", field.content_type)),
- }
-}
-
-/**
- * This will extract fields from RSS entry, and replace special tags
- * from the input string with those entries.
- */
-fn replace_tags(input: String, entry: &Entry) -> String {
- let mut out = input;
- out = out.replace("{{id}}", entry.id.as_ref());
- out = out.replace("{{title}}", extract_text(&entry.title).as_ref());
- out = out.replace("{{summary}}", extract_text(&entry.summary).as_ref());
- return out;
-}
-
-/**
- * Method that escapes some characters that would break json spec, and also escape
- * special HTML characters.
- */
-fn escape(input: String) -> String {
- return input.replace("\\","\\\\")
- .replace("\"", "\\\"")
- .replace("\n", "\\n")
- .replace("<", "&lt;")
- .replace(">", "&gt;")
- .replace("&", "$amp;");
-}
-
-/**
- * Push feed entry to gotify
- */
-async fn gotify_push(entry: &Entry, feed_conf: &FeedConf) -> Result<(), reqwest::Error> {
- let uri = format!("{}/message", &feed_conf.push_url);
-
- // Extract content and create title and message strings
- let mut title_content = feed_conf.title.to_owned();
- let mut message_content = feed_conf.message.to_owned();
- title_content = replace_tags(title_content, entry);
- message_content = replace_tags(message_content, entry);
- // Build json string that will be sent as payload to gotify
- let mut req = "{".to_owned();
-
- req.push_str(format!("\"title\":\"{}\"", escape(title_content.to_owned())).as_str());
- req.push_str(format!(",\"message\":\"{}\"", escape(message_content.to_owned())).as_str());
- req.push_str(",\"priority\":1");
-
- req.push_str(",\"extras\": {");
- req.push_str("\"client::display\": { \"contentType\": \"text/markdown\" }");
- if entry.links.len() > 0 {
- req.push_str(",\"client::notification\": { \"click\": { \"url\": \"");
- req.push_str(escape(entry.links[0].href.to_owned()).as_str());
- req.push_str("\"}}")
- }
- req.push_str("}}");
-
- // Send request to gotify
- let client = reqwest::Client::new();
- let res = client.post(uri)
- .query(&[("token",&feed_conf.push_token)])
- .body(req.to_owned())
- .header("Content-Type", "application/json")
- .send()
- .await?;
- if res.status().is_success() {
- info!("Sent notification with title \"{}\"", title_content);
- } else {
- error!("payload: {}", req);
- error!("Could not send notification... {:#?}", res);
- }
- Ok(())
-}
-
-/**
- * Function takes a FeedConf struct, and makes a get request to fetch
- * the feed. It then uses feed_rs to parse that feed and returns that
- * parsed feed.
- */
-async fn fetch_feed(feed_conf: &FeedConf, last_fetch_time: DateTime<Utc>) -> Result<Option<Feed>, Box<dyn Error>> {
- info!("Fetching feed \"{}\"", &feed_conf.url);
- let client = reqwest::Client::new();
- let last_fetch_rfc2822 = last_fetch_time.to_rfc2822().replace("+0000", "GMT");
- debug!("Using header \"If-Modified-Since {:?}\"", &last_fetch_rfc2822);
- let resp = client.get(&feed_conf.url)
- .header("If-Modified-Since", &last_fetch_rfc2822)
- .send()
- .await?;
- if resp.status() == 304 {
- info!("No changes since last fetch at {}", &last_fetch_rfc2822);
- Ok(None)
- } else {
- let feed = parser::parse(&resp.bytes().await?[..])?;
- debug!("{:#?}", feed);
- Ok(Some(feed))
- }
-}
+use chrono::prelude::{Utc,DateTime,NaiveDateTime};
+use tokio::{time};
+use std::time::Duration;
+use feed_rs::model::Feed;
/**
* This calls fetch_feed, and figures out wether it succeeded or not.
@@ -139,39 +29,24 @@ async fn get_feed(feed_conf: &FeedConf) -> bool {
debug!("Using last_fetch_time {:?}", last_fetch_time.to_owned());
// Fetch the feed and parse it
- let res = fetch_feed(&feed_conf, last_fetch_time).await;
- let feed: Option<Feed>;
+ let res = rss_utils::fetch_feed(&feed_conf, last_fetch_time).await;
+ let feed_res: Option<Feed>;
match res {
Err(e) => {
error!("Could not fetch feed ({:?})", e);
return false;
},
- Ok(x) => feed = x
+ Ok(x) => feed_res = x
}
// If feed is empty (we got status code 304), we should skip any further
// processing
- if let None = feed { return false; }
+ if let None = feed_res { return false; }
+ let feed = feed_res.unwrap();
// Process all entries in the feed
- for entry in feed.unwrap().entries {
- // Skip sending notification if the publish time is before the
- // last_fetch_time
- if let Some(x) = entry.published {
- if last_fetch_time > x {
- info!("Skipping entry that was published at {}", x);
- continue;
- }
- }
- // Attempt to send notification, give up feed for this main loop
- // iteration without saving last_fetch_time
- if let Err(e) = gotify_push(&entry, &feed_conf).await {
- error!("Could not send push notification ({:#?})", e);
- return false;
- }
- }
-
- return true;
+ let res_notif = notify::all(&feed, &feed_conf, last_fetch_time).await;
+ return res_notif;
}
/**
@@ -199,8 +74,7 @@ async fn main_loop() {
for feed in feeds {
let time_now = Utc::now();
- let res = get_feed(&feed).await;
- if res {
+ if get_feed(&feed).await {
database::update_last_fetch(feed.id, time_now.timestamp(), &mut conn);
}
}