initial version

This commit is contained in:
Robin Appelman 2021-01-07 20:40:24 +01:00
commit 51cba405ec
7 changed files with 1414 additions and 5 deletions

28
src/config.rs Normal file
View file

@ -0,0 +1,28 @@
use color_eyre::{eyre::WrapErr, Result};
use serde::{Deserialize, Deserializer};
use std::fs::read_to_string;
use tokio::time::Duration;
#[derive(Debug, Deserialize)]
pub struct Config {
interval: Option<u64>,
pub feed: Vec<FeedConfig>,
}
#[derive(Debug, Deserialize)]
pub struct FeedConfig {
pub feed: String,
pub hook: String,
}
impl Config {
pub fn from_file(path: &str) -> Result<Self> {
let file = read_to_string(path)
.wrap_err_with(|| format!("Failed to open config file {}", path))?;
toml::from_str(&file).wrap_err_with(|| format!("Failed to open config file {}", path))
}
pub fn interval(&self) -> Duration {
Duration::from_secs(self.interval.unwrap_or(30 * 60))
}
}

View file

@ -1,3 +1,103 @@
fn main() {
println!("Hello, world!");
mod config;
use crate::config::Config;
use color_eyre::{
eyre::{eyre, WrapErr},
Result,
};
use reqwest::Client;
use rss::Channel;
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use tokio::time::sleep;
#[tokio::main]
async fn main() -> Result<()> {
let mut args = std::env::args();
let bin = args.next().unwrap();
let file = match args.next() {
Some(file) => file,
None => {
eprintln!("Usage {} <config file>", bin);
return Ok(());
}
};
let config = Config::from_file(&file)?;
let mut fetcher = FeedFetcher::default();
loop {
for feed in config.feed.iter() {
match fetcher.is_feed_updated(&feed.feed).await {
Ok(true) => {
println!("Trigering hook for {}", feed.feed);
fetcher.client.post(&feed.hook).send().await?;
}
Err(e) => eprintln!("{:#}", e),
Ok(false) => {}
}
}
sleep(config.interval()).await;
}
}
#[derive(Default)]
pub struct FeedFetcher {
client: Client,
cache: HashMap<String, u64>,
}
impl FeedFetcher {
pub async fn is_feed_updated(&mut self, feed: &str) -> Result<bool> {
let new_key = self.get_feed_key(feed).await?;
Ok(match self.cache.get_mut(feed) {
Some(cached) => {
if *cached != new_key {
*cached = new_key;
true
} else {
false
}
}
None => {
self.cache.insert(feed.into(), new_key);
// dont trigger the actions on start
false
}
})
}
async fn get_feed_key(&self, feed: &str) -> Result<u64> {
let content = self
.client
.get(feed)
.send()
.await
.wrap_err_with(|| eyre!("Failed to load feed {}", feed))?
.bytes()
.await
.wrap_err_with(|| eyre!("Failed to load feed {}", feed))?;
let channel = Channel::read_from(content.as_ref())
.wrap_err_with(|| eyre!("Failed to parse feed {}", feed))?;
let item = channel.items.first().ok_or(eyre!("Empty feed"))?;
let mut hasher = DefaultHasher::new();
if let Some(guid) = item.guid() {
guid.value.hash(&mut hasher);
} else if let Some(date) = item.pub_date() {
date.hash(&mut hasher);
} else if let Some(link) = item.link() {
link.hash(&mut hasher);
} else {
return Err(eyre!("No guid, pubDate or link set on feed item"));
}
Ok(hasher.finish())
}
}