diff --git a/Cargo.lock b/Cargo.lock index fd894d3..fa2c49c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,18 +84,19 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "d09dbe0e490df5da9d69b36dca48a76635288a82f92eca90024883a56202026d" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.1.0", + "hyper-util", "itoa", "matchit", "memchr", @@ -112,23 +113,28 @@ dependencies = [ "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "e87c8503f93e6d144ee5690907ba22db7ba79ab001a932ab99034f0fe836b3df" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.0.0", + "http-body 1.0.0", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -214,8 +220,10 @@ checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets", ] @@ -643,7 +651,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.9", "indexmap 1.9.3", "slab", "tokio", @@ -651,6 +659,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.0.0", + "indexmap 2.0.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -695,6 +722,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.5" @@ -702,7 +740,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -744,9 +805,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.21", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa", @@ -758,6 +819,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.0", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -765,12 +845,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.27", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.1.0", + "pin-project-lite", + "socket2 0.5.4", + "tokio", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -1224,6 +1322,18 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "posthog-rs" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad1b35ffe50419992615288c40ee90fbf30da4c6faf251414675ea64e1cdfa3" +dependencies = [ + "chrono", + "reqwest", + "serde", + "serde_json", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1370,10 +1480,10 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.21", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-tls", "ipnet", "js-sys", @@ -2310,6 +2420,7 @@ dependencies = [ "humantime-serde", "indoc", "log", + "posthog-rs", "pretty_env_logger", "rand", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index f46f8e1..8bf0c8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,12 +21,13 @@ keywords = [ log = "0.4" rand = "0.8" url = "2.4.1" +axum = "0.7.3" indoc = "2.0.4" dashmap = "5.5" -axum = "0.6.20" dotenvy = "0.15.7" serde_with = "3.3" reqwest = "0.11.22" +posthog-rs = "0.2.2" serde_json = "1.0.108" humantime-serde = "1.1" pretty_env_logger = "0.5" diff --git a/config.toml b/config.toml index a09ff67..83a319d 100644 --- a/config.toml +++ b/config.toml @@ -6,6 +6,10 @@ # You can put it below or set the WLD_CAPTCHA_BOT_TOKEN env var # bot_token = "..." +# You can optionally set up PostHog to track bot usage. +# To enable, put your PostHog API token below or set the WLD_CAPTCHA_POSTHOG_TOKEN env var +# posthog_token = "..." + # The URL to the server running this bot. It must be accessible from the internet. # You can put it below or set the WLD_CAPTCHA_APP_URL env var # app_url = "..." diff --git a/src/bot/commands.rs b/src/bot/commands.rs index 4160375..0f49577 100644 --- a/src/bot/commands.rs +++ b/src/bot/commands.rs @@ -15,11 +15,11 @@ use crate::{ #[derive(BotCommands)] #[command(rename_rule = "lowercase", description = "Available commands:")] pub enum Command { - #[command(description = "Display this text")] + #[command(description = "Explain how this bot works, and how to use it.")] Help, - #[command(description = "Pong!")] - Ping, - #[command(description = "Start the bot")] + #[command(description = "Check that the bot is online and has the right permissions.")] + Check, + #[command(description = "Initial help when talking to the bot for the first time.")] Start, } @@ -43,14 +43,9 @@ pub async fn command_handler( }; match command { - Command::Help => { - bot.send_message(msg.chat.id, Command::descriptions().to_string()) - .reply_to_message_id(msg.id) - .await?; - }, - Command::Ping => { + Command::Check => { if msg.chat.is_private() { - bot.send_message(msg.chat.id, "pong") + bot.send_message(msg.chat.id, "You can only use this bot in public groups. Please add me to a public group (with admin permissions) and try again.") .reply_to_message_id(msg.id) .await?; return Ok(()); @@ -62,7 +57,7 @@ pub async fn command_handler( .is_administrator(); if is_admin { - bot.send_message(msg.chat.id, "Bot has admin permissions and is ready to go!") + bot.send_message(msg.chat.id, "Bot has admin permissions and is ready to go! Once someone joins the group, they'll be asked to prove they're human with World ID before they can send messages.") .reply_to_message_id(msg.id) .await?; } else { @@ -70,9 +65,13 @@ pub async fn command_handler( .await?; } }, - Command::Start => { + Command::Help | Command::Start => { if msg.chat.is_private() { - bot.send_message(msg.chat.id, "Hello! To use this bot, add it to a group and give it admin permissions. Then, use /ping to check if it's working.").reply_to_message_id(msg.id).await?; + bot.send_message(msg.chat.id, r#" +Welcome to the World ID Telegram bot! + +You can use me to protect your group from spammers and bots. To get started, add me to your (public) group and give me admin permissions. When someone joins your group, they'll be asked to prove they're human with World ID before they can send messages. + "#).reply_to_message_id(msg.id).await?; } }, }; diff --git a/src/config.rs b/src/config.rs index 152db75..afae23b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,6 +15,7 @@ pub struct AppConfig { pub bot_token: String, #[serde(flatten, default)] pub groups_config: GroupsConfig, + pub posthog_token: Option, } impl AppConfig { @@ -26,6 +27,12 @@ impl AppConfig { .build()? .try_deserialize() } + + pub fn posthog(&self) -> Option { + self.posthog_token + .as_ref() + .map(|token| posthog_rs::client(token.as_str())) + } } #[serde_as] diff --git a/src/server/mod.rs b/src/server/mod.rs index eaf3393..feb8912 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,13 +6,13 @@ use axum::{ Extension, Json, Router, }; use indoc::formatdoc; +use posthog_rs::Event; use serde_json::json; -use std::net::SocketAddr; use teloxide::{ types::{ChatId, User, UserId}, Bot, }; -use tokio::signal; +use tokio::{net::TcpListener, signal}; use crate::{ bot::{on_verified, JoinRequests}, @@ -36,11 +36,13 @@ pub async fn start(bot: Bot, config: AppConfig, bot_data: User, join_requests: J .layer(Extension(config)) .layer(Extension(join_requests)); - let addr = SocketAddr::from(([0, 0, 0, 0], 8000)); - log::info!("Starting server at http://{addr}"); + let listener = TcpListener::bind(("0.0.0.0", 8000)).await.unwrap(); + log::info!( + "Starting server at http://{}", + listener.local_addr().unwrap() + ); - axum::Server::bind(&addr) - .serve(app.into_make_service()) + axum::serve(listener, app) .with_graceful_shutdown(async move { signal::ctrl_c().await.unwrap() }) .await .unwrap(); @@ -163,5 +165,15 @@ async fn verify_api( .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + if let Some(posthog) = config.posthog() { + let event = Event::new("telegram integration verification", &user_id.to_string()); + + posthog.capture(event).map_err(|e| { + log::error!("Failed to send event to PostHog: {e:?}"); + + StatusCode::INTERNAL_SERVER_ERROR + })?; + } + Ok("Verified!") }