diff --git a/Cargo.lock b/Cargo.lock index 69bc8dd..044105f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,6 +292,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -755,6 +776,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -823,6 +855,7 @@ version = "0.1.0" dependencies = [ "chrono", "data-encoding", + "dirs", "http-body-util", "hyper 1.2.0", "hyper-util", @@ -896,6 +929,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1017,6 +1056,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "reqwest" version = "0.11.26" diff --git a/Cargo.toml b/Cargo.toml index 9d8789b..0bb6c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ ring = { version = "0.17.8" } data-encoding = "2.5" stderrlog = "0.6.0" log = { version = "0.4.21", features = ["serde"] } -thiserror = "1.0.37" \ No newline at end of file +thiserror = "1.0.37" +dirs = "5.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2c21959..141ce38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{convert::Infallible, fs, net::SocketAddr}; +use std::{convert::Infallible, fs, net::SocketAddr, path::PathBuf}; use config::Config; use http_body_util::{BodyExt, Full}; @@ -21,23 +21,48 @@ mod config; mod discord; mod miniflux_requests; -//FIXME! -const CONFIG_PATH: &'static str = "./config.json"; - #[tokio::main] async fn main() -> Result<(), Box> { - let config: Config = serde_json::from_slice(&fs::read(CONFIG_PATH).unwrap()).unwrap(); + let path_opt = { + let default_path = dirs::config_dir().map(|p| p.join("miniflux_discord.json")); + let path = std::env::var("CONFIG_PATH").ok().map(|s| PathBuf::from(s)).or(default_path); + path + }; + let path = match path_opt { + Some(p) => p, + None => panic!("config path could not be resolved! Use CONFIG_PATH environment variable"), + }; + + let res: Result<_, StartingError> = fs::read(path) + .map_err(|e| e.into()) + .and_then(|s| serde_json::from_slice(&s).map_err(|e| e.into())); + + let config: Config = match res { + Ok(c) => c, + Err(e) => panic!("{}", e), + }; stderrlog::new() .module(module_path!()) .verbosity(config.log_level) .timestamp(stderrlog::Timestamp::Second) .init() - .unwrap(); + .expect("stderrlog could not be initialized!"); - let mut client = Client::builder(&config.discord_token, GatewayIntents::empty()) - .await - .expect("Error creating Discord client"); + dbg!("hello?"); + match start_serving(config).await { + Ok(_) => Ok(()), + Err(e) => { + log::error!("{}", e); + Ok(()) + } + } +} + +async fn start_serving(config: Config) -> Result<(), StartingError> { + dbg!("hello?"); + + let mut client = Client::builder(&config.discord_token, GatewayIntents::empty()).await?; let addr = SocketAddr::from((config.host, config.port)); let listener = TcpListener::bind(addr).await?; @@ -92,33 +117,47 @@ async fn main() -> Result<(), Box> { Ok(()) } +#[derive(Error, Debug)] +pub enum StartingError { + #[error("configuration file path not found!")] + ConfigPathNotFound, + #[error("IO Error: {0}")] + IOError(#[from] std::io::Error), + #[error("Config Json deserialization error: {0}")] + ConfigJsonError(#[from] serde_json::Error), + #[error("Logger init failed: {0}")] + LoggerError(#[from] log::SetLoggerError), + #[error("Discord Client Error: {0}")] + DiscordClientError(#[from] serenity::Error), +} + async fn process_webhook( req: Request, holder: &DiscordHolder, -) -> Result>, CustomError> { +) -> Result>, ServingError> { let method = req.method(); if method != Method::POST { - Err(CustomError::InvalidMethod(method.clone()))?; + Err(ServingError::InvalidMethod(method.clone()))?; } let userid: u64 = - req.uri().path().split("/").nth(1).ok_or(CustomError::IdPathNotFound)?.parse()?; + req.uri().path().split("/").nth(1).ok_or(ServingError::IdPathNotFound)?.parse()?; if !holder.config.whitelisted_user_ids.contains(&userid) { - Err(CustomError::NotWhitelistedUser(userid))?; + Err(ServingError::NotWhitelistedUser(userid))?; } let headers = req.headers(); let signature_bytes = - headers.get("x-miniflux-signature").ok_or(CustomError::SignatureMissing)?.as_bytes(); + headers.get("x-miniflux-signature").ok_or(ServingError::SignatureMissing)?.as_bytes(); let signature = data_encoding::HEXLOWER.decode(signature_bytes)?; let event_type = - headers.get("x-miniflux-event-type").ok_or(CustomError::EventTypeMissing)?.clone(); + headers.get("x-miniflux-event-type").ok_or(ServingError::EventTypeMissing)?.clone(); let upper = req.body().size_hint().upper().unwrap_or(u64::MAX); if upper > holder.config.payload_max_size { - Err(CustomError::PayloadTooLarge(upper))?; + Err(ServingError::PayloadTooLarge(upper))?; } let whole_body = req.collect().await?.to_bytes(); @@ -127,7 +166,7 @@ async fn process_webhook( let key = hmac::Key::new(hmac::HMAC_SHA256, &holder.config.miniflux_webhook_secret.as_bytes()); let () = - hmac::verify(&key, &bytes, &signature).map_err(|_| CustomError::HmacValidationError)?; + hmac::verify(&key, &bytes, &signature).map_err(|_| ServingError::HmacValidationError)?; let event: MinifluxEvent = serde_json::from_slice(&bytes)?; match event { @@ -146,7 +185,7 @@ async fn process_webhook( } #[derive(Error, Debug)] -pub enum CustomError { +pub enum ServingError { #[error("id path not found")] IdPathNotFound, #[error("invalid method: {0}")] @@ -171,31 +210,31 @@ pub enum CustomError { JsonError(#[from] serde_json::Error), } -impl Into>> for CustomError { +impl Into>> for ServingError { fn into(self) -> Response> { match &self { - CustomError::JsonError(_) => log::error!("{}", self), - CustomError::HmacValidationError => log::warn!("{}", self), - CustomError::HyperError(_) => log::warn!("{}", self), - CustomError::PayloadTooLarge(_) => log::warn!("{}", self), - CustomError::SignatureMissing => log::warn!("{}", self), - CustomError::EventTypeMissing => log::warn!("{}", self), + ServingError::JsonError(_) => log::error!("{}", self), + ServingError::HmacValidationError => log::warn!("{}", self), + ServingError::HyperError(_) => log::warn!("{}", self), + ServingError::PayloadTooLarge(_) => log::warn!("{}", self), + ServingError::SignatureMissing => log::warn!("{}", self), + ServingError::EventTypeMissing => log::warn!("{}", self), _ => log::debug!("{}", self), } let mut resp = Response::new(Full::from(Bytes::from(self.to_string()))); let status_code = match self { - CustomError::IdPathNotFound => StatusCode::NOT_FOUND, - CustomError::InvalidMethod(_) => StatusCode::METHOD_NOT_ALLOWED, - CustomError::ParseIntError(_) => StatusCode::BAD_REQUEST, - CustomError::NotWhitelistedUser(_) => StatusCode::FORBIDDEN, - CustomError::SignatureMissing => StatusCode::BAD_REQUEST, - CustomError::EventTypeMissing => StatusCode::BAD_REQUEST, - CustomError::DecodeError(_) => StatusCode::BAD_REQUEST, - CustomError::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE, - CustomError::HyperError(_) => StatusCode::INTERNAL_SERVER_ERROR, - CustomError::HmacValidationError => StatusCode::FORBIDDEN, - CustomError::JsonError(_) => StatusCode::BAD_REQUEST, + ServingError::IdPathNotFound => StatusCode::NOT_FOUND, + ServingError::InvalidMethod(_) => StatusCode::METHOD_NOT_ALLOWED, + ServingError::ParseIntError(_) => StatusCode::BAD_REQUEST, + ServingError::NotWhitelistedUser(_) => StatusCode::FORBIDDEN, + ServingError::SignatureMissing => StatusCode::BAD_REQUEST, + ServingError::EventTypeMissing => StatusCode::BAD_REQUEST, + ServingError::DecodeError(_) => StatusCode::BAD_REQUEST, + ServingError::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE, + ServingError::HyperError(_) => StatusCode::INTERNAL_SERVER_ERROR, + ServingError::HmacValidationError => StatusCode::FORBIDDEN, + ServingError::JsonError(_) => StatusCode::BAD_REQUEST, }; *resp.status_mut() = status_code;