* init

* bot framework done

* here and ready for orm

* might use sea-orm

* orm done

* use teloxide

* ready to go?

* 需要完成命令部分

* 需要完成:list_handler()

* 查询用户名应当以@开头

* use rustls to avoid segfault?

* postgresql ready

* inline query done

* list_handler

* flattern code

* test needed

* ready to build

* some bugs

* almost done

* ready to take off

Co-authored-by: senset <dummy@dummy.d>
This commit is contained in:
senseab
2022-06-28 18:11:47 +08:00
committed by GitHub
parent fe53f1abd3
commit 56635e0e1b
28 changed files with 4723 additions and 0 deletions

21
src/callback_commands.rs Normal file
View File

@@ -0,0 +1,21 @@
use teloxide::utils::command::BotCommands;
#[derive(PartialEq, Debug, BotCommands)]
#[command(rename = "lowercase", prefix = "!")]
pub enum CallbackCommands {
#[command(description = "internal command page", parse_with = "split")]
Page {
msg_id: i32,
username: String,
page: usize,
},
#[command(description = "default dummy command")]
Default,
}
impl Default for CallbackCommands {
fn default() -> Self {
CallbackCommands::Default
}
}

330
src/commands.rs Normal file
View File

@@ -0,0 +1,330 @@
use std::collections::HashMap;
use strfmt::Format;
use teloxide::{
prelude::*,
types::{InlineKeyboardButton, InlineKeyboardMarkup},
types::{InlineKeyboardButtonKind, ReplyMarkup},
utils::command::{BotCommands, ParseError},
};
use wd_log::log_debug_ln;
use crate::{
db_controller::PaginatedRecordData,
messages::{
BOT_ABOUT, BOT_BUTTON_END, BOT_BUTTON_HEAD, BOT_BUTTON_NEXT, BOT_BUTTON_PREV, BOT_HELP,
BOT_TEXT_DELETED, BOT_TEXT_LOADING, BOT_TEXT_MUTE_STATUS, BOT_TEXT_STATUS_OFF,
BOT_TEXT_STATUS_ON, BOT_TEXT_WELCOME,
},
telegram_bot::BotServer,
};
#[derive(BotCommands, PartialEq, Debug)]
#[command(rename = "lowercase")]
pub enum Commands {
#[command(description = "显示帮助信息")]
Help,
#[command(description = "关于本 Bot")]
About,
#[command(description = "关闭提醒")]
Mute,
#[command(description = "开启提醒")]
Unmute,
#[command(description = "列出已记录的内容", parse_with = "list_command_parser")]
List { username: String },
#[command(description = "删除记录")]
Del { id: i64 },
#[command(description = "注册")]
Start,
}
impl Default for Commands {
fn default() -> Self {
Commands::Help
}
}
fn list_command_parser(input: String) -> Result<(String,), ParseError> {
log_debug_ln!(
"list_command_parse = \"{}\", is empty = {}",
input,
input.trim().is_empty()
);
let output: String;
if input.trim().is_empty() {
output = "me".to_string();
} else {
output = input
}
Ok((output,))
}
pub struct CommandHandler {}
impl CommandHandler {
pub async fn about_handler(bot_s: &BotServer, message: &Message) {
bot_s.send_text_reply(message, BOT_ABOUT).await;
}
pub async fn help_handler(bot_s: &BotServer, message: &Message) {
bot_s.send_text_reply(message, BOT_HELP).await;
}
pub async fn notify_handler(bot_s: &BotServer, message: &Message, enabled: bool) {
let user = match message.from() {
Some(user) => user,
None => return,
};
if user.is_bot {
if let Err(error) = bot_s
.controller
.set_user_notify(&user.id.0.try_into().unwrap(), enabled)
.await
{
bot_s.controller.err_handler(error);
}
let mut vars = HashMap::new();
vars.insert(
"status".to_string(),
match enabled {
true => BOT_TEXT_STATUS_ON,
false => BOT_TEXT_STATUS_OFF,
},
);
bot_s
.send_text_reply(message, &BOT_TEXT_MUTE_STATUS.format(&vars).unwrap())
.await;
}
}
pub async fn setup_handler(bot_s: &BotServer, message: &Message) {
let user = match message.from() {
Some(user) => user,
None => return,
};
if user.is_bot {
return;
}
let user_id: i64 = user.id.0.try_into().unwrap();
let username = match user.username.to_owned() {
Some(username) => format!("@{}", username),
None => user.first_name.to_owned(),
};
if let Err(error) = bot_s.controller.register_user(&user_id, &username).await {
bot_s.controller.err_handler(error);
}
bot_s.send_text_reply(message, BOT_TEXT_WELCOME).await;
}
pub async fn del_handler(bot_s: &BotServer, message: &Message, id: i64) {
let user = match message.from() {
Some(user) => user,
None => return,
};
if user.is_bot {
return;
}
if let Err(error) = bot_s
.controller
.del_record(id, user.id.0.try_into().unwrap())
.await
{
bot_s.controller.err_handler(error);
}
bot_s.send_text_reply(message, BOT_TEXT_DELETED).await;
}
pub async fn list_handler(bot_s: &BotServer, message: &Message, username: &str, page: usize) {
let user = match message.from() {
Some(user) => user,
None => return,
};
if user.is_bot {
return;
}
let msg_id = match bot_s.send_text_reply(message, BOT_TEXT_LOADING).await {
Some(id) => id,
None => return,
};
let (msg, markup) = match Self::record_msg_genrator(bot_s, message, username, page).await {
Some(d) => d,
None => return,
};
bot_s
.edit_text_reply_with_inline_key(message, msg_id, msg.as_str(), markup)
.await;
}
pub async fn record_msg_genrator(
bot_s: &BotServer,
message: &Message,
username: &str,
page: usize,
) -> Option<(String, ReplyMarkup)> {
let someone = match bot_s.controller.get_user_by_username(username).await {
Ok(someone) => someone,
Err(error) => {
bot_s.controller.err_handler(error);
return None;
}
};
let someone = match someone {
Some(someone) => someone,
None => return None,
};
let data = match bot_s
.controller
.get_records_by_userid_with_pagination(someone.id, page)
.await
{
Ok(data) => data,
Err(error) => {
bot_s.controller.err_handler(error);
return None;
}
};
let paginated_record_data = match data {
Some(d) => d,
None => return None,
};
Some((
Self::generate_text_record_msg(&paginated_record_data, page),
Self::generate_inline_keyboard(
page,
paginated_record_data.pages_count,
username,
message,
),
))
}
fn generate_inline_keyboard(
page: usize,
pages_count: usize,
username: &str,
message: &Message,
) -> ReplyMarkup {
let inline_keyboards = match page {
page if page == 0 && pages_count > 1 => vec![
InlineKeyboardButton {
text: BOT_BUTTON_NEXT.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id,
username,
page + 1
)),
},
InlineKeyboardButton {
text: BOT_BUTTON_END.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id,
username,
pages_count - 1
)),
},
],
page if page == 0 && pages_count <= 1 => vec![],
page if page >= pages_count - 1 => vec![
InlineKeyboardButton {
text: BOT_BUTTON_HEAD.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id, username, 0
)),
},
InlineKeyboardButton {
text: BOT_BUTTON_PREV.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id,
username,
page - 1
)),
},
],
_ => vec![
InlineKeyboardButton {
text: BOT_BUTTON_HEAD.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id, username, 0
)),
},
InlineKeyboardButton {
text: BOT_BUTTON_PREV.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id,
username,
page - 1
)),
},
InlineKeyboardButton {
text: BOT_BUTTON_NEXT.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id,
username,
page + 1
)),
},
InlineKeyboardButton {
text: BOT_BUTTON_END.to_string(),
kind: InlineKeyboardButtonKind::CallbackData(format!(
"!page {} {} {}",
message.id,
username,
pages_count - 1
)),
},
],
};
ReplyMarkup::InlineKeyboard(InlineKeyboardMarkup {
inline_keyboard: vec![inline_keyboards],
})
}
fn generate_text_record_msg(
paginated_record_data: &PaginatedRecordData,
page: usize,
) -> String {
let mut msg = String::from("```");
for (message, _) in paginated_record_data.current_data.iter() {
msg = format!("{}\n{}\t\t\t\t{}", msg, message.id, message.message);
}
msg = format!(
"{}\n```\n{}/{}",
msg,
page + 1,
paginated_record_data.pages_count
);
msg
}
}

