Skip to main content

irssc/
main.rs

1//! # I(RSS)C - An RSS bot for IRC
2//!
3//! ![A preview of the bot showing its help and info commands](preview.png)
4//!
5//! This is an IRC bot that simply checks for RSS updates to feeds you have subscribed to.
6//!
7//! ## Quickstart
8//!
9//! There's no binary releases yet, so build it from source:
10//!
11//! ```bash
12//! # clone and build
13//! git clone https://rad.vit.am/zrgjzc4YDUNZzPZ4oNx1VvXokPC6.git irssc && cd irssc && cargo build --release && cargo install --path=.
14//! # copy the sample config file
15//! cp irssc.toml{.example,}
16//! # Edit the configuration, for instance the server you wish to connect to.
17//! $EDITOR irssc.toml
18//! # run irssc
19//! irssc
20//! ```
21//!
22//! ## Installation
23//!
24//! Two possibilities: **A**, with [mise-en-place][mise] and **B**, without [mise].
25//!
26//! ### A. With [mise]
27//!
28//! With [mise], everything is easy and smells good:
29//!
30//! ```bash
31//! # clone the repository
32//! git clone https://rad.vit.am/zrgjzc4YDUNZzPZ4oNx1VvXokPC6.git irssc && cd irssc
33//! # copy the sample files
34//! cp irssc.toml{.example,}
35//! cp systemd.service{.example,}
36//! # edit them to suit your preferences
37//! $EDITOR irssc.toml systemd.service
38//! # OPTIONAL: change the FEED_REFRESH_INTERVAL_SECONDS value in `src/main.rs` to change the feed refresh rate.
39//! # $EDITOR src/main.rs
40//! # install the system dependencies
41//! apt update && apt install build-essential pkg-config
42//! # then, run the deployment
43//! mise deploy # will ask for sudo password.
44//! # enable the systemd service
45//! sudo systemctl enable --now irssc.service
46//! ```
47//!
48//! ### B. Without [mise]
49//!
50//! #### 0. Ensure rustup is installed and has a valid rust toolchain
51//!
52//! ```bash
53//! rustup install stable
54//! apt update && apt install -y build-essential pkg-config
55//! ```
56//!
57//! #### 1. Clone and build the project
58//!
59//! ```bash
60//! git clone https://rad.vit.am/zrgjzc4YDUNZzPZ4oNx1VvXokPC6.git irssc && cd irssc
61//! cargo build --release
62//! ```
63//!
64//! #### 2. Copy and edit sample configuration files
65//!
66//! ```bash
67//! cp irssc.toml{.example,}
68//! cp systemd.service{.example,}
69//! $EDITOR irssc.toml systemd.service
70//! ```
71//!
72//! OPTIONAL: change the `FEED_REFRESH_INTERVAL_SECONDS` value in `src/main.rs` to change the feed refresh rate.
73//!
74//! #### 3. Create data directory
75//!
76//! ```bash
77//! sudo mkdir -p /var/lib/irssc/
78//! ```
79//!
80//! #### 4. Install configuration file
81//!
82//! ```bash
83//! sudo cp irssc.toml /etc/irssc.toml
84//! ```
85//!
86//! #### 5. Install systemd service
87//!
88//! ```bash
89//! sudo cp systemd.service /etc/systemd/system/irssc.service
90//! sudo systemctl daemon-reload
91//! ```
92//!
93//! #### 6. Install binary
94//!
95//! ```bash
96//! sudo cp target/release/irssc /usr/bin/irssc
97//! ```
98//!
99//! #### 7. Enable and start the service
100//!
101//! ```bash
102//! sudo systemctl enable --now irssc.service
103//! ```
104//!
105//! ### Verification
106//!
107//! Check the service status:
108//!
109//! ```bash
110//! sudo systemctl status irssc.service
111//! ```
112//!
113//! View logs:
114//!
115//! ```bash
116//! journalctl -u irssc.service -f
117//! ```
118//!
119//! If you are satisfied with your installation, you may run `cargo clean` to free up some space.
120//!
121//!
122//! ## Contributing
123//!
124//! Contributions are appreciated, be it in the form of bug reports or feature suggestions, or even patches. you can reach me on irc (`ololduck@libera.chat`) or on ActivityPub/Mastodon (`@ololduck@fosstodon.org`)
125//!
126//! [mise]: (https://mise.jdx.dev/)
127
128use std::{path::PathBuf, sync::Arc};
129
130use const_format::concatcp;
131use futures::StreamExt;
132use irc::client::{Client, prelude::Config};
133use log::debug;
134use miette::{Context, IntoDiagnostic};
135use tokio::sync::RwLock;
136
137use crate::bot::Subscriptions;
138
139mod bot;
140mod feed_cache;
141mod rss;
142
143/// How many seconds to wait between RSS refreshes
144pub(crate) const FEED_REFRESH_INTERVAL_SECONDS: u32 = 600;
145pub(crate) const APP_USER_AGENT: &str = concat!(
146    env!("CARGO_PKG_NAME"),
147    "/",
148    env!("CARGO_PKG_VERSION"),
149    " +",
150    env!("CARGO_PKG_REPOSITORY")
151);
152
153const HELP_TXT: &str = concatcp!(
154    "\x11{bot_name}\x11 lets you subscribe to RSS/Atom feeds from the comfort of your IRC client. It will PRIVMSG updates to you when they appear.",
155    "\nI am configured to check for feed updates every ",
156    FEED_REFRESH_INTERVAL_SECONDS,
157    "s.",
158    "\nCOMMANDS:
159\x02help|h\x02: this text
160\x02subscribe|sub|s \x1d<url>\x1d\x02: Subscribe to the feed at <url>
161\x02unsubscribe|unsub|u \x1d<url>\x1d\x02: Unsubscribe to the feed at <url> (see list command)
162\x02list|l\x02: list your subscriptions
163\x02info|i\x02: print some info about the project."
164);
165
166const INFO_TXT: &str = concat!(
167    "\x11",
168    env!["CARGO_PKG_NAME"],
169    "\x11 v",
170    env!["CARGO_PKG_VERSION"],
171    "\n",
172    env!["CARGO_PKG_DESCRIPTION"],
173    "\nBy: ",
174    env!["CARGO_PKG_AUTHORS"],
175    "\nSource Code: ",
176    env!["CARGO_PKG_REPOSITORY"]
177);
178
179#[tokio::main]
180async fn main() -> miette::Result<()> {
181    pretty_env_logger::try_init_timed().into_diagnostic()?;
182    let config_path = if let Ok(p) = std::env::var("IRSSC_CONFIG") {
183        PathBuf::from(p)
184    } else {
185        PathBuf::from(".").join("irssc.toml")
186    };
187    let mut config = Config::load(&config_path)
188        .into_diagnostic()
189        .wrap_err_with(|| format!("failed to read config at {}", config_path.display()))?;
190    config.realname = Some(env!("CARGO_PKG_NAME").to_string());
191    let storage = Arc::new(RwLock::new(
192        Subscriptions::load(config.server().into_diagnostic()?).await?,
193    ));
194    let mut client = Client::from_config(config.clone())
195        .await
196        .into_diagnostic()?;
197    client.identify().into_diagnostic()?;
198    let mut stream = client.stream().into_diagnostic()?;
199    let client = Arc::new(client);
200    let feed_update_handle = tokio::task::spawn(rss::feed_watcher(storage.clone(), client.clone()));
201    while let Some(message) = stream.next().await.transpose().into_diagnostic()? {
202        debug!("{message}");
203        bot::dispatch_message(&message, &config, storage.clone(), &client).await?;
204    }
205    feed_update_handle.abort();
206    Ok(())
207}