Documentation handler and callback query dispatcher
This commit is contained in:
		
							
								
								
									
										4
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -357,6 +357,10 @@ dependencies = [
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "log",
 | 
			
		||||
 "pretty_env_logger",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "reqwest",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "teloxide",
 | 
			
		||||
 "tokio",
 | 
			
		||||
 "url",
 | 
			
		||||
 | 
			
		||||
@ -13,3 +13,7 @@ tokio = { version =  "1.8", features = ["rt-multi-thread", "macros"] }
 | 
			
		||||
lazy_static = "1.4.0"
 | 
			
		||||
url = "2.3.1"
 | 
			
		||||
dotenv = "0.15.0"
 | 
			
		||||
regex = "1"
 | 
			
		||||
reqwest = "0.11.12"
 | 
			
		||||
serde = "1.0.145"
 | 
			
		||||
serde_json = "1.0.86"
 | 
			
		||||
@ -39,6 +39,9 @@ impl Godette {
 | 
			
		||||
    pub fn create_handler(
 | 
			
		||||
        &self,
 | 
			
		||||
    ) -> Handler<'static, DependencyMap, Result<(), RequestError>, DpHandlerDescription> {
 | 
			
		||||
        dptree::entry()
 | 
			
		||||
            .branch(Update::filter_callback_query().endpoint(Godette::callback_dispatcher))
 | 
			
		||||
            .branch(
 | 
			
		||||
                Update::filter_message()
 | 
			
		||||
                    // User commands
 | 
			
		||||
                    .branch(
 | 
			
		||||
@ -62,6 +65,7 @@ impl Godette {
 | 
			
		||||
                                .unwrap_or_default()
 | 
			
		||||
                        })
 | 
			
		||||
                        .endpoint(Godette::message_dispatcher),
 | 
			
		||||
                    ),
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,9 @@
 | 
			
		||||
use teloxide::prelude::*;
 | 
			
		||||
 | 
			
		||||
use super::{handlers, utils, Godette, KarmaTrigger, Trigger};
 | 
			
		||||
use super::{handlers, utils, Godette};
 | 
			
		||||
use crate::commands::{AdminCommand, Command};
 | 
			
		||||
use lazy_static::lazy_static;
 | 
			
		||||
use regex::Regex;
 | 
			
		||||
 | 
			
		||||
impl Godette {
 | 
			
		||||
    pub async fn commands_dispatcher(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
 | 
			
		||||
@ -40,12 +42,27 @@ impl Godette {
 | 
			
		||||
            }
 | 
			
		||||
            None => (),
 | 
			
		||||
        };
 | 
			
		||||
        let text = utils::get_text_or_empty(&msg).to_lowercase();
 | 
			
		||||
        match text.find("оффтоп") {
 | 
			
		||||
        let text = utils::get_text_or_empty(&msg);
 | 
			
		||||
        match text.to_lowercase().find("оффтоп") {
 | 
			
		||||
            Some(_) => handlers::offtop(&bot, &msg).await?,
 | 
			
		||||
            None => (),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        lazy_static! {
 | 
			
		||||
            static ref DOC_RE: Regex =
 | 
			
		||||
                Regex::new(r"(?i)док(ументац[а-я]+|[а-я])? ((п)?о )?(?P<topic>@?[\w\d]{1,32})")
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(caps) = DOC_RE.captures(&text) {
 | 
			
		||||
            match caps.name("topic") {
 | 
			
		||||
                Some(topic) => {
 | 
			
		||||
                    handlers::documentation(&bot, &msg, String::from(topic.as_str())).await?
 | 
			
		||||
                }
 | 
			
		||||
                None => (),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -55,4 +72,17 @@ impl Godette {
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn callback_dispatcher(bot: Bot, q: CallbackQuery) -> ResponseResult<()> {
 | 
			
		||||
        if let Some(data) = q.data {
 | 
			
		||||
            bot.answer_callback_query(q.id).await?;
 | 
			
		||||
 | 
			
		||||
            if data == "no_thanks" {
 | 
			
		||||
                if let Some(Message { id, chat, .. }) = q.message {
 | 
			
		||||
                    bot.delete_message(chat.id, id).await?;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ use teloxide::{
 | 
			
		||||
    utils::command::BotCommands,
 | 
			
		||||
    utils::markdown::{self, bold, escape, italic},
 | 
			
		||||
};
 | 
			
		||||
use url::Url;
 | 
			
		||||
 | 
			
		||||
use crate::commands::{AdminCommand, Command};
 | 
			
		||||
 | 
			
		||||
@ -114,3 +115,38 @@ pub async fn offtop(bot: &Bot, msg: &Message) -> ResponseResult<()> {
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn documentation(bot: &Bot, msg: &Message, topic: String) -> ResponseResult<()> {
 | 
			
		||||
    let mut text = format!(
 | 
			
		||||
        "Извините, по запросу \"{}\" ничего не найдено\\.",
 | 
			
		||||
        escape(&topic)
 | 
			
		||||
    );
 | 
			
		||||
    let btn_text = format!("Поиск \"{}\"", topic);
 | 
			
		||||
    let btn_url = Url::parse(&format!(
 | 
			
		||||
        "https://docs.godotengine.org/ru/stable/search.html?q={}",
 | 
			
		||||
        topic
 | 
			
		||||
    ))
 | 
			
		||||
    .unwrap();
 | 
			
		||||
    let results = utils::request_docs(&topic).await;
 | 
			
		||||
 | 
			
		||||
    if results.len() > 0 {
 | 
			
		||||
        let links = results
 | 
			
		||||
            .iter()
 | 
			
		||||
            .take(10)
 | 
			
		||||
            .map(|res| format!("\\- [{}]({})", escape(&res.title), res.path))
 | 
			
		||||
            .collect::<Vec<String>>()
 | 
			
		||||
            .join("\n");
 | 
			
		||||
        text = format!(
 | 
			
		||||
            "Вот что удалось мне найти в документации по запроу {}:\n\n{}",
 | 
			
		||||
            bold(&escape(&topic)),
 | 
			
		||||
            links
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bot.send_message(msg.chat.id, text)
 | 
			
		||||
        .parse_mode(MarkdownV2)
 | 
			
		||||
        .reply_markup(utils::make_docs_keyboard(btn_text, btn_url))
 | 
			
		||||
        .reply_to_message_id(msg.id)
 | 
			
		||||
        .await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup, Message};
 | 
			
		||||
use url::Url;
 | 
			
		||||
 | 
			
		||||
@ -7,8 +8,60 @@ pub fn get_text_or_empty(msg: &Message) -> String {
 | 
			
		||||
        .to_string()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn no_thanks_button() -> InlineKeyboardButton {
 | 
			
		||||
    InlineKeyboardButton::callback("Спасибо, не надо", "no_thanks")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn make_offtop_keyboard() -> InlineKeyboardMarkup {
 | 
			
		||||
    let link = Url::parse("https://t.me/Godot_Engine_Offtop").unwrap();
 | 
			
		||||
    let button = InlineKeyboardButton::url("Godot Engine оффтоп чат".to_owned(), link);
 | 
			
		||||
    return InlineKeyboardMarkup::new(vec![[button]]);
 | 
			
		||||
    InlineKeyboardMarkup::new(vec![[button], [no_thanks_button()]])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn make_docs_keyboard(text: String, url: Url) -> InlineKeyboardMarkup {
 | 
			
		||||
    let button = InlineKeyboardButton::url(text, url);
 | 
			
		||||
    InlineKeyboardMarkup::new(vec![[button], [no_thanks_button()]])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug)]
 | 
			
		||||
struct DocResponse {
 | 
			
		||||
    count: u32,
 | 
			
		||||
    next: Option<String>,
 | 
			
		||||
    previous: Option<String>,
 | 
			
		||||
    results: Vec<DocResult>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize, Debug)]
 | 
			
		||||
pub struct DocResult {
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    pub path: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn request_docs(query: &str) -> Vec<DocResult> {
 | 
			
		||||
    let empty_results: Vec<DocResult> = Vec::new();
 | 
			
		||||
    let link = format!(
 | 
			
		||||
        "https://docs.godotengine.org/_/api/v2/search/?q={}&project=godot-ru&version=stable&language=ru",
 | 
			
		||||
        query
 | 
			
		||||
    );
 | 
			
		||||
    let domain = "https://docs.godotengine.org";
 | 
			
		||||
    let response = reqwest::get(link).await;
 | 
			
		||||
    match response {
 | 
			
		||||
        Ok(response) => {
 | 
			
		||||
            let json = response.json::<DocResponse>().await;
 | 
			
		||||
            match json {
 | 
			
		||||
                Ok(json) => {
 | 
			
		||||
                    return json
 | 
			
		||||
                        .results
 | 
			
		||||
                        .iter()
 | 
			
		||||
                        .map(|res| DocResult {
 | 
			
		||||
                            path: format!("{}{}", domain, res.path),
 | 
			
		||||
                            title: res.title.clone(),
 | 
			
		||||
                        })
 | 
			
		||||
                        .collect::<Vec<DocResult>>()
 | 
			
		||||
                }
 | 
			
		||||
                Err(_) => empty_results,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Err(_) => empty_results,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user