19
src/config.rs Normal file
View File

@@ -0,0 +1,19 @@
use clap::Parser;
const DEFAULT_DATABASE: &'static str = "sqlite:///saysthbot.db";
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
/// Enable debug mode
#[clap(short = 'D', long, value_parser, default_value_t = false)]
pub debug: bool,
/// Telegram bot token
#[clap(short, long, value_parser, env = "TGBOT_TOKEN")]
pub tgbot_token: String,
/// Database URI
#[clap(short, long, value_parser, env = "DATABASE_URI", default_value=DEFAULT_DATABASE)]
pub database_uri: String,
}

190
src/db_controller.rs Normal file
View File

@@ -0,0 +1,190 @@
use migration::{Migrator, MigratorTrait};
use models::prelude::*;
use sea_orm::{
ActiveModelTrait, ColumnTrait, Database, DatabaseConnection, DatabaseTransaction, DbErr,
EntityTrait, PaginatorTrait, QueryFilter, Set, TransactionTrait,
};
use wd_log::{log_error_ln, log_info_ln, log_warn_ln};
const PAGE_SIZE: usize = 25;
#[derive(Debug)]
pub struct Controller {
db: DatabaseConnection,
}
pub struct PaginatedRecordData {
pub items_count: usize,
pub pages_count: usize,
pub current_data: Vec<(RecordModel, Option<UserModel>)>,
}
impl Controller {
/// Create controller
pub async fn new(config: String) -> Result<Self, DbErr> {
Ok(Self {
db: Database::connect(config).await?,
})
}
/// Do migrate
pub async fn migrate(&self) -> Result<(), DbErr> {
if let Err(err) = Migrator::install(&self.db).await {
log_warn_ln!("{}", err)
}
if let Err(err) = Migrator::up(&self.db, None).await {
Err(err)
} else {
log_info_ln!("database initialized.");
Ok(())
}
}
/// register user when `/start` command called.
pub async fn register_user(&self, user_id: &i64, username: &String) -> Result<(), DbErr> {
let transaction = self.db.begin().await?;
self.setup_user(user_id, username, &transaction).await?;
transaction.commit().await
}
/// update user notify when `/mute` or `/unmute` command called.
pub async fn set_user_notify(&self, user_id: &i64, notify: bool) -> Result<(), DbErr> {
let transaction = self.db.begin().await?;
if let Some(user) = self.get_user(user_id, &transaction).await? {
let mut user_active: UserActiveModel = user.into();
user_active.notify = Set(notify);
user_active.save(&transaction).await?;
}
transaction.commit().await
}
pub async fn get_user_notify(&self, user_id: &i64) -> Result<bool, DbErr> {
let transaction = self.db.begin().await?;
if let Some(user) = self.get_user(&user_id, &transaction).await? {
Ok(user.notify)
} else {
Ok(false)
}
}
async fn setup_user(
&self,
user_id: &i64,
username: &String,
transaction: &DatabaseTransaction,
) -> Result<UserActiveModel, DbErr> {
match self.get_user(user_id, &transaction).await? {
Some(user) => {
let mut user_active: UserActiveModel = user.into();
user_active.username = Set(Some(username.to_string()));
user_active.save(transaction).await
}
None => {
UserActiveModel {
tg_uid: Set(user_id.to_owned()),
username: Set(Some(username.to_string())),
notify: Set(true),
..Default::default()
}
.save(transaction)
.await
}
}
}
async fn get_user(
&self,
user_id: &i64,
transaction: &DatabaseTransaction,
) -> Result<Option<UserModel>, DbErr> {
User::find()
.filter(UserColumn::Id.eq(user_id.to_owned()))
.one(transaction)
.await
}
pub async fn get_user_by_username(&self, username: &str) -> Result<Option<UserModel>, DbErr> {
let transaction = self.db.begin().await?;
User::find()
.filter(UserColumn::Username.eq(username.to_owned()))
.one(&transaction)
.await
}
/// get records when inline query called.
pub async fn get_records_by_keywords(
&self,
key_word: &String,
) -> Result<PaginatedRecordData, DbErr> {
let pagination = Record::find()
.find_also_related(User)
.filter(RecordColumn::Message.contains(key_word.as_str()))
.paginate(&self.db, PAGE_SIZE * 2); // 50 records seems ok.
Ok(PaginatedRecordData {
items_count: pagination.num_items().await?,
pages_count: pagination.num_pages().await?,
current_data: pagination.fetch().await?,
})
}
/// get records when `/list` command called or inline button request.
pub async fn get_records_by_userid_with_pagination(
&self,
user_id: i64,
page: usize,
) -> Result<Option<PaginatedRecordData>, DbErr> {
let transaction = self.db.begin().await?;
if let Some(user) = self.get_user(&user_id, &transaction).await? {
let pagination = Record::find()
.find_also_related(User)
.filter(RecordColumn::UserId.eq(user.id))
.paginate(&transaction, PAGE_SIZE);
Ok(Some(PaginatedRecordData {
current_data: pagination.fetch_page(page).await?,
items_count: pagination.num_items().await?,
pages_count: pagination.num_pages().await?,
}))
} else {
log_error_ln!("cannot find user tg_uid={}", user_id);
Ok(None)
}
}
/// add record forward a message to bot.
pub async fn add_record(
&self,
user_id: i64,
username: &String,
text: String,
) -> Result<(), DbErr> {
let transaction = self.db.begin().await?;
let user = self.setup_user(&user_id, &username, &transaction).await?;
RecordActiveModel {
message: Set(text),
user_id: user.id,
..Default::default()
}
.insert(&transaction)
.await?;
transaction.commit().await
}
/// del record when `/delete` command called.
pub async fn del_record(&self, id: i64, user_id: i64) -> Result<(), DbErr> {
let transaction = self.db.begin().await?;
if let Some(user) = self.get_user(&user_id, &transaction).await? {
RecordActiveModel {
id: Set(id),
user_id: Set(user.id),
..Default::default()
}
.delete(&transaction)
.await?;
}
transaction.commit().await
}
pub fn err_handler(&self, error: DbErr) {
log_error_ln!("{}", error);
}
}

