diff --git a/Cargo.lock b/Cargo.lock index 6a723a6..276aca2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,6 +112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -136,6 +137,23 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "crates_io_api" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0346712abc6ffc5b287637815c838f6126ff62df37f12806fa029e66c0ec285c" +dependencies = [ + "chrono", + "futures", + "reqwest", + "serde", + "serde_derive", + "serde_json", + "serde_path_to_error", + "tokio", + "url", +] + [[package]] name = "darling" version = "0.13.4" @@ -941,11 +959,13 @@ dependencies = [ name = "rustina" version = "0.1.0" dependencies = [ + "crates_io_api", "log", "pretty_env_logger", "teloxide", "tokio", "url", + "uuid", ] [[package]] @@ -1042,6 +1062,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 02a2472..980c21c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,5 @@ log = "0.4" pretty_env_logger = "0.5.0" tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } url = "2.4.1" +crates_io_api = "0.8.2" +uuid = { version = "1.5.0", features = ["v4"] } diff --git a/src/functions/about.rs b/src/functions/about.rs index e1ef996..880b3ba 100644 --- a/src/functions/about.rs +++ b/src/functions/about.rs @@ -1,4 +1,4 @@ -use crate::{utils::kbmng::Keyboard, hooks}; +use crate::{hooks, utils::kbmng::Keyboard}; use teloxide::{ payloads::SendMessageSetters, prelude::*, @@ -13,10 +13,7 @@ Bizning botimiz aktiv tarzda shakllantirib boriladi. Buning ustida esa bir necha pub fn keyboard() -> InlineKeyboardMarkup { let mut keyboard = Keyboard::new(); - keyboard.url( - "Ochiq Havolalar", - "https://github.com/rust-lang-uz/rustina", - ) + keyboard.url("Ochiq Havolalar", "https://github.com/rust-lang-uz/rustina") } pub async fn command(bot: &Bot, msg: &Message) -> ResponseResult<()> { @@ -24,7 +21,7 @@ pub async fn command(bot: &Bot, msg: &Message) -> ResponseResult<()> { return { hooks::is_private(bot, msg).await.unwrap(); Ok(()) - } + }; } bot.send_message(msg.chat.id, TEXT) diff --git a/src/functions/inline.rs b/src/functions/inline.rs new file mode 100644 index 0000000..d2d6517 --- /dev/null +++ b/src/functions/inline.rs @@ -0,0 +1,88 @@ +use crates_io_api::AsyncClient; +use crates_io_api::CratesQuery; +use std::error::Error; +use teloxide::prelude::*; +use teloxide::types::*; + +use crate::utils::inmgr::*; + +pub async fn inline( + bot: Bot, + crates_client: AsyncClient, + q: InlineQuery, +) -> Result<(), Box> { + if q.query.is_empty() { + return { + bot.answer_inline_query( + q.id, + vec![InlineQueryResultArticle::new( + "101", + "Qidirishni boshlang!", + InputMessageContent::Text( + InputMessageContentText::new(NO_INPUT) + .parse_mode(ParseMode::Html) + .disable_web_page_preview(true), + ), + ) + .reply_markup(err_keyboard()) + .into()], + ) + .await?; + Ok(()) + }; + } + + let query = CratesQuery::builder() + .search(q.query.clone()) + .page(1) + .page_size(50) + .build(); + + let request = crates_client.crates(query).await.unwrap().crates; + + if request.is_empty() { + return { + bot.answer_inline_query( + q.id, + vec![InlineQueryResultArticle::new( + "404", + "Xatolik yuz berdi!", + InputMessageContent::Text( + InputMessageContentText::new( + format!("{} ga oid natija mavjud emas!\n{}", + q.query.clone(), "Iltimos, boshqattan ushbu qidirmoqchi bo'lgan paketingiz yozib qidirib ko'ring!") + ) + .parse_mode(ParseMode::Html) + .disable_web_page_preview(true), + ), + ) + .reply_markup(err_keyboard()) + .into()], + ) + .await?; + Ok(()) + }; + } + + let mut result: Vec = Vec::new(); + + for c in request.iter() { + result.push(InlineQueryResult::Article( + InlineQueryResultArticle::new( + uuid::Uuid::new_v4(), + c.name.clone(), + InputMessageContent::Text( + InputMessageContentText::new(view_generate(c)) + .parse_mode(ParseMode::Html) + .disable_web_page_preview(true), + ), + ) + .description(c.description.clone().unwrap()) + .url(url::Url::parse(&format!("https://crates.io/crates/{}", c.id)).unwrap()) + .into(), + )); + } + + bot.answer_inline_query(q.id, result).send().await?; + Ok(()) +} diff --git a/src/functions/mod.rs b/src/functions/mod.rs index 3016f01..30a7650 100644 --- a/src/functions/mod.rs +++ b/src/functions/mod.rs @@ -1,12 +1,14 @@ +pub mod about; pub mod help; +pub mod inline; pub mod rules; pub mod start; -pub mod about; -pub use teloxide::prelude::*; +pub use inline::inline; use crate::Command; use std::error::Error; +use teloxide::prelude::*; pub async fn commands( bot: Bot, diff --git a/src/functions/rules.rs b/src/functions/rules.rs index fae6f6b..79a3220 100644 --- a/src/functions/rules.rs +++ b/src/functions/rules.rs @@ -21,10 +21,7 @@ Iltimos qoidalarga oz bo'lsada vaqt ajratishni unutmang, bu muhim! Ushbu guruhda pub fn keyboard() -> InlineKeyboardMarkup { let mut keyboard = Keyboard::new(); - keyboard.url( - "Guruhga qaytish", - "https://t.me/rustlanguz", - ) + keyboard.url("Guruhga qaytish", "https://t.me/rustlanguz") } pub async fn command(bot: &Bot, msg: &Message) -> ResponseResult<()> { @@ -32,7 +29,7 @@ pub async fn command(bot: &Bot, msg: &Message) -> ResponseResult<()> { return { hooks::is_private(bot, msg).await.unwrap(); Ok(()) - } + }; } bot.send_message(msg.chat.id, TEXT) diff --git a/src/functions/start.rs b/src/functions/start.rs index b6bfd36..a80f014 100644 --- a/src/functions/start.rs +++ b/src/functions/start.rs @@ -13,14 +13,8 @@ Sizni ko'rib turganimdan bag'oyatda xursandman. Men O'zbek Rust jamiyati tomonid pub fn keyboard() -> InlineKeyboardMarkup { let mut keyboard = Keyboard::new(); - keyboard.url( - "Jamiyat", - "https://t.me/rustlanguz", - ); - keyboard.url( - "Web Sahifa", - "https://rust-lang.uz", - ) + keyboard.url("Jamiyat", "https://t.me/rustlanguz"); + keyboard.url("Web Sahifa", "https://rust-lang.uz") } pub async fn command(bot: &Bot, msg: &Message) -> ResponseResult<()> { diff --git a/src/lib.rs b/src/lib.rs index 2432a81..d5f1ff8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,8 @@ pub enum Command { pub fn handler() -> UpdateHandler> { dptree::entry() + // Inline Queries + .branch(Update::filter_inline_query().endpoint(functions::inline)) // Commands .branch( Update::filter_message() diff --git a/src/main.rs b/src/main.rs index f35c6b9..80099d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use crates_io_api::AsyncClient; use rustina::handler; use std::error::Error; use teloxide::prelude::*; @@ -9,7 +10,14 @@ async fn main() -> Result<(), Box> { let bot = Bot::from_env(); + let crates_client = AsyncClient::new( + "Rustina Assistant (rust@maid.uz)", + std::time::Duration::from_millis(100), + ) + .unwrap(); + Dispatcher::builder(bot, handler()) + .dependencies(dptree::deps![crates_client]) // If no handler succeeded to handle an update, this closure will be called .default_handler(|upd| async move { log::warn!("Unhandled update: {:?}", upd); diff --git a/src/utils/inmgr.rs b/src/utils/inmgr.rs new file mode 100644 index 0000000..c14fb77 --- /dev/null +++ b/src/utils/inmgr.rs @@ -0,0 +1,60 @@ +use super::kbmng::Keyboard; +use crates_io_api::Crate; +use teloxide::types::*; + +pub static NO_INPUT: &str = r#" +Salom foydalanuvchi! + +Siz inline rejim ishga tushurdingiz. Ushbu qulaylik yordamida siz crates.io Rust dasturlash tili paketlar registridan web sahifani ishlatmasdan turib telegram o'zida kerakli paketlarni qidirishingiz mumkin! Ushbu qulaylik yozish uchun o'zimizning API SDK ishlatildi. + +Qidirishni boshlash uchun: +@rustaceanbot <nomi> +"#; + +pub fn view_generate(c: &Crate) -> String { + let mut result = String::from("🦀 Rust Telegram Cratelari 🦀\n\n"); + + result.push_str(&format!("📦 Nomi: {}\n", c.name)); + result.push_str(&format!( + "🚨 Oxirgi versiya: {}\n", + c.max_version + )); + result.push_str(&format!( + "🎚 Yuklab olingan: yaqin orada: {} | hammasi: {}\n", + c.recent_downloads.unwrap(), + c.downloads + )); + result.push_str(&format!( + "⌚️ Yaratilgan: {}", + c.created_at.naive_local().to_string() + )); + result.push_str(&format!( + "📡 Yangilangan: {}\n", + c.updated_at.date_naive().to_string() + )); + result.push_str(&format!( + "📰 Ma'lumot: {}{}\n\n", + if c.description.clone().unwrap().len() > 100 { + c.description + .clone() + .unwrap() + .chars() + .take(100) + .collect::() + } else { + c.description.clone().unwrap() + }, + if c.description.clone().unwrap().len() > 100 { + "..." + } else { + "" + } + )); + + result +} + +pub fn err_keyboard() -> InlineKeyboardMarkup { + let mut keyboard = Keyboard::new(); + keyboard.switch_inline_current("Qayta urinib ko'ramizmi?", "rand") +} diff --git a/src/utils/kbmng.rs b/src/utils/kbmng.rs index d74eab6..d3783a4 100644 --- a/src/utils/kbmng.rs +++ b/src/utils/kbmng.rs @@ -41,6 +41,15 @@ impl Keyboard { self.get() } + pub fn switch_inline_current(&mut self, text: &str, query: &str) -> InlineKeyboardMarkup { + self.keyboard + .last_mut() + .unwrap() + .push(InlineKeyboardButton::switch_inline_query_current_chat(text, query)); + + self.get() + } + /// Add next buttons from new line pub fn row(&mut self) -> InlineKeyboardMarkup { self.keyboard.push(vec![]); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e93e8bb..3c91cd1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,2 @@ +pub mod inmgr; pub mod kbmng;