use rand::{distributions::Alphanumeric, prelude::*}; use std::sync::Arc; use tbot::{ contexts::fields, prelude::*, state::{ messages::{MessageId, Messages}, Chats, }, types::{chat, parameters::Text}, Bot, }; use tokio::sync::RwLock; #[derive(Default)] struct State { chats: RwLock>, messages: RwLock>>, } impl State { async fn participants(&self, room: &str) -> Vec { self.chats .read() .await .iter() .filter_map( |(id, chat_room)| { if chat_room == room { Some(id) } else { None } }, ) .collect::>() } async fn join(&self, bot: &Bot, participant: chat::Id, room: String) { self.notify( bot, &room, Text::with_markdown_v2("_A participant has joined the room\\._"), ) .await; let previous_room = self.chats.write().await.insert_by_id(participant, room); if let Some(room) = previous_room { self.notify( bot, &room, Text::with_markdown_v2("_A participant has left the room\\._"), ) .await; } } async fn notify(&self, bot: &Bot, room: &str, message: Text<'_>) { let participants = self.participants(room).await; for id in participants { let call_result = bot.send_message(id, message).call().await; if let Err(err) = call_result { dbg!(err); } } } } async fn broadcast(context: Arc, state: Arc) where C: fields::Text, { let chats = state.chats.read().await; let room = chats.get(&*context); let sender_id = context.chat().id; if let Some(room) = room { let mut recipients = state.participants(room).await; recipients.retain(|&id| id != sender_id); let mut sent_messages = Vec::with_capacity(recipients.len()); for id in recipients { let call_result = context .bot() .send_message(id, &context.text().value) .call() .await; match call_result { Ok(message) => { sent_messages.push(MessageId::from_message(&message)); } Err(err) => { dbg!(err); } } } state .messages .write() .await .insert(&*context, sent_messages); } else { let call_result = context .send_message( "You have not joined a room to send messages. \ Join one or create a room with /create_room.", ) .call() .await; if let Err(err) = call_result { dbg!(err); } } } async fn broadcast_edit(context: Arc, state: Arc) where C: fields::Text, { if let Some(messages) = state.messages.read().await.get(&*context) { for MessageId { chat_id, message_id, } in messages { let call_result = context .bot() .edit_message_text(*chat_id, *message_id, &context.text().value) .call() .await; if let Err(err) = call_result { dbg!(err); } } } } #[tokio::main] async fn main() { let bot = Bot::from_env("BOT_TOKEN"); let me = bot.get_me().call().await.ok(); let username = me .and_then(|me| me.user.username) .expect("Could not get username"); let mut bot = bot.stateful_event_loop(State::default()); bot.start(|context, state| async move { if context.text.value.is_empty() { let call_result = context .send_message( "Hello! I'm a bot for anonymous messaging in rooms. \ Use the /create_room command to create a new room.", ) .call() .await; if let Err(err) = call_result { dbg!(err); } } else { state .join( &context.bot, context.chat.id, context.text.value.to_owned(), ) .await; let call_result = context .send_message(Text::with_markdown_v2( "_You have joined the room\\._", )) .call() .await; if let Err(err) = call_result { dbg!(err); } } }); bot.help(|context, _| async move { let call_result = context .send_message( "Here are the commands I know:\n\n\ — /create_room — create rooms;\n\ — /send — send messages (you can omit the command if you don't \ need to send a command in the beginning of your message);\n\ — /leave — leave the current room;\n\ — /help — send this message.", ) .call() .await; if let Err(err) = call_result { dbg!(err); } }); bot.command("send", broadcast); bot.text(broadcast); bot.edited_command("send", broadcast_edit); bot.edited_text(broadcast_edit); bot.command("leave", |context, state| async move { let room = state.chats.write().await.remove(&*context); if let Some(room) = room { state .notify( &context.bot, &room, Text::with_markdown_v2( "_A participant has left the room\\._", ), ) .await; } let call_result = context .send_message(Text::with_markdown_v2("_You have left the room\\._")) .call() .await; if let Err(err) = call_result { dbg!(err); } }); bot.command("create_room", move |context, state| { let username = username.clone(); async move { let room = rand::thread_rng() .sample_iter(&Alphanumeric) .take(10) .collect::(); let message = format!( "_You have created a new room\\. Share this link so others \ can join your room:_ t\\.me/{}?start\\={}", username.replace("_", "\\_"), room ); state.join(&context.bot, context.chat.id, room).await; let call_result = context .send_message(Text::with_markdown_v2(&message)) .call() .await; if let Err(err) = call_result { dbg!(err); } } }); bot.polling().start().await.unwrap(); }