irssc/main.rs
1//! # I(RSS)C - An RSS bot for IRC
2//!
3//! 
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}