From d93d12e48a1ec470394112ba0e8caf55bf956bb9 Mon Sep 17 00:00:00 2001
From: Pavel Kirilin <win10@list.ru>
Date: Thu, 23 Feb 2023 22:14:59 +0000
Subject: [PATCH] Added weather.

---
 src/bot/filters/message_fitlers.rs           | 13 +++
 src/bot/handlers/basic/mod.rs                |  1 +
 src/bot/handlers/basic/weather_forecaster.rs | 84 ++++++++++++++++++++
 src/bot/handlers/fun/mod.rs                  |  1 +
 src/bot/handlers/fun/repeator.rs             | 15 ++++
 src/bot/main.rs                              | 21 ++++-
 6 files changed, 131 insertions(+), 4 deletions(-)
 create mode 100644 src/bot/handlers/basic/weather_forecaster.rs
 create mode 100644 src/bot/handlers/fun/repeator.rs

diff --git a/src/bot/filters/message_fitlers.rs b/src/bot/filters/message_fitlers.rs
index f2d973d..6533d4a 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 3f81a21..acc4306 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 0000000..2edd4a7
--- /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 8d24276..46355f6 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 0000000..20398e3
--- /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 1288014..5ddd382 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>>>,
-- 
GitLab