diff --git a/Cargo.lock b/Cargo.lock index 9bed83d..8d3f4ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,185 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash", + "base64", + "bitflags 2.4.1", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.38", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.4", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.5.4", + "time", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -17,6 +196,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -193,12 +384,22 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -221,6 +422,23 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -340,6 +558,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -688,6 +919,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -697,12 +937,35 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "libc" version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "local-channel" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a493488de5f18c8ffcba89eebb8532ffc562dc400490eb65b84893fae0b178" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.4.10" @@ -757,6 +1020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi", "windows-sys", ] @@ -894,6 +1158,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -912,6 +1182,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "poise" version = "0.5.5" @@ -1098,6 +1374,15 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustls" version = "0.21.7" @@ -1161,6 +1446,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.189" @@ -1621,6 +1912,7 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" name = "valfisk" version = "0.1.0" dependencies = [ + "actix-web", "anyhow", "dotenvy", "num", @@ -1630,6 +1922,7 @@ dependencies = [ "regex", "reqwest", "serde", + "serde_json", "tokio", ] @@ -1861,3 +2154,32 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 6574a9a..04dfefb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ authors = ["Ryan Cao "] [dependencies] +actix-web = "4.4.0" anyhow = "1.0.75" dotenvy = "0.15.7" num = "0.4.1" @@ -15,12 +16,9 @@ once_cell = "1.18.0" owo-colors = { version = "3.5.0", features = ["supports-colors"] } poise = { git = "https://github.com/serenity-rs/poise.git", branch = "serenity-next", version = "0.5.5" } regex = "1.10.2" -reqwest = { version = "0.11.22", default-features = false, features = [ - "rustls-tls", - "json", - "brotli", -] } +reqwest = { version = "0.11.22", default-features = false, features = ["rustls-tls", "json", "brotli"] } serde = { version = "1.0.189", features = ["derive"] } +serde_json = "1.0.107" tokio = { version = "1.33.0", features = ["full"] } [profile.release] diff --git a/src/main.rs b/src/main.rs index c96f164..bdfb7d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,15 +12,21 @@ pub type Context<'a> = poise::Context<'a, Data, Error>; mod commands; mod handlers; +mod presence_api; mod reqwest_client; mod utils; +async fn wrapped_start(mut client: Client) -> Result<()> { + client.start().await?; + Ok(()) +} + #[tokio::main] async fn main() -> Result<()> { #[cfg(debug_assertions)] dotenvy::dotenv().ok(); - let mut client = Client::builder( + let client = Client::builder( std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"), GatewayIntents::all(), ) @@ -33,6 +39,15 @@ async fn main() -> Result<()> { FullEvent::Message { new_message, ctx } => { handlers::handle(new_message, ctx).await?; } + + FullEvent::PresenceUpdate { new_data, .. } => { + let mut presence_store = presence_api::PRESENCE_STORE.lock().await; + presence_store.insert( + new_data.user.id, + presence_api::ValfiskPresenceData::from_presence(new_data), + ); + } + &_ => {} } @@ -44,7 +59,7 @@ async fn main() -> Result<()> { |ctx, ready, framework| { Box::pin(async move { let tag = ready.user.tag(); - println!("{} to Discord ({})", "Connected".green(), tag.cyan()); + println!("{} to Discord as {}", "Connected".green(), tag.cyan()); let commands = &framework.options().commands; @@ -62,7 +77,11 @@ async fn main() -> Result<()> { )) .await?; - client.start().await?; + tokio::select! { + _ = wrapped_start(client) => {}, + _ = presence_api::serve() => {}, + _ = tokio::signal::ctrl_c() => {}, + }; Ok(()) } diff --git a/src/presence_api.rs b/src/presence_api.rs new file mode 100644 index 0000000..071fb99 --- /dev/null +++ b/src/presence_api.rs @@ -0,0 +1,73 @@ +use poise::serenity_prelude as serenity; + +use actix_web::{get, web, App, HttpResponse, HttpServer, Responder}; +use serde_json::json; + +use anyhow::Result; +use once_cell::sync::Lazy; +use std::collections::HashMap; +use tokio::sync::Mutex; + +use owo_colors::OwoColorize; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ValfiskPresenceData { + pub status: serenity::OnlineStatus, + pub client_status: Option, + #[serde(default)] + pub activities: Vec, +} + +impl ValfiskPresenceData { + pub fn from_presence(presence: &serenity::Presence) -> ValfiskPresenceData { + Self { + status: presence.status, + client_status: presence.client_status.clone(), + activities: presence.activities.clone(), + } + } +} + +pub static PRESENCE_STORE: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +#[get("/")] +async fn route_ping() -> impl Responder { + HttpResponse::Ok().json(json!({"ok": true})) +} + +#[get("/presence/{user}")] +async fn route_get_presence(path: web::Path<(u64,)>) -> impl Responder { + let path = path.into_inner(); + let user_id = serenity::UserId::new(path.0); + + let store = PRESENCE_STORE.lock().await; + let presence_data = store.get(&user_id).cloned(); + drop(store); + + if let Some(presence_data) = presence_data { + HttpResponse::Ok().json(presence_data) + } else { + HttpResponse::NotFound().json(json!({"error": "User not found!"})) + } +} + +pub async fn serve() -> Result<()> { + let host = std::env::var("HOST").unwrap_or("127.0.0.1".to_owned()); + let port = std::env::var("PORT") + .unwrap_or("8080".to_owned()) + .parse::()?; + + println!( + "{} API server {}", + "Started".green(), + format!("http://{}:{}", host, port).dimmed() + ); + + HttpServer::new(|| App::new().service(route_get_presence)) + .bind((host, port))? + .run() + .await?; + + Ok(()) +}