From a13d6dc0cacec9a02a1b9d269e2e1ed9bfc87658 Mon Sep 17 00:00:00 2001 From: Nefrace Date: Tue, 18 Oct 2022 15:17:11 +0300 Subject: [PATCH] Documentation handler and callback query dispatcher --- Cargo.lock | 4 +++ Cargo.toml | 6 ++++- src/godette.rs | 48 ++++++++++++++++++--------------- src/godette/dispatchers.rs | 36 ++++++++++++++++++++++--- src/godette/handlers.rs | 36 +++++++++++++++++++++++++ src/godette/utils.rs | 55 +++++++++++++++++++++++++++++++++++++- 6 files changed, 158 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e47109c..4f8953b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,10 @@ dependencies = [ "lazy_static", "log", "pretty_env_logger", + "regex", + "reqwest", + "serde", + "serde_json", "teloxide", "tokio", "url", diff --git a/Cargo.toml b/Cargo.toml index dcc23fb..fcead6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,8 @@ pretty_env_logger = "0.4" tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } lazy_static = "1.4.0" url = "2.3.1" -dotenv = "0.15.0" \ No newline at end of file +dotenv = "0.15.0" +regex = "1" +reqwest = "0.11.12" +serde = "1.0.145" +serde_json = "1.0.86" \ No newline at end of file diff --git a/src/godette.rs b/src/godette.rs index 6f1b0b3..fed2fe1 100644 --- a/src/godette.rs +++ b/src/godette.rs @@ -39,29 +39,33 @@ impl Godette { pub fn create_handler( &self, ) -> Handler<'static, DependencyMap, Result<(), RequestError>, DpHandlerDescription> { - Update::filter_message() - // User commands + dptree::entry() + .branch(Update::filter_callback_query().endpoint(Godette::callback_dispatcher)) .branch( - dptree::entry() - .filter_command::() - .endpoint(Godette::commands_dispatcher), - ) - // Admin commands - .branch( - dptree::entry() - .filter_command::() - .endpoint(Godette::admin_dispatcher), - ) - // Replies - // .branch(Message::filter_reply_to_message().endpoint(Godette::reply_dispatcher)) - // Messages - .branch( - dptree::filter(|msg: Message| { - msg.from() - .map(|user| user.id == UserId(60441930)) - .unwrap_or_default() - }) - .endpoint(Godette::message_dispatcher), + Update::filter_message() + // User commands + .branch( + dptree::entry() + .filter_command::() + .endpoint(Godette::commands_dispatcher), + ) + // Admin commands + .branch( + dptree::entry() + .filter_command::() + .endpoint(Godette::admin_dispatcher), + ) + // Replies + // .branch(Message::filter_reply_to_message().endpoint(Godette::reply_dispatcher)) + // Messages + .branch( + dptree::filter(|msg: Message| { + msg.from() + .map(|user| user.id == UserId(60441930)) + .unwrap_or_default() + }) + .endpoint(Godette::message_dispatcher), + ), ) } } diff --git a/src/godette/dispatchers.rs b/src/godette/dispatchers.rs index e9a4471..94edea2 100644 --- a/src/godette/dispatchers.rs +++ b/src/godette/dispatchers.rs @@ -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@?[\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(()) + } } diff --git a/src/godette/handlers.rs b/src/godette/handlers.rs index 10bc9a4..5fb1afa 100644 --- a/src/godette/handlers.rs +++ b/src/godette/handlers.rs @@ -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::>() + .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(()) +} diff --git a/src/godette/utils.rs b/src/godette/utils.rs index 7afe156..68b8a20 100644 --- a/src/godette/utils.rs +++ b/src/godette/utils.rs @@ -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, + previous: Option, + results: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct DocResult { + pub title: String, + pub path: String, +} + +pub async fn request_docs(query: &str) -> Vec { + let empty_results: Vec = 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::().await; + match json { + Ok(json) => { + return json + .results + .iter() + .map(|res| DocResult { + path: format!("{}{}", domain, res.path), + title: res.title.clone(), + }) + .collect::>() + } + Err(_) => empty_results, + } + } + Err(_) => empty_results, + } }