36
src/main.rs Normal file
View File

@@ -0,0 +1,36 @@
mod callback_commands;
mod commands;
mod config;
mod db_controller;
mod messages;
mod telegram_bot;
use clap::Parser;
use config::Args;
use telegram_bot::BotServer;
use wd_log::{log_debug_ln, log_panic, set_level, set_prefix, DEBUG, INFO};
#[tokio::main]
async fn main() {
let args = Args::parse();
set_prefix("saysthbot");
if args.debug {
set_level(DEBUG);
log_debug_ln!("{:?}", args);
} else {
set_level(INFO);
}
let bot = match BotServer::new(args).await {
Ok(bot) => bot,
Err(err) => log_panic!("{}", err),
};
if let Err(err) = bot.init().await {
log_panic!("{}", err);
}
bot.run().await;
}

21
src/messages.rs Normal file
View File

@@ -0,0 +1,21 @@
pub const BOT_TEXT_MESSAGE_ONLY: &'static str = "仅支持文本信息";
pub const BOT_TEXT_FORWARDED_ONLY: &'static str = "仅支持转发信息";
pub const BOT_TEXT_USER_ONLY: &'static str = "仅支持用户信息";
pub const BOT_TEXT_NO_BOT: &'static str = "不支持 bot 消息";
pub const BOT_TEXT_NOTED: &'static str = "✅ `{data}` 已记录";
pub const BOT_TEXT_NOTICE: &'static str = "[{username}](tg://user?id={user_id}) 转发了你的 `{data}`\n\n\t你可以使用 /list 命令查看自己或者他人被记录的信息\n\t你可以使用 /del 命令删除某条自己的信息\n\t你也可以使用 /mute 或者 /unmute 命令开启或者关闭提醒";
pub const BOT_TEXT_WELCOME: &'static str =
"✅ 注册成功!如果有别人记录了你的消息,这里会有提醒,可使用 /mute 命令关闭提醒";
pub const BOT_HELP: &'static str = "*帮助*\n\n\t/list `[@username]` 列出已记录的内容\n\t/del `id` 删除对应id的记录只能删除自己的\n\t/mute 关闭提醒\n\t/unmute 开启提醒";
pub const BOT_ABOUT: &'static str =
"Say something bot \\- Reborn\n\n[Github](https://github.com/senseab/saysthbot-reborn) @ssthbot";
pub const BOT_TEXT_MUTE_STATUS: &'static str = "提醒状态:{status}";
pub const BOT_TEXT_STATUS_ON: &'static str = "✅ 开启";
pub const BOT_TEXT_STATUS_OFF: &'static str = "❎ 关闭";
pub const BOT_TEXT_DELETED: &'static str = "已删除";
pub const BOT_TEXT_SHOULD_START_WITH_AT: &'static str = "用户名应当以 `@` 开头";
pub const BOT_BUTTON_HEAD: &'static str = "⏮ 首页";
pub const BOT_BUTTON_END: &'static str = "末页 ⏭";
pub const BOT_BUTTON_PREV: &'static str = "⏪ 上一页";
pub const BOT_BUTTON_NEXT: &'static str = "下一页 ⏩";
pub const BOT_TEXT_LOADING: &'static str = "⌛️ 载入中……";

