Skip to main content

irssc/
rss.rs

1use std::collections::HashSet;
2use std::sync::LazyLock;
3use std::{sync::Arc, time::Duration};
4
5use chrono::Utc;
6use feed_rs::model::Feed;
7use irc::client::Client;
8use log::{debug, error, info, trace};
9use tokio::sync::RwLock;
10
11use miette::{IntoDiagnostic, WrapErr, miette};
12
13use crate::bot::Subscriptions;
14use crate::{APP_USER_AGENT, FEED_REFRESH_INTERVAL_SECONDS};
15
16/// Infinite loop tempered by an interval. runs [`update_feeds`] periodically
17pub(crate) async fn feed_watcher(subs: Arc<RwLock<Subscriptions>>, client: Arc<Client>) {
18    let mut feed_interval = tokio::time::interval(tokio::time::Duration::from_secs(
19        FEED_REFRESH_INTERVAL_SECONDS as u64,
20    ));
21
22    loop {
23        feed_interval.tick().await;
24        // Check if a reasonable lapse of time has occured since last check. Won't do anygood
25        if Utc::now()
26            < subs.read().await.last_check
27                + Duration::from_secs(FEED_REFRESH_INTERVAL_SECONDS as u64 / 2)
28        {
29            continue;
30        }
31        if let Err(e) = update_feeds(subs.clone(), &client).await {
32            error!("{:?}", e);
33        }
34    }
35}
36
37/// updates all feeds on a regular interval, until death.
38pub(crate) async fn update_feeds(
39    subscriptions: Arc<RwLock<Subscriptions>>,
40    client: &Client,
41) -> miette::Result<()> {
42    info!("Checking feeds updates…");
43    let subs = subscriptions.read().await;
44    let subs_clone = subs.subs.clone();
45    drop(subs);
46    for (sub_url, sub_users) in &subs_clone {
47        debug!("Checking feed {sub_url}");
48        match update_feed(client, subscriptions.clone(), sub_url, sub_users).await {
49            Ok(_) => {
50                // great!
51            }
52            Err(e) => {
53                error!("Error while updating feed {sub_url}: {e}");
54            }
55        }
56    }
57    debug!("Feed checking finished, writing last check");
58    let mut subs = subscriptions.write().await;
59    subs.last_check = Utc::now();
60    subs.save().await?;
61    Ok(())
62}
63
64async fn update_feed(
65    client: &Client,
66    subs: Arc<RwLock<Subscriptions>>,
67    sub_url: &String,
68    sub_users: &HashSet<String>,
69) -> Result<(), miette::Error> {
70    let feed = get_feed(sub_url).await?;
71    trace!("Feed parsed successfully, locking subscriptions…");
72    let sub = subs.read().await;
73    let new_articles = feed.entries.iter().filter(|entry| {
74        if let Some(published) = entry.published
75            && published > sub.last_check
76        {
77            true
78        } else {
79            false
80        }
81    });
82    trace!("filtered articles…");
83    for new in new_articles {
84        let s = format!(
85            "{}: {} {}",
86            feed.title
87                .as_ref()
88                .map(|t| t.content.as_str())
89                .unwrap_or("No title"),
90            new.title
91                .as_ref()
92                .map(|t| t.content.as_str())
93                .unwrap_or("New entry"),
94            new.links
95                .first()
96                .ok_or(miette!(
97                    "Could not find a link in entry {new:?} for feed {sub_url}"
98                ))?
99                .href
100        );
101        info!("Sending \"{s}\" to clients {sub_users:?}");
102        for u in sub_users {
103            client.send_privmsg(u, &s).into_diagnostic()?;
104        }
105    }
106    Ok(())
107}
108
109pub(crate) async fn get_feed(feed_url: &str) -> miette::Result<Feed> {
110    static HTTP_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
111        reqwest::Client::builder()
112            .user_agent(APP_USER_AGENT)
113            .build()
114            .into_diagnostic()
115            .expect("Could not build http client")
116    });
117    trace!("update_feed {feed_url} making request…");
118    let response = HTTP_CLIENT
119        .get(feed_url)
120        .send()
121        .await
122        .into_diagnostic()
123        .wrap_err(format!("unable to fetch {feed_url}"))?
124        .bytes()
125        .await
126        .into_diagnostic()?;
127    trace!("update_feed: got response; parsing…");
128    let feed = feed_rs::parser::parse(response.as_ref()).into_diagnostic()?;
129    Ok(feed)
130}