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//! sudo mise deploy # will do stuff and print stuff
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 rss;
141
142/// How many seconds to wait between RSS refreshes
143pub(crate) const FEED_REFRESH_INTERVAL_SECONDS: u32 = 600;
144pub(crate) const APP_USER_AGENT: &str = concat!(
145    env!("CARGO_PKG_NAME"),
146    "/",
147    env!("CARGO_PKG_VERSION"),
148    " +",
149    env!("CARGO_PKG_REPOSITORY")
150);
151
152const HELP_TXT: &str = concatcp!(
153    "\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.",
154    "\nI am configured to check for feed updates every ",
155    FEED_REFRESH_INTERVAL_SECONDS,
156    "s.",
157    "\nCOMMANDS:
158\x02help|h\x02: this text
159\x02subscribe|sub|s \x1d<url>\x1d\x02: Subscribe to the feed at <url>
160\x02unsubscribe|unsub|u \x1d<url>\x1d\x02: Unsubscribe to the feed at <url> (see list command)
161\x02list|l\x02: list your subscriptions
162\x02info|i\x02: print some info about the project."
163);
164
165const INFO_TXT: &str = concat!(
166    "\x11",
167    env!["CARGO_PKG_NAME"],
168    "\x11 v",
169    env!["CARGO_PKG_VERSION"],
170    "\n",
171    env!["CARGO_PKG_DESCRIPTION"],
172    "\nBy: ",
173    env!["CARGO_PKG_AUTHORS"],
174    "\nSource Code: ",
175    env!["CARGO_PKG_REPOSITORY"]
176);
177
178#[tokio::main]
179async fn main() -> miette::Result<()> {
180    pretty_env_logger::try_init_timed().into_diagnostic()?;
181    let config_path = if let Ok(p) = std::env::var("IRSSC_CONFIG") {
182        PathBuf::from(p)
183    } else {
184        PathBuf::from(".").join("irssc.toml")
185    };
186    let mut config = Config::load(&config_path)
187        .into_diagnostic()
188        .wrap_err_with(|| format!("failed to read config at {}", config_path.display()))?;
189    config.realname = Some(env!("CARGO_PKG_NAME").to_string());
190    let storage = Arc::new(RwLock::new(
191        Subscriptions::load(config.server().into_diagnostic()?).await?,
192    ));
193    let mut client = Client::from_config(config.clone())
194        .await
195        .into_diagnostic()?;
196    client.identify().into_diagnostic()?;
197    let mut stream = client.stream().into_diagnostic()?;
198    let client = Arc::new(client);
199    let feed_update_handle = tokio::task::spawn(rss::feed_watcher(storage.clone(), client.clone()));
200    while let Some(message) = stream.next().await.transpose().into_diagnostic()? {
201        debug!("{message}");
202        bot::dispatch_message(&message, &config, storage.clone(), &client).await?;
203    }
204    feed_update_handle.abort();
205    Ok(())
206}