433
src/telegram_bot.rs Normal file
View File

@@ -0,0 +1,433 @@
use std::collections::HashMap;
use crate::callback_commands::CallbackCommands;
use crate::db_controller::Controller;
use crate::messages::*;
use crate::{commands::CommandHandler, commands::Commands, config::Args};
use migration::DbErr;
use strfmt::Format;
use teloxide::utils::command::BotCommands;
use teloxide::{
prelude::*, types::ForwardedFrom, types::InlineQueryResult, types::InlineQueryResultArticle,
types::InputMessageContent, types::InputMessageContentText, types::ParseMode,
types::ReplyMarkup, types::UpdateKind, RequestError,
};
use wd_log::{log_debug_ln, log_error_ln, log_info_ln, log_panic, log_warn_ln};
pub struct BotServer {
pub controller: Controller,
bot: Bot,
}
impl BotServer {
/// Create new bot
pub async fn new(config: Args) -> Result<Self, DbErr> {
Ok(Self {
bot: Bot::new(config.tgbot_token),
controller: Controller::new(config.database_uri).await?,
})
}
pub async fn init(&self) -> Result<(), DbErr> {
self.controller.migrate().await
}
/// Run the bot
pub async fn run(&self) {
match self.bot.get_me().send().await {
Ok(result) => log_info_ln!(
"connect succeed: id={}, botname=\"{}\"",
result.id,
result.username()
),
Err(error) => log_panic!("{}", error),
}
self.register_commands().await;
let mut offset_id = 0;
loop {
let updates = match self.bot.get_updates().offset(offset_id).send().await {
Ok(it) => it,
_ => continue,
};
for update in updates {
self.update_handler(&update).await;
offset_id = update.id + 1;
}
}
}
async fn register_commands(&self) {
if let Err(error) = self
.bot
.set_my_commands(Commands::bot_commands())
.send()
.await
{
self.default_error_handler(&error);
} else {
log_info_ln!("commands registered")
}
}
async fn update_handler(&self, update: &Update) {
match &update.kind {
UpdateKind::Message(ref message) => self.message_handler(message).await,
UpdateKind::InlineQuery(inline_query) => self.inline_query_hander(inline_query).await,
UpdateKind::CallbackQuery(callback) => self.callback_handler(callback).await,
kind => self.default_update_hander(&kind).await,
}
}
async fn default_update_hander(&self, update_kind: &UpdateKind) {
log_debug_ln!("non-supported kind {:?}", update_kind);
}
async fn callback_handler(&self, callback: &CallbackQuery) {
log_debug_ln!("callback={:#?}", callback);
let message = match &callback.message {
Some(msg) => msg,
None => return,
};
let text = match &callback.data {
Some(text) => text,
None => return,
};
let bot_username = match self.bot.get_me().send().await {
Ok(result) => result.username.to_owned(),
Err(error) => {
self.default_error_handler(&error);
return;
}
};
let bot_username = match bot_username {
Some(b) => b,
None => return,
};
let commands = match CallbackCommands::parse(text, bot_username) {
Ok(c) => c,
Err(error) => {
log_warn_ln!("{}", error);
return;
}
};
match commands {
CallbackCommands::Page {
msg_id: _,
username,
page,
} => {
let (msg, keyboard) = match CommandHandler::record_msg_genrator(
self,
message,
username.as_str(),
page,
)
.await
{
Some(d) => d,
None => return,
};
self.edit_text_reply_with_inline_key(message, message.id, msg.as_str(), keyboard)
.await;
match self.bot.answer_callback_query(&callback.id).send().await {
Ok(_) => (),
Err(error) => self.default_error_handler(&error),
}
}
CallbackCommands::Default => return,
}
}
async fn inline_query_hander(&self, inline_query: &InlineQuery) {
let results = match self
.controller
.get_records_by_keywords(&inline_query.query)
.await
{
Ok(results) => results,
Err(error) => {
self.controller.err_handler(error);
return;
}
};
let mut r: Vec<InlineQueryResult> = vec![];
for (record, o_user) in results.current_data.iter() {
let user = match o_user {
Some(user) => user,
None => continue,
};
let username = match &user.username {
Some(username) => username,
None => continue,
};
r.push(InlineQueryResult::Article(InlineQueryResultArticle {
id: record.id.to_string(),
title: record.message.to_owned(),
input_message_content: InputMessageContent::Text(InputMessageContentText {
message_text: format!(
"*{}*: {}",
username.trim_start_matches("@"),
record.message
),
parse_mode: Some(ParseMode::MarkdownV2),
entities: None,
disable_web_page_preview: Some(true),
}),
reply_markup: None,
url: None,
hide_url: None,
description: Some(format!("By: {}", username)),
thumb_url: None,
thumb_width: None,
thumb_height: None,
}));
}
if let Err(error) = self
.bot
.answer_inline_query(&inline_query.id, r.into_iter())
.send()
.await
{
self.default_error_handler(&error);
}
}
async fn message_handler(&self, message: &Message) {
if let Some(data) = &message.text() {
self.text_message_heandler(message, data).await
} else {
self.default_message_handler(message).await
}
}
async fn text_message_heandler(&self, message: &Message, data: &str) {
let forward = match message.forward() {
Some(forward) => forward,
None => {
if data.starts_with("/") {
self.command_hanler(message).await;
} else {
self.send_text_reply(message, BOT_TEXT_FORWARDED_ONLY).await;
}
return;
}
};
match &forward.from {
ForwardedFrom::User(user) if !user.is_bot => {
let username = match &user.username {
Some(username) => format!("@{}", username),
None => user.first_name.to_owned(),
};
if let Err(err) = self
.controller
.add_record(user.id.0.try_into().unwrap(), &username, data.to_string())
.await
{
log_error_ln!("{}", err);
return;
}
let mut vars = HashMap::new();
vars.insert("data".to_string(), data);
self.send_text_reply(message, &BOT_TEXT_NOTED.format(&vars).unwrap())
.await;
let from = match message.from() {
Some(from) => from,
None => return,
};
if from.id == user.id {
return;
}
if match self
.controller
.get_user_notify(&user.id.0.try_into().unwrap())
.await
{
Ok(notify) => notify,
Err(error) => {
log_error_ln!("{}", error);
return;
}
} {
let mut vars = HashMap::new();
let user_id = user.id.to_string();
let data = data.to_string();
vars.insert("username".to_string(), &from.first_name);
vars.insert("user_id".to_string(), &user_id);
vars.insert("data".to_string(), &data);
match self
.bot
.send_message(user.id, &BOT_TEXT_NOTICE.format(&vars).unwrap())
.send()
.await
{
Ok(result) => {
log_debug_ln!("message sent {:?}", result)
}
Err(err) => self.default_error_handler(&err),
}
}
}
ForwardedFrom::User(_) => {
self.send_text_reply(message, BOT_TEXT_NO_BOT).await;
}
_ => {
self.send_text_message(message, BOT_TEXT_USER_ONLY).await;
}
}
}
async fn command_hanler(&self, message: &Message) {
let msg = match message.text() {
Some(msg) => msg,
None => return,
};
let bot_username = match self.bot.get_me().send().await {
Ok(result) => result.username.to_owned(),
Err(error) => {
self.default_error_handler(&error);
return;
}
};
let bot_username = match bot_username {
Some(b) => b,
None => return,
};
let commands = match Commands::parse(msg, bot_username) {
Ok(c) => c,
Err(error) => {
log_warn_ln!("{}", error);
return;
}
};
match commands {
Commands::Help => CommandHandler::help_handler(&self, message).await,
Commands::About => CommandHandler::about_handler(&self, message).await,
Commands::Mute => CommandHandler::notify_handler(&self, message, true).await,
Commands::Unmute => CommandHandler::notify_handler(&self, message, false).await,
Commands::List { mut username } => {
if username == "me" {
if let Some(from) = message.from() {
if let Some(_username) = &from.username {
username = format!("@{}", _username);
}
}
}
if username.starts_with("@") {
// always start from page=0
CommandHandler::list_handler(&self, message, &username, 0).await;
} else {
self.send_text_reply(message, BOT_TEXT_SHOULD_START_WITH_AT)
.await;
}
}
Commands::Del { id } => CommandHandler::del_handler(&self, message, id).await,
Commands::Start => CommandHandler::setup_handler(&self, message).await,
}
}
fn default_error_handler(&self, error: &RequestError) {
log_error_ln!("{:?}", error);
}
async fn default_message_handler(&self, message: &Message) {
log_debug_ln!(
"non-spported message {:?} from `{:?}`",
message.kind,
message.from()
);
self.send_text_reply(message, BOT_TEXT_MESSAGE_ONLY).await;
}
pub async fn send_text_message(&self, message: &Message, text: &str) -> Option<i32> {
match &self
.bot
.send_message(message.chat.id, text)
.parse_mode(ParseMode::MarkdownV2)
.send()
.await
{
Ok(result) => {
log_debug_ln!("message sent {:?}", result);
Some(result.id)
}
Err(error) => {
self.default_error_handler(error);
return None;
}
}
}
pub async fn send_text_reply(&self, message: &Message, text: &str) -> Option<i32> {
match &self
.bot
.send_message(message.chat.id, text)
.reply_to_message_id(message.id)
.parse_mode(ParseMode::MarkdownV2)
.send()
.await
{
Ok(result) => {
log_debug_ln!("reply sent {:?}", result);
Some(result.id)
}
Err(error) => {
self.default_error_handler(error);
None
}
}
}
pub async fn edit_text_reply_with_inline_key(
&self,
message: &Message,
msg_id: i32,
text: &str,
keyboard: ReplyMarkup,
) {
let keyboard = match keyboard {
ReplyMarkup::InlineKeyboard(keyboard) => keyboard,
_ => return,
};
match &self
.bot
.edit_message_text(message.chat.id, msg_id, text)
.reply_markup(keyboard)
.parse_mode(ParseMode::MarkdownV2)
.send()
.await
{
Ok(result) => log_debug_ln!("reply sent {:?}", result),
Err(error) => self.default_error_handler(error),
}
}
}