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//! 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}