diff --git a/src/bot/filters/message_fitlers.rs b/src/bot/filters/message_fitlers.rs index f2d973d20848cffd4328f954b429c54b674f109f..6533d4ae30e61d713a7915cc9cc4ede76d74057a 100644 --- a/src/bot/filters/message_fitlers.rs +++ b/src/bot/filters/message_fitlers.rs @@ -1,4 +1,5 @@ use grammers_client::Update; +use regex::Regex; use crate::utils::messages::get_message; @@ -31,9 +32,14 @@ pub struct ExcludedChatsFilter(pub Vec<i64>); #[derive(Clone)] pub struct MessageDirectionFilter(pub MessageDirection); +/// Filters text by predicate. #[derive(Clone)] pub struct TextFilter<'a>(pub &'a [&'a str], pub TextMatchMethod); +/// Filters using provided regex. +#[derive(Clone)] +pub struct RegexFilter(pub Regex); + impl Filter for ExcludedChatsFilter { fn filter(&self, update: &Update) -> anyhow::Result<bool> { let a = match update { @@ -89,3 +95,10 @@ impl Filter for SilentFilter { Ok(!message.silent()) } } + +impl Filter for RegexFilter { + fn filter(&self, update: &Update) -> anyhow::Result<bool> { + let Some(message) = get_message(update) else {return Ok(false)}; + Ok(self.0.is_match(message.text())) + } +} diff --git a/src/bot/handlers/basic/mod.rs b/src/bot/handlers/basic/mod.rs index 3f81a214a474dcf573fca1e475444a68aa3e2012..acc4306c2c07cd0186f2ccdb38ce2a1b09f9ecde 100644 --- a/src/bot/handlers/basic/mod.rs +++ b/src/bot/handlers/basic/mod.rs @@ -1,3 +1,4 @@ pub mod currency_converter; pub mod get_chat_id; pub mod help; +pub mod weather_forecaster; diff --git a/src/bot/handlers/basic/weather_forecaster.rs b/src/bot/handlers/basic/weather_forecaster.rs new file mode 100644 index 0000000000000000000000000000000000000000..2edd4a76574b534677b0c90d84225db9d013eb7a --- /dev/null +++ b/src/bot/handlers/basic/weather_forecaster.rs @@ -0,0 +1,84 @@ +use std::time::Duration; + +use grammers_client::{Client, InputMessage, Update}; +use rand::seq::SliceRandom; + +use crate::{bot::handlers::Handler, utils::messages::get_message}; +use rand::rngs::OsRng; + +#[derive(Clone)] +pub struct WeatherForecaster { + client: reqwest::Client, +} + +impl WeatherForecaster { + pub fn new() -> anyhow::Result<Self> { + let client = reqwest::ClientBuilder::new() + .timeout(Duration::from_secs(2)) + .gzip(true) + .build()?; + Ok(Self { client }) + } +} + +#[async_trait::async_trait] +impl Handler for WeatherForecaster { + async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> { + let Some(message) = get_message(update) else { + return Ok(()); + }; + let Some(city) = message.text().strip_prefix(".w").map(str::trim) else { + return Ok(()); + }; + + let response = self + .client + .get(format!("https://wttr.in/{city}?0QT")) + .header("user-agent", "curl/7.0") + .send() + .await?; + + if response.status() == reqwest::StatusCode::NOT_FOUND { + message + .reply( + [ + "Лол, ты Ñам-то понÑл что напиÑал?", + "Ðе, Ñ Ñ…Ð· где Ñто.", + "ЕÑли ты дейÑтвительно тут живешь, то ÑочувÑтвую.", + ] + .choose(&mut OsRng) + .copied() + .unwrap(), + ) + .await?; + return Ok(()); + } + + if response.status() != reqwest::StatusCode::OK { + message.reply("Ð¡ÐµÑ€Ð²Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупен. Попробуй потом.").await?; + return Ok(()); + } + + let forecast = response.text().await?; + + message + .reply( + InputMessage::html( + forecast + // We need to wrap every line in pre tag. + .split('\n') + .filter(|line| !line.trim().is_empty()) + .map(|line| format!("<pre>{line}</pre>\n")) + // Also we add a message at the end by chaining another + // iterable. + .chain([String::from("Ваш прогноз.")]) + // And collect interator to final HTML string. + .collect::<String>(), + ) + .silent(true), + ) + .await?; + + Ok(()) + } +} diff --git a/src/bot/handlers/fun/mod.rs b/src/bot/handlers/fun/mod.rs index 8d24276ed9b86f90ce272366a8ef75505d39950c..46355f68b71815a1ff211e692068119d1a7840a0 100644 --- a/src/bot/handlers/fun/mod.rs +++ b/src/bot/handlers/fun/mod.rs @@ -1,3 +1,4 @@ pub mod blyaficator; pub mod greeter; +pub mod repeator; pub mod rotator; diff --git a/src/bot/handlers/fun/repeator.rs b/src/bot/handlers/fun/repeator.rs new file mode 100644 index 0000000000000000000000000000000000000000..20398e3c548319a8982379a79775c2b75d0633c3 --- /dev/null +++ b/src/bot/handlers/fun/repeator.rs @@ -0,0 +1,15 @@ +use grammers_client::{Client, Update}; + +use crate::{bot::handlers::Handler, utils::messages::get_message}; + +#[derive(Clone)] +pub struct Repeator; + +#[async_trait::async_trait] +impl Handler for Repeator { + async fn react(&self, _: &Client, update: &Update) -> anyhow::Result<()> { + let Some(message) = get_message(update) else { return Ok(()) }; + message.respond(message.text()).await?; + Ok(()) + } +} diff --git a/src/bot/main.rs b/src/bot/main.rs index 1288014fc6acbb285a0f58602788cf3214a80ad2..5ddd3823f7938dc9c20b21fc07bfe111af3fba7f 100644 --- a/src/bot/main.rs +++ b/src/bot/main.rs @@ -4,14 +4,15 @@ use crate::args::BotConfig; use grammers_client::{Client, Config, SignInError, Update}; use grammers_session::Session; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; +use regex::Regex; use tokio::sync::RwLock; use super::{ filters::{ filtered_handler::FilteredHandler, message_fitlers::{ - ExcludedChatsFilter, MessageDirection, MessageDirectionFilter, SilentFilter, - TextFilter, TextMatchMethod, + ExcludedChatsFilter, MessageDirection, MessageDirectionFilter, RegexFilter, + SilentFilter, TextFilter, TextMatchMethod, }, }, handlers::{ @@ -19,8 +20,9 @@ use super::{ currency_converter::{CurrencyConverter, CurrencyTextFilter}, get_chat_id::GetChatId, help::Help, + weather_forecaster::WeatherForecaster, }, - fun::{blyaficator::Blyaficator, greeter::Greeter, rotator::Rotator}, + fun::{blyaficator::Blyaficator, greeter::Greeter, repeator::Repeator, rotator::Rotator}, Handler, }, }; @@ -90,11 +92,13 @@ async fn handle_with_log(handler: Box<dyn Handler>, client: Client, update_data: /// /// Also, every available handler is defined here. async fn run(args: BotConfig, client: Client) -> anyhow::Result<()> { + let me = client.get_me().await?; let handlers: Vec<FilteredHandler> = vec![ // Printing help. FilteredHandler::new(Help).add_filter(TextFilter(&[".h"], TextMatchMethod::IMatches)), // Greeting my fellow humans. FilteredHandler::new(Greeter) + .add_filter(ExcludedChatsFilter(vec![me.id()])) .add_filter(SilentFilter) .add_filter(MessageDirectionFilter(MessageDirection::Incoming)) .add_filter(TextFilter(&["привет"], TextMatchMethod::IStartsWith)) @@ -115,6 +119,16 @@ async fn run(args: BotConfig, client: Client) -> anyhow::Result<()> { FilteredHandler::new(Rotator) .add_filter(SilentFilter) .add_filter(TextFilter(&[".rl"], TextMatchMethod::IStartsWith)), + // Weather forecast. + FilteredHandler::new(WeatherForecaster::new()?) + .add_filter(SilentFilter) + .add_filter(TextFilter(&[".w"], TextMatchMethod::IStartsWith)), + // Smiley repeator. + FilteredHandler::new(Repeator) + .add_filter(ExcludedChatsFilter(vec![me.id()])) + .add_filter(MessageDirectionFilter(MessageDirection::Incoming)) + .add_filter(SilentFilter) + .add_filter(RegexFilter(Regex::new("^[)0]+$")?)), ]; let mut errors_count = 0; @@ -154,7 +168,6 @@ async fn run(args: BotConfig, client: Client) -> anyhow::Result<()> { } Ok(()) } - pub async fn bot_life( args: BotConfig, web_code: Arc<RwLock<Option<String>>>,