Compare commits
14 Commits
03e42c9108
...
ec49ab24b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec49ab24b6 | ||
|
|
a51344d089 | ||
|
|
fd3ef221c9 | ||
|
|
ffe2fd8bc1 | ||
|
|
1c9ab867ed | ||
|
|
c11e671b15 | ||
|
|
55d393eb2c | ||
|
|
3761cc6fa8 | ||
|
|
091c0a436e | ||
|
|
982b6c8ff0 | ||
|
|
329ead4f6e | ||
|
|
8b371ebb2f | ||
|
|
adfd019166 | ||
|
|
2aff289a96 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
/target
|
||||
.env
|
||||
result
|
||||
**/.DS_Store
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -839,9 +839,11 @@ dependencies = [
|
||||
"enum_stringify",
|
||||
"envconfig",
|
||||
"futures",
|
||||
"log",
|
||||
"mongodb",
|
||||
"serde",
|
||||
"teloxide",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
||||
@ -14,7 +14,13 @@ dotenvy = "0.15.7"
|
||||
enum_stringify = "0.6.3"
|
||||
envconfig = "0.11.0"
|
||||
futures = "0.3.31"
|
||||
log = "0.4.27"
|
||||
mongodb = "3.2.3"
|
||||
serde = { version = "1.0.219", features = ["derive", "serde_derive"] }
|
||||
teloxide = { version = "0.14.0", features = ["macros", "postgres-storage-nativetls"] }
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.44.1", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[lints.clippy]
|
||||
print_stdout = "warn"
|
||||
unwrap_used = "warn"
|
||||
|
||||
45
src/admin.rs
45
src/admin.rs
@ -3,8 +3,12 @@ use teloxide::{
|
||||
utils::{command::BotCommands, render::RenderMessageTextHelper},
|
||||
};
|
||||
|
||||
use crate::db::{CallDB, DB};
|
||||
use crate::LogMsg;
|
||||
use crate::{
|
||||
db::{CallDB, DB},
|
||||
BotResult,
|
||||
};
|
||||
use log::info;
|
||||
|
||||
// These are should not appear in /help
|
||||
#[derive(BotCommands, Clone)]
|
||||
@ -30,9 +34,15 @@ pub async fn admin_command_handler(
|
||||
bot: Bot,
|
||||
msg: Message,
|
||||
cmd: AdminCommands,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
let tguser = msg.from.clone().unwrap();
|
||||
println!("MSG: {}", msg.html_text().unwrap());
|
||||
) -> BotResult<()> {
|
||||
let tguser = match msg.from.clone() {
|
||||
Some(user) => user,
|
||||
None => return Ok(()), // do nothing, cause its not usecase of function
|
||||
};
|
||||
info!(
|
||||
"MSG: {}",
|
||||
msg.html_text().unwrap_or("|EMPTY_MESSAGE|".into())
|
||||
);
|
||||
match cmd {
|
||||
AdminCommands::MyId => {
|
||||
bot.send_message(msg.chat.id, format!("Your ID is: {}", tguser.id))
|
||||
@ -54,7 +64,7 @@ pub async fn admin_command_handler(
|
||||
Ok(())
|
||||
}
|
||||
AdminCommands::Deop => {
|
||||
db.set_admin(tguser.id.0 as i64, false).await;
|
||||
db.set_admin(tguser.id.0 as i64, false).await?;
|
||||
bot.send_message(msg.chat.id, "You are not an admin anymore")
|
||||
.await?;
|
||||
Ok(())
|
||||
@ -69,22 +79,27 @@ pub async fn secret_command_handler(
|
||||
msg: Message,
|
||||
cmd: SecretCommands,
|
||||
admin_password: String,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
println!("Admin Pass: {}", admin_password);
|
||||
let tguser = msg.from.clone().unwrap();
|
||||
) -> BotResult<()> {
|
||||
info!("Admin Pass: {}", admin_password);
|
||||
let tguser = match msg.from.clone() {
|
||||
Some(user) => user,
|
||||
None => return Ok(()), // do nothing, cause its not usecase of function
|
||||
};
|
||||
let user = db
|
||||
.get_or_init_user(tguser.id.0 as i64, &tguser.first_name)
|
||||
.await;
|
||||
println!("MSG: {}", msg.html_text().unwrap());
|
||||
.await?;
|
||||
info!(
|
||||
"MSG: {}",
|
||||
msg.html_text().unwrap_or("|EMPTY_MESSAGE|".into())
|
||||
);
|
||||
match cmd {
|
||||
SecretCommands::Secret { pass } => {
|
||||
if user.is_admin == true {
|
||||
bot.send_message(msg.from.unwrap().id, "You are an admin already")
|
||||
if user.is_admin {
|
||||
bot.send_message(tguser.id, "You are an admin already")
|
||||
.await?;
|
||||
} else if pass == admin_password {
|
||||
db.set_admin(user.id, true).await;
|
||||
bot.send_message(msg.from.unwrap().id, "You are an admin now!")
|
||||
.await?;
|
||||
db.set_admin(user.id, true).await?;
|
||||
bot.send_message(tguser.id, "You are an admin now!").await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
109
src/db/mod.rs
109
src/db/mod.rs
@ -1,7 +1,7 @@
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enum_stringify::EnumStringify;
|
||||
use futures::stream::{StreamExt, TryStreamExt};
|
||||
use futures::stream::TryStreamExt;
|
||||
|
||||
use mongodb::options::IndexOptions;
|
||||
use mongodb::{bson::doc, options::ClientOptions, Client};
|
||||
@ -38,7 +38,7 @@ pub struct User {
|
||||
macro_rules! query_call {
|
||||
($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => {
|
||||
pub async fn $func_name<D: CallDB>(&$self, $db: &mut D)
|
||||
-> Result<$return_type, Box<dyn std::error::Error>> $body
|
||||
-> DbResult<$return_type> $body
|
||||
};
|
||||
}
|
||||
|
||||
@ -101,14 +101,14 @@ pub struct DB {
|
||||
}
|
||||
|
||||
impl DB {
|
||||
pub async fn new<S: Into<String>>(db_url: S) -> Self {
|
||||
let options = ClientOptions::parse(db_url.into()).await.unwrap();
|
||||
let client = Client::with_options(options).unwrap();
|
||||
pub async fn new<S: Into<String>>(db_url: S) -> DbResult<Self> {
|
||||
let options = ClientOptions::parse(db_url.into()).await?;
|
||||
let client = Client::with_options(options)?;
|
||||
|
||||
DB { client }
|
||||
Ok(DB { client })
|
||||
}
|
||||
|
||||
pub async fn migrate(&mut self) -> Result<(), mongodb::error::Error> {
|
||||
pub async fn migrate(&mut self) -> DbResult<()> {
|
||||
let events = self.get_database().await.collection::<Event>("events");
|
||||
events
|
||||
.create_index(
|
||||
@ -121,8 +121,8 @@ impl DB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn init<S: Into<String>>(db_url: S) -> Result<Self, mongodb::error::Error> {
|
||||
let mut db = Self::new(db_url).await;
|
||||
pub async fn init<S: Into<String>>(db_url: S) -> DbResult<Self> {
|
||||
let mut db = Self::new(db_url).await?;
|
||||
db.migrate().await?;
|
||||
|
||||
Ok(db)
|
||||
@ -136,24 +136,22 @@ impl CallDB for DB {
|
||||
}
|
||||
}
|
||||
|
||||
pub type DbError = mongodb::error::Error;
|
||||
pub type DbResult<T> = Result<T, DbError>;
|
||||
|
||||
#[async_trait]
|
||||
pub trait CallDB {
|
||||
//type C;
|
||||
async fn get_database(&mut self) -> Database;
|
||||
//async fn get_pool(&mut self) -> PooledConnection<'_, AsyncDieselConnectionManager<C>>;
|
||||
async fn get_users(&mut self) -> Vec<User> {
|
||||
async fn get_users(&mut self) -> DbResult<Vec<User>> {
|
||||
let db = self.get_database().await;
|
||||
let users = db.collection::<User>("users");
|
||||
users
|
||||
.find(doc! {})
|
||||
.await
|
||||
.unwrap()
|
||||
.map(|u| u.unwrap())
|
||||
.collect()
|
||||
.await
|
||||
|
||||
users.find(doc! {}).await?.try_collect().await
|
||||
}
|
||||
|
||||
async fn set_admin(&mut self, userid: i64, isadmin: bool) {
|
||||
async fn set_admin(&mut self, userid: i64, isadmin: bool) -> DbResult<()> {
|
||||
let db = self.get_database().await;
|
||||
let users = db.collection::<User>("users");
|
||||
users
|
||||
@ -165,11 +163,12 @@ pub trait CallDB {
|
||||
"$set": { "is_admin": isadmin }
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_or_init_user(&mut self, userid: i64, firstname: &str) -> User {
|
||||
async fn get_or_init_user(&mut self, userid: i64, firstname: &str) -> DbResult<User> {
|
||||
let db = self.get_database().await;
|
||||
let users = db.collection::<User>("users");
|
||||
|
||||
@ -182,21 +181,15 @@ pub trait CallDB {
|
||||
},
|
||||
)
|
||||
.upsert(true)
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
|
||||
users
|
||||
Ok(users
|
||||
.find_one(doc! { "id": userid })
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("no such user created")
|
||||
.await?
|
||||
.expect("no such user created"))
|
||||
}
|
||||
|
||||
async fn get_message(
|
||||
&mut self,
|
||||
chatid: i64,
|
||||
messageid: i32,
|
||||
) -> Result<Option<Message>, Box<dyn std::error::Error>> {
|
||||
async fn get_message(&mut self, chatid: i64, messageid: i32) -> DbResult<Option<Message>> {
|
||||
let db = self.get_database().await;
|
||||
let messages = db.collection::<Message>("messages");
|
||||
|
||||
@ -211,7 +204,7 @@ pub trait CallDB {
|
||||
&mut self,
|
||||
chatid: i64,
|
||||
messageid: i32,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
) -> DbResult<Option<String>> {
|
||||
let msg = self.get_message(chatid, messageid).await?;
|
||||
Ok(msg.map(|m| m.token))
|
||||
}
|
||||
@ -221,7 +214,7 @@ pub trait CallDB {
|
||||
chatid: i64,
|
||||
messageid: i32,
|
||||
literal: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
) -> DbResult<()> {
|
||||
let db = self.get_database().await;
|
||||
let messages = db.collection::<Message>("messages");
|
||||
|
||||
@ -241,10 +234,7 @@ pub trait CallDB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_literal(
|
||||
&mut self,
|
||||
literal: &str,
|
||||
) -> Result<Option<Literal>, Box<dyn std::error::Error>> {
|
||||
async fn get_literal(&mut self, literal: &str) -> DbResult<Option<Literal>> {
|
||||
let db = self.get_database().await;
|
||||
let messages = db.collection::<Literal>("literals");
|
||||
|
||||
@ -253,20 +243,13 @@ pub trait CallDB {
|
||||
Ok(literal)
|
||||
}
|
||||
|
||||
async fn get_literal_value(
|
||||
&mut self,
|
||||
literal: &str,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
async fn get_literal_value(&mut self, literal: &str) -> DbResult<Option<String>> {
|
||||
let literal = self.get_literal(literal).await?;
|
||||
|
||||
Ok(literal.map(|l| l.value))
|
||||
}
|
||||
|
||||
async fn set_literal(
|
||||
&mut self,
|
||||
literal: &str,
|
||||
valuestr: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn set_literal(&mut self, literal: &str, valuestr: &str) -> DbResult<()> {
|
||||
let db = self.get_database().await;
|
||||
let literals = db.collection::<Literal>("literals");
|
||||
|
||||
@ -281,23 +264,14 @@ pub trait CallDB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_all_events(&mut self) -> Vec<Event> {
|
||||
async fn get_all_events(&mut self) -> DbResult<Vec<Event>> {
|
||||
let db = self.get_database().await;
|
||||
let events = db.collection::<Event>("events");
|
||||
|
||||
events
|
||||
.find(doc! {})
|
||||
.await
|
||||
.unwrap()
|
||||
.map(|e| e.unwrap())
|
||||
.collect()
|
||||
.await
|
||||
events.find(doc! {}).await?.try_collect().await
|
||||
}
|
||||
|
||||
async fn create_event(
|
||||
&mut self,
|
||||
event_datetime: chrono::DateTime<Utc>,
|
||||
) -> Result<Event, Box<dyn std::error::Error>> {
|
||||
async fn create_event(&mut self, event_datetime: chrono::DateTime<Utc>) -> DbResult<Event> {
|
||||
let db = self.get_database().await;
|
||||
let events = db.collection::<Event>("events");
|
||||
|
||||
@ -311,7 +285,7 @@ pub trait CallDB {
|
||||
Ok(new_event)
|
||||
}
|
||||
|
||||
async fn get_media(&mut self, literal: &str) -> Result<Vec<Media>, Box<dyn std::error::Error>> {
|
||||
async fn get_media(&mut self, literal: &str) -> DbResult<Vec<Media>> {
|
||||
let db = self.get_database().await;
|
||||
let media = db.collection::<Media>("media");
|
||||
|
||||
@ -324,10 +298,7 @@ pub trait CallDB {
|
||||
Ok(media_items)
|
||||
}
|
||||
|
||||
async fn is_media_group_exists(
|
||||
&mut self,
|
||||
media_group: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
async fn is_media_group_exists(&mut self, media_group: &str) -> DbResult<bool> {
|
||||
let db = self.get_database().await;
|
||||
let media = db.collection::<Media>("media");
|
||||
|
||||
@ -339,7 +310,7 @@ pub trait CallDB {
|
||||
Ok(is_exists)
|
||||
}
|
||||
|
||||
async fn drop_media(&mut self, literal: &str) -> Result<usize, Box<dyn std::error::Error>> {
|
||||
async fn drop_media(&mut self, literal: &str) -> DbResult<usize> {
|
||||
let db = self.get_database().await;
|
||||
let media = db.collection::<Media>("media");
|
||||
|
||||
@ -351,11 +322,7 @@ pub trait CallDB {
|
||||
Ok(deleted_count as usize)
|
||||
}
|
||||
|
||||
async fn drop_media_except(
|
||||
&mut self,
|
||||
literal: &str,
|
||||
except_group: &str,
|
||||
) -> Result<usize, Box<dyn std::error::Error>> {
|
||||
async fn drop_media_except(&mut self, literal: &str, except_group: &str) -> DbResult<usize> {
|
||||
let db = self.get_database().await;
|
||||
let media = db.collection::<Media>("media");
|
||||
|
||||
@ -376,7 +343,7 @@ pub trait CallDB {
|
||||
mediatype: &str,
|
||||
fileid: &str,
|
||||
media_group: Option<&str>,
|
||||
) -> Result<Media, Box<dyn std::error::Error>> {
|
||||
) -> DbResult<Media> {
|
||||
let db = self.get_database().await;
|
||||
let media = db.collection::<Media>("media");
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use dotenvy;
|
||||
|
||||
use super::CallDB;
|
||||
@ -6,21 +8,20 @@ use super::DB;
|
||||
async fn setup_db() -> DB {
|
||||
dotenvy::dotenv().unwrap();
|
||||
let db_url = std::env::var("DATABASE_URL").unwrap();
|
||||
let db = DB::new(db_url).await;
|
||||
|
||||
db
|
||||
DB::new(db_url).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_media() {
|
||||
let mut db = setup_db().await;
|
||||
|
||||
let result = db.drop_media("test_get_media_literal").await.unwrap();
|
||||
let _result = db.drop_media("test_get_media_literal").await.unwrap();
|
||||
|
||||
let media_items = db.get_media("test_get_media_literal").await.unwrap();
|
||||
assert_eq!(media_items.len(), 0);
|
||||
|
||||
let result = db
|
||||
let _result = db
|
||||
.add_media("test_get_media_literal", "photo", "file_id_1", None)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -28,7 +29,7 @@ async fn test_get_media() {
|
||||
let media_items = db.get_media("test_get_media_literal").await.unwrap();
|
||||
assert_eq!(media_items.len(), 1);
|
||||
|
||||
let result = db
|
||||
let _result = db
|
||||
.add_media("test_get_media_literal", "video", "file_id_2", None)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -37,7 +38,7 @@ async fn test_get_media() {
|
||||
assert_eq!(media_items.len(), 2);
|
||||
|
||||
// Clean up after test
|
||||
let result = db.drop_media("test_get_media_literal").await.unwrap();
|
||||
let _result = db.drop_media("test_get_media_literal").await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -48,9 +49,9 @@ async fn test_add_media() {
|
||||
let media_type = "photo";
|
||||
let file_id = "LjaldhAOh";
|
||||
|
||||
let result = db.drop_media(literal).await.unwrap();
|
||||
let _result = db.drop_media(literal).await.unwrap();
|
||||
|
||||
let result = db
|
||||
let _result = db
|
||||
.add_media(literal, media_type, file_id, None)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -63,14 +64,14 @@ async fn test_add_media() {
|
||||
assert_eq!(media_items[0].file_id, file_id);
|
||||
|
||||
// Clean up after test
|
||||
let result = db.drop_media(literal).await.unwrap();
|
||||
let _result = db.drop_media(literal).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_drop_media() {
|
||||
let mut db = setup_db().await;
|
||||
|
||||
let result = db
|
||||
let _result = db
|
||||
.add_media("test_drop_media_literal", "photo", "file_id_1", None)
|
||||
.await
|
||||
.unwrap();
|
||||
@ -79,14 +80,14 @@ async fn test_drop_media() {
|
||||
let media_items = db.get_media("test_drop_media_literal").await.unwrap();
|
||||
assert_eq!(media_items.len(), 1);
|
||||
|
||||
let result = db.drop_media("test_drop_media_literal").await.unwrap();
|
||||
let _result = db.drop_media("test_drop_media_literal").await.unwrap();
|
||||
|
||||
// Verify that the media has been dropped
|
||||
let media_items = db.get_media("test_drop_media_literal").await.unwrap();
|
||||
assert_eq!(media_items.len(), 0);
|
||||
|
||||
// Clean up after test
|
||||
let result = db.drop_media("test_drop_media_literal").await.unwrap();
|
||||
let _result = db.drop_media("test_drop_media_literal").await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -135,7 +136,7 @@ async fn test_drop_media_except() {
|
||||
let media_items = db.get_media(literal).await.unwrap();
|
||||
assert_eq!(media_items.len(), 2);
|
||||
|
||||
let deleted_count = db.drop_media_except(literal, media_group).await.unwrap();
|
||||
let _deleted_count = db.drop_media_except(literal, media_group).await.unwrap();
|
||||
let media_items = db.get_media(literal).await.unwrap();
|
||||
assert_eq!(media_items.len(), 0);
|
||||
|
||||
@ -152,7 +153,7 @@ async fn test_drop_media_except() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let deleted_count = db.drop_media_except(literal, media_group).await.unwrap();
|
||||
let _deleted_count = db.drop_media_except(literal, media_group).await.unwrap();
|
||||
let media_items = db.get_media(literal).await.unwrap();
|
||||
assert_eq!(media_items.len(), 1);
|
||||
let _ = db.drop_media(literal).await.unwrap();
|
||||
@ -166,7 +167,7 @@ async fn test_drop_media_except() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let deleted_count = db.drop_media_except(literal, media_group).await.unwrap();
|
||||
let _deleted_count = db.drop_media_except(literal, media_group).await.unwrap();
|
||||
let media_items = db.get_media(literal).await.unwrap();
|
||||
assert_eq!(media_items.len(), 2);
|
||||
|
||||
|
||||
167
src/main.rs
167
src/main.rs
@ -2,6 +2,7 @@ pub mod admin;
|
||||
pub mod db;
|
||||
pub mod mongodb_storage;
|
||||
|
||||
use log::info;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::admin::{admin_command_handler, AdminCommands};
|
||||
@ -11,10 +12,11 @@ use crate::mongodb_storage::MongodbStorage;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono_tz::Asia;
|
||||
use db::DbError;
|
||||
use envconfig::Envconfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use teloxide::dispatching::dialogue::serializer::Json;
|
||||
use teloxide::dispatching::dialogue::GetChatId;
|
||||
use teloxide::dispatching::dialogue::{GetChatId, Serializer};
|
||||
use teloxide::types::{
|
||||
InlineKeyboardButton, InlineKeyboardMarkup, InputFile, InputMedia, MediaKind, MessageKind,
|
||||
ParseMode, ReplyMarkup,
|
||||
@ -52,7 +54,7 @@ trait LogMsg {
|
||||
|
||||
impl LogMsg for <teloxide::Bot as teloxide::prelude::Requester>::SendMessage {
|
||||
fn log(self) -> Self {
|
||||
println!("msg: {}", self.text);
|
||||
info!("msg: {}", self.text);
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -82,6 +84,21 @@ impl BotController {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum BotError {
|
||||
DBError(#[from] DbError),
|
||||
TeloxideError(#[from] teloxide::RequestError),
|
||||
StorageError(#[from] mongodb_storage::MongodbStorageError<<Json as Serializer<State>>::Error>),
|
||||
}
|
||||
|
||||
pub type BotResult<T> = Result<T, BotError>;
|
||||
|
||||
impl std::fmt::Display for BotError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dotenvy::dotenv()?;
|
||||
@ -91,33 +108,39 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let state_mgr = MongodbStorage::open(config.db_url.clone().as_ref(), "gongbot", Json).await?;
|
||||
|
||||
// TODO: delete this in production
|
||||
let events: Vec<DateTime<Utc>> = vec!["2025-04-09T18:00:00+04:00", "2025-04-11T16:00:00+04:00"]
|
||||
// allow because values are hardcoded and if they will be unparsable
|
||||
// we should panic anyway
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let events: Vec<DateTime<Utc>> = ["2025-04-09T18:00:00+04:00", "2025-04-11T16:00:00+04:00"]
|
||||
.iter()
|
||||
.map(|d| DateTime::parse_from_rfc3339(d).unwrap().into())
|
||||
.collect();
|
||||
|
||||
for event in events {
|
||||
match bc.db.create_event(event).await {
|
||||
Ok(e) => println!("Created event {}", e._id),
|
||||
Err(err) => println!("Failed to create event, error: {}", err),
|
||||
Ok(e) => info!("Created event {}", e._id),
|
||||
Err(err) => info!("Failed to create event, error: {}", err),
|
||||
}
|
||||
}
|
||||
//
|
||||
|
||||
let handler = dptree::entry()
|
||||
.inspect(|u: Update| {
|
||||
eprintln!("{u:#?}"); // Print the update to the console with inspect
|
||||
info!("{u:#?}"); // Print the update to the console with inspect
|
||||
})
|
||||
.branch(Update::filter_callback_query().endpoint(callback_handler))
|
||||
.branch(command_handler(config))
|
||||
.branch(
|
||||
Update::filter_message()
|
||||
.filter_async(async |msg: Message, mut db: DB| {
|
||||
let tguser = msg.from.unwrap();
|
||||
let tguser = match msg.from.clone() {
|
||||
Some(user) => user,
|
||||
None => return false, // do nothing, cause its not usecase of function
|
||||
};
|
||||
let user = db
|
||||
.get_or_init_user(tguser.id.0 as i64, &tguser.first_name)
|
||||
.await;
|
||||
user.is_admin
|
||||
user.map(|u| u.is_admin).unwrap_or(false)
|
||||
})
|
||||
.enter_dialogue::<Message, MongodbStorage<Json>, State>()
|
||||
.branch(
|
||||
@ -148,11 +171,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn callback_handler(
|
||||
bot: Bot,
|
||||
mut db: DB,
|
||||
q: CallbackQuery,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<()> {
|
||||
bot.answer_callback_query(&q.id).await?;
|
||||
|
||||
if let Some(ref data) = q.data {
|
||||
@ -160,10 +179,7 @@ async fn callback_handler(
|
||||
"more_info" => {
|
||||
answer_message(
|
||||
&bot,
|
||||
q.chat_id()
|
||||
.clone()
|
||||
.map(|i| i.0)
|
||||
.unwrap_or(q.from.id.0 as i64),
|
||||
q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64),
|
||||
&mut db,
|
||||
"more_info",
|
||||
None as Option<InlineKeyboardMarkup>,
|
||||
@ -182,16 +198,12 @@ async fn edit_msg_cmd_handler(
|
||||
mut db: DB,
|
||||
dialogue: BotDialogue,
|
||||
msg: Message,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
) -> BotResult<()> {
|
||||
match msg.reply_to_message() {
|
||||
Some(replied) => {
|
||||
let msgid = replied.id;
|
||||
// look for message in db and set text
|
||||
let literal = match db
|
||||
.get_message_literal(msg.chat.id.0, msgid.0)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
let literal = match db.get_message_literal(msg.chat.id.0, msgid.0).await? {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
bot.send_message(msg.chat.id, "No such message found to edit. Look if you replying bot's message and this message is supposed to be editable").await?;
|
||||
@ -206,8 +218,7 @@ async fn edit_msg_cmd_handler(
|
||||
lang,
|
||||
is_caption_set: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
bot.send_message(
|
||||
msg.chat.id,
|
||||
"Ok, now you have to send message text (formatting supported)",
|
||||
@ -228,45 +239,44 @@ async fn edit_msg_handler(
|
||||
dialogue: BotDialogue,
|
||||
(literal, lang, is_caption_set): (String, String, bool),
|
||||
msg: Message,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
) -> BotResult<()> {
|
||||
use teloxide::utils::render::Renderer;
|
||||
|
||||
let chat_id = msg.chat.id;
|
||||
println!("Type: {:#?}", msg.kind);
|
||||
info!("Type: {:#?}", msg.kind);
|
||||
let msg = if let MessageKind::Common(msg) = msg.kind {
|
||||
msg
|
||||
} else {
|
||||
println!("Not a Common, somehow");
|
||||
info!("Not a Common, somehow");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match msg.media_kind {
|
||||
MediaKind::Text(text) => {
|
||||
db.drop_media(&literal).await.unwrap();
|
||||
db.drop_media(&literal).await?;
|
||||
if is_caption_set {
|
||||
return Ok(());
|
||||
};
|
||||
let html_text = Renderer::new(&text.text, &text.entities).as_html();
|
||||
db.set_literal(&literal, &html_text).await.unwrap();
|
||||
db.set_literal(&literal, &html_text).await?;
|
||||
bot.send_message(chat_id, "Updated text of message!")
|
||||
.await?;
|
||||
dialogue.exit().await.unwrap();
|
||||
dialogue.exit().await?;
|
||||
}
|
||||
MediaKind::Photo(photo) => {
|
||||
let group = photo.media_group_id;
|
||||
if let Some(group) = group.clone() {
|
||||
db.drop_media_except(&literal, &group).await.unwrap();
|
||||
db.drop_media_except(&literal, &group).await?;
|
||||
} else {
|
||||
db.drop_media(&literal).await.unwrap();
|
||||
db.drop_media(&literal).await?;
|
||||
}
|
||||
let file_id = photo.photo[0].file.id.clone();
|
||||
db.add_media(&literal, "photo", &file_id, group.as_deref())
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
match photo.caption {
|
||||
Some(text) => {
|
||||
let html_text = Renderer::new(&text, &photo.caption_entities).as_html();
|
||||
db.set_literal(&literal, &html_text).await.unwrap();
|
||||
db.set_literal(&literal, &html_text).await?;
|
||||
bot.send_message(chat_id, "Updated photo caption!").await?;
|
||||
}
|
||||
None => {
|
||||
@ -275,10 +285,9 @@ async fn edit_msg_handler(
|
||||
// set text empty
|
||||
if !db
|
||||
.is_media_group_exists(group.as_deref().unwrap_or(""))
|
||||
.await
|
||||
.unwrap()
|
||||
.await?
|
||||
{
|
||||
db.set_literal(&literal, "").await.unwrap();
|
||||
db.set_literal(&literal, "").await?;
|
||||
bot.send_message(chat_id, "Set photo without caption")
|
||||
.await?;
|
||||
};
|
||||
@ -296,8 +305,7 @@ async fn edit_msg_handler(
|
||||
lang,
|
||||
is_caption_set: true,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
dialogue.exit().await.unwrap_or(());
|
||||
@ -306,18 +314,17 @@ async fn edit_msg_handler(
|
||||
MediaKind::Video(video) => {
|
||||
let group = video.media_group_id;
|
||||
if let Some(group) = group.clone() {
|
||||
db.drop_media_except(&literal, &group).await.unwrap();
|
||||
db.drop_media_except(&literal, &group).await?;
|
||||
} else {
|
||||
db.drop_media(&literal).await.unwrap();
|
||||
db.drop_media(&literal).await?;
|
||||
}
|
||||
let file_id = video.video.file.id;
|
||||
db.add_media(&literal, "video", &file_id, group.as_deref())
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
match video.caption {
|
||||
Some(text) => {
|
||||
let html_text = Renderer::new(&text, &video.caption_entities).as_html();
|
||||
db.set_literal(&literal, &html_text).await.unwrap();
|
||||
db.set_literal(&literal, &html_text).await?;
|
||||
bot.send_message(chat_id, "Updated video caption!").await?;
|
||||
}
|
||||
None => {
|
||||
@ -326,10 +333,9 @@ async fn edit_msg_handler(
|
||||
// set text empty
|
||||
if !db
|
||||
.is_media_group_exists(group.as_deref().unwrap_or(""))
|
||||
.await
|
||||
.unwrap()
|
||||
.await?
|
||||
{
|
||||
db.set_literal(&literal, "").await.unwrap();
|
||||
db.set_literal(&literal, "").await?;
|
||||
bot.send_message(chat_id, "Set video without caption")
|
||||
.await?;
|
||||
};
|
||||
@ -347,8 +353,7 @@ async fn edit_msg_handler(
|
||||
lang,
|
||||
is_caption_set: true,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
.await?;
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||
dialogue.exit().await.unwrap_or(());
|
||||
@ -365,12 +370,7 @@ async fn edit_msg_handler(
|
||||
|
||||
fn command_handler(
|
||||
config: Config,
|
||||
) -> Handler<
|
||||
'static,
|
||||
DependencyMap,
|
||||
Result<(), teloxide::RequestError>,
|
||||
teloxide::dispatching::DpHandlerDescription,
|
||||
> {
|
||||
) -> Handler<'static, DependencyMap, BotResult<()>, teloxide::dispatching::DpHandlerDescription> {
|
||||
Update::filter_message()
|
||||
.branch(
|
||||
dptree::entry()
|
||||
@ -386,11 +386,14 @@ fn command_handler(
|
||||
.branch(
|
||||
dptree::entry()
|
||||
.filter_async(async |msg: Message, mut db: DB| {
|
||||
let tguser = msg.from.unwrap();
|
||||
let tguser = match msg.from.clone() {
|
||||
Some(user) => user,
|
||||
None => return false, // do nothing, cause its not usecase of function
|
||||
};
|
||||
let user = db
|
||||
.get_or_init_user(tguser.id.0 as i64, &tguser.first_name)
|
||||
.await;
|
||||
user.is_admin
|
||||
user.map(|u| u.is_admin).unwrap_or(false)
|
||||
})
|
||||
.filter_command::<AdminCommands>()
|
||||
.endpoint(admin_command_handler),
|
||||
@ -402,14 +405,20 @@ async fn user_command_handler(
|
||||
bot: Bot,
|
||||
msg: Message,
|
||||
cmd: UserCommands,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
let tguser = msg.from.clone().unwrap();
|
||||
) -> BotResult<()> {
|
||||
let tguser = match msg.from.clone() {
|
||||
Some(user) => user,
|
||||
None => return Ok(()), // do nothing, cause its not usecase of function
|
||||
};
|
||||
let user = db
|
||||
.get_or_init_user(tguser.id.0 as i64, &tguser.first_name)
|
||||
.await;
|
||||
let user = update_user_tg(user, msg.from.as_ref().unwrap());
|
||||
user.update_user(&mut db).await.unwrap();
|
||||
println!("MSG: {}", msg.html_text().unwrap());
|
||||
.await?;
|
||||
let user = update_user_tg(user, &tguser);
|
||||
user.update_user(&mut db).await?;
|
||||
info!(
|
||||
"MSG: {}",
|
||||
msg.html_text().unwrap_or("|EMPTY_MESSAGE|".into())
|
||||
);
|
||||
match cmd {
|
||||
UserCommands::Start => {
|
||||
let mut db2 = db.clone();
|
||||
@ -418,9 +427,10 @@ async fn user_command_handler(
|
||||
msg.chat.id.0,
|
||||
&mut db,
|
||||
"start",
|
||||
Some(make_start_buttons(&mut db2).await),
|
||||
Some(make_start_buttons(&mut db2).await?),
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
UserCommands::Help => {
|
||||
bot.send_message(msg.chat.id, UserCommands::descriptions().to_string())
|
||||
@ -436,13 +446,12 @@ async fn answer_message<RM: Into<ReplyMarkup>>(
|
||||
db: &mut DB,
|
||||
literal: &str,
|
||||
keyboard: Option<RM>,
|
||||
) -> Result<(), teloxide::RequestError> {
|
||||
) -> BotResult<()> {
|
||||
let text = db
|
||||
.get_literal_value(literal)
|
||||
.await
|
||||
.unwrap()
|
||||
.await?
|
||||
.unwrap_or("Please, set content of this message".into());
|
||||
let media = db.get_media(&literal).await.unwrap();
|
||||
let media = db.get_media(literal).await?;
|
||||
let (chat_id, msg_id) = match media.len() {
|
||||
// just a text
|
||||
0 => {
|
||||
@ -452,7 +461,7 @@ async fn answer_message<RM: Into<ReplyMarkup>>(
|
||||
None => msg,
|
||||
};
|
||||
let msg = msg.parse_mode(teloxide::types::ParseMode::Html);
|
||||
println!("ENTS: {:?}", msg.entities);
|
||||
info!("ENTS: {:?}", msg.entities);
|
||||
let msg = msg.await?;
|
||||
|
||||
(msg.chat.id.0, msg.id.0)
|
||||
@ -554,16 +563,14 @@ async fn answer_message<RM: Into<ReplyMarkup>>(
|
||||
(msg[0].chat.id.0, msg[0].id.0)
|
||||
}
|
||||
};
|
||||
db.set_message_literal(chat_id, msg_id, literal)
|
||||
.await
|
||||
.unwrap();
|
||||
db.set_message_literal(chat_id, msg_id, literal).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn make_start_buttons(db: &mut DB) -> InlineKeyboardMarkup {
|
||||
async fn make_start_buttons(db: &mut DB) -> BotResult<InlineKeyboardMarkup> {
|
||||
let mut buttons: Vec<Vec<InlineKeyboardButton>> = db
|
||||
.get_all_events()
|
||||
.await
|
||||
.await?
|
||||
.iter()
|
||||
.map(|e| {
|
||||
vec![InlineKeyboardButton::callback(
|
||||
@ -577,12 +584,12 @@ async fn make_start_buttons(db: &mut DB) -> InlineKeyboardMarkup {
|
||||
"more_info",
|
||||
)]);
|
||||
|
||||
InlineKeyboardMarkup::new(buttons)
|
||||
Ok(InlineKeyboardMarkup::new(buttons))
|
||||
}
|
||||
|
||||
async fn echo(bot: Bot, msg: Message) -> Result<(), teloxide::RequestError> {
|
||||
async fn echo(bot: Bot, msg: Message) -> BotResult<()> {
|
||||
if let Some(photo) = msg.photo() {
|
||||
println!("File ID: {}", photo[0].file.id);
|
||||
info!("File ID: {}", photo[0].file.id);
|
||||
}
|
||||
bot.send_message(msg.chat.id, msg.html_text().unwrap_or("UNWRAP".into()))
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
|
||||
@ -36,6 +36,26 @@ pub struct Dialogue {
|
||||
dialogue: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MongodbStorageError<SE>
|
||||
where
|
||||
SE: Debug + Display,
|
||||
{
|
||||
MongodbError(#[from] mongodb::error::Error),
|
||||
SerdeError(SE),
|
||||
}
|
||||
|
||||
pub type MongodbStorageResult<T, SE> = Result<T, MongodbStorageError<SE>>;
|
||||
|
||||
impl<SE> std::fmt::Display for MongodbStorageError<SE>
|
||||
where
|
||||
SE: Debug + Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, D> Storage<D> for MongodbStorage<S>
|
||||
where
|
||||
S: Send + Sync + Serializer<D> + 'static,
|
||||
@ -43,7 +63,7 @@ where
|
||||
|
||||
<S as Serializer<D>>::Error: Debug + Display,
|
||||
{
|
||||
type Error = mongodb::error::Error;
|
||||
type Error = MongodbStorageError<<S as Serializer<D>>::Error>;
|
||||
|
||||
fn remove_dialogue(
|
||||
self: std::sync::Arc<Self>,
|
||||
@ -56,7 +76,8 @@ where
|
||||
let d = self.database.collection::<Dialogue>("dialogues");
|
||||
d.delete_one(doc! { "chat_id": chat_id.0 })
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map(|_| ())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@ -71,14 +92,19 @@ where
|
||||
Box::pin(async move {
|
||||
let d = self.database.collection::<Dialogue>("dialogues");
|
||||
d.update_one(
|
||||
doc! {
|
||||
"chat_id": chat_id.0
|
||||
},
|
||||
doc! {
|
||||
"$set": doc! {
|
||||
"dialogue": self.serializer.serialize(&dialogue).unwrap().into_iter().map(|v| v as u32).collect::<Vec<u32>>()
|
||||
}
|
||||
}).upsert(true).await?;
|
||||
doc! {
|
||||
"chat_id": chat_id.0
|
||||
},
|
||||
doc! {
|
||||
"$set": doc! {
|
||||
"dialogue": self.serializer.serialize(&dialogue)
|
||||
.map_err(MongodbStorageError::SerdeError)?
|
||||
.into_iter().map(|v| v as u32).collect::<Vec<u32>>()
|
||||
}
|
||||
},
|
||||
)
|
||||
.upsert(true)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@ -89,17 +115,23 @@ where
|
||||
) -> BoxFuture<'static, Result<Option<D>, Self::Error>> {
|
||||
Box::pin(async move {
|
||||
let d = self.database.collection::<Dialogue>("dialogues");
|
||||
Ok(d.find_one(doc! { "chat_id": chat_id.0 }).await?.map(|d| {
|
||||
self.serializer
|
||||
.deserialize(
|
||||
d.dialogue
|
||||
.into_iter()
|
||||
.map(|i| i as u8)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.unwrap()
|
||||
}))
|
||||
let d = d.find_one(doc! { "chat_id": chat_id.0 }).await?;
|
||||
let d = match d {
|
||||
Some(d) => d,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let d = self
|
||||
.serializer
|
||||
.deserialize(
|
||||
d.dialogue
|
||||
.into_iter()
|
||||
.map(|i| i as u8)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
)
|
||||
.map_err(MongodbStorageError::SerdeError)?;
|
||||
|
||||
Ok(Some(d))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user