mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-24 17:29:08 +00:00
Made subs
This commit is contained in:
@@ -5,7 +5,8 @@ use crate::database::DatabaseManager;
|
||||
use crate::database::repository::{UserRepository, UserRequestRepository};
|
||||
use crate::database::entities::user_request::RequestStatus;
|
||||
use super::super::localization::{LocalizationService, Language};
|
||||
use super::types::get_selected_servers;
|
||||
use super::types::{get_selected_servers, generate_short_request_id, get_full_request_id, generate_short_server_id, get_full_server_id};
|
||||
use super::user::handle_start;
|
||||
|
||||
/// Handle admin requests edit (show list of recent requests)
|
||||
pub async fn handle_admin_requests_edit(
|
||||
@@ -168,12 +169,14 @@ pub async fn handle_approve_request(
|
||||
if let teloxide::types::MaybeInaccessibleMessage::Regular(msg) = message {
|
||||
if let Some(text) = msg.text() {
|
||||
let updated_text = format!("{}\n\n✅ <b>APPROVED</b> by {}\n\n📋 Select servers to grant access:", text, admin.name);
|
||||
let request_id_compact = request_id.to_string().replace("-", "");
|
||||
let callback_data = format!("s:{}", request_id_compact);
|
||||
tracing::info!("Callback data length: {} bytes, data: '{}'", callback_data.len(), callback_data);
|
||||
// Generate short ID for the request
|
||||
let short_request_id = generate_short_request_id(&request_id.to_string());
|
||||
let callback_data = format!("s:{}", short_request_id);
|
||||
tracing::info!("Generated callback data for server selection: '{}' (length: {})", callback_data, callback_data.len());
|
||||
|
||||
let server_selection_keyboard = InlineKeyboardMarkup::new(vec![
|
||||
vec![InlineKeyboardButton::callback("🖥️ Select Servers", callback_data)],
|
||||
vec![InlineKeyboardButton::callback("📋 All Requests", "back_to_requests")],
|
||||
vec![InlineKeyboardButton::callback("Select Servers", callback_data)],
|
||||
vec![InlineKeyboardButton::callback("All Requests", "back_to_requests")],
|
||||
]);
|
||||
|
||||
let _ = bot.edit_message_text(msg.chat.id, msg.id, updated_text)
|
||||
@@ -184,13 +187,32 @@ pub async fn handle_approve_request(
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the user using their saved language preference
|
||||
// Send main menu to the user instead of just notification
|
||||
let user_lang = Language::from_telegram_code(Some(&request.get_language()));
|
||||
let user_message = l10n.format(user_lang, "request_approved_notification", &[("user_id", &new_user.id.to_string())]);
|
||||
let user_repo_for_user = UserRepository::new(db.connection());
|
||||
let is_admin = false; // New users are not admins by default
|
||||
|
||||
bot.send_message(ChatId(request.telegram_id), user_message)
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
.await?;
|
||||
// Create a fake user object for language detection
|
||||
let fake_user = teloxide::types::User {
|
||||
id: teloxide::types::UserId(request.telegram_id as u64),
|
||||
is_bot: false,
|
||||
first_name: request.telegram_first_name.clone().unwrap_or_default(),
|
||||
last_name: request.telegram_last_name.clone(),
|
||||
username: request.telegram_username.clone(),
|
||||
language_code: Some(request.get_language()),
|
||||
is_premium: false,
|
||||
added_to_attachment_menu: false,
|
||||
};
|
||||
|
||||
// Send main menu using handle_start
|
||||
handle_start(
|
||||
bot.clone(),
|
||||
ChatId(request.telegram_id),
|
||||
request.telegram_id,
|
||||
&fake_user,
|
||||
&user_repo_for_user,
|
||||
db
|
||||
).await?;
|
||||
|
||||
bot.answer_callback_query(q.id.clone())
|
||||
.text(l10n.get(lang, "request_approved_admin"))
|
||||
@@ -457,7 +479,7 @@ pub async fn handle_broadcast(
|
||||
pub async fn handle_select_server_access(
|
||||
bot: Bot,
|
||||
q: &CallbackQuery,
|
||||
request_id: &str,
|
||||
short_request_id: &str,
|
||||
db: &DatabaseManager,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let lang = Language::English; // Default admin language
|
||||
@@ -481,17 +503,23 @@ pub async fn handle_select_server_access(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the full request ID from the short ID
|
||||
let request_id = get_full_request_id(short_request_id)
|
||||
.ok_or("Invalid request ID")?;
|
||||
|
||||
tracing::info!("Handling server selection for request: {} (short: {})", request_id, short_request_id);
|
||||
|
||||
// Initialize selected servers for this request (empty initially)
|
||||
{
|
||||
let mut selected = get_selected_servers().lock().unwrap();
|
||||
selected.insert(request_id.to_string(), Vec::new());
|
||||
selected.insert(request_id.clone(), Vec::new());
|
||||
}
|
||||
|
||||
// Build keyboard with server toggle buttons
|
||||
let mut keyboard_buttons = vec![];
|
||||
let selected_servers = {
|
||||
let selected = get_selected_servers().lock().unwrap();
|
||||
selected.get(request_id).cloned().unwrap_or_default()
|
||||
selected.get(&request_id).cloned().unwrap_or_default()
|
||||
};
|
||||
|
||||
for server in &servers {
|
||||
@@ -502,17 +530,18 @@ pub async fn handle_select_server_access(
|
||||
format!("⬜ {}", server.name)
|
||||
};
|
||||
|
||||
let short_server_id = generate_short_server_id(&server.id.to_string());
|
||||
let callback_data = format!("t:{}:{}", short_request_id, short_server_id);
|
||||
tracing::debug!("Toggle button callback: '{}' (length: {})", callback_data, callback_data.len());
|
||||
|
||||
keyboard_buttons.push(vec![
|
||||
InlineKeyboardButton::callback(
|
||||
button_text,
|
||||
format!("t:{}:{}", request_id.to_string().replace("-", ""), server.id.to_string().replace("-", ""))
|
||||
)
|
||||
InlineKeyboardButton::callback(button_text, callback_data)
|
||||
]);
|
||||
}
|
||||
|
||||
// Add apply and back buttons
|
||||
keyboard_buttons.push(vec![
|
||||
InlineKeyboardButton::callback("✅ Apply Selected", format!("a:{}", request_id.to_string().replace("-", ""))),
|
||||
InlineKeyboardButton::callback("✅ Apply Selected", format!("a:{}", short_request_id)),
|
||||
InlineKeyboardButton::callback("🔙 Back", "back_to_requests"),
|
||||
]);
|
||||
|
||||
@@ -536,8 +565,8 @@ pub async fn handle_select_server_access(
|
||||
pub async fn handle_toggle_server(
|
||||
bot: Bot,
|
||||
q: &CallbackQuery,
|
||||
request_id: &str,
|
||||
server_id: &str,
|
||||
short_request_id: &str,
|
||||
short_server_id: &str,
|
||||
db: &DatabaseManager,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let chat_id = q.message.as_ref().and_then(|m| {
|
||||
@@ -547,15 +576,23 @@ pub async fn handle_toggle_server(
|
||||
}
|
||||
}).ok_or("No chat ID")?;
|
||||
|
||||
// Get the full IDs from the short IDs
|
||||
let request_id = get_full_request_id(short_request_id)
|
||||
.ok_or("Invalid request ID")?;
|
||||
let server_id = get_full_server_id(short_server_id)
|
||||
.ok_or("Invalid server ID")?;
|
||||
|
||||
tracing::info!("Toggling server {} for request {}", server_id, request_id);
|
||||
|
||||
// Toggle server selection
|
||||
{
|
||||
let mut selected = get_selected_servers().lock().unwrap();
|
||||
let server_list = selected.entry(request_id.to_string()).or_insert_with(Vec::new);
|
||||
let server_list = selected.entry(request_id.clone()).or_insert_with(Vec::new);
|
||||
|
||||
if let Some(pos) = server_list.iter().position(|x| x == server_id) {
|
||||
if let Some(pos) = server_list.iter().position(|x| x == &server_id) {
|
||||
server_list.remove(pos);
|
||||
} else {
|
||||
server_list.push(server_id.to_string());
|
||||
server_list.push(server_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +603,7 @@ pub async fn handle_toggle_server(
|
||||
let mut keyboard_buttons = vec![];
|
||||
let selected_servers = {
|
||||
let selected = get_selected_servers().lock().unwrap();
|
||||
selected.get(request_id).cloned().unwrap_or_default()
|
||||
selected.get(&request_id).cloned().unwrap_or_default()
|
||||
};
|
||||
|
||||
for server in &servers {
|
||||
@@ -577,17 +614,17 @@ pub async fn handle_toggle_server(
|
||||
format!("⬜ {}", server.name)
|
||||
};
|
||||
|
||||
let short_server_id = generate_short_server_id(&server.id.to_string());
|
||||
let callback_data = format!("t:{}:{}", short_request_id, short_server_id);
|
||||
|
||||
keyboard_buttons.push(vec![
|
||||
InlineKeyboardButton::callback(
|
||||
button_text,
|
||||
format!("t:{}:{}", request_id.to_string().replace("-", ""), server.id.to_string().replace("-", ""))
|
||||
)
|
||||
InlineKeyboardButton::callback(button_text, callback_data)
|
||||
]);
|
||||
}
|
||||
|
||||
// Add apply and back buttons
|
||||
keyboard_buttons.push(vec![
|
||||
InlineKeyboardButton::callback("✅ Apply Selected", format!("a:{}", request_id.to_string().replace("-", ""))),
|
||||
InlineKeyboardButton::callback("✅ Apply Selected", format!("a:{}", short_request_id)),
|
||||
InlineKeyboardButton::callback("🔙 Back", "back_to_requests"),
|
||||
]);
|
||||
|
||||
@@ -613,7 +650,7 @@ pub async fn handle_toggle_server(
|
||||
pub async fn handle_apply_server_access(
|
||||
bot: Bot,
|
||||
q: &CallbackQuery,
|
||||
request_id: &str,
|
||||
short_request_id: &str,
|
||||
db: &DatabaseManager,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let lang = Language::English; // Default admin language
|
||||
@@ -625,10 +662,14 @@ pub async fn handle_apply_server_access(
|
||||
}
|
||||
}).ok_or("No chat ID")?;
|
||||
|
||||
// Get the full request ID from the short ID
|
||||
let request_id = get_full_request_id(short_request_id)
|
||||
.ok_or("Invalid request ID")?;
|
||||
|
||||
// Get selected servers
|
||||
let selected_server_ids = {
|
||||
let selected = get_selected_servers().lock().unwrap();
|
||||
selected.get(request_id).cloned().unwrap_or_default()
|
||||
selected.get(&request_id).cloned().unwrap_or_default()
|
||||
};
|
||||
|
||||
if selected_server_ids.is_empty() {
|
||||
@@ -645,7 +686,7 @@ pub async fn handle_apply_server_access(
|
||||
let inbound_users_repo = crate::database::repository::InboundUsersRepository::new(db.connection().clone());
|
||||
|
||||
// Parse request ID and get request
|
||||
let request_uuid = Uuid::parse_str(request_id).map_err(|_| "Invalid request ID")?;
|
||||
let request_uuid = Uuid::parse_str(&request_id).map_err(|_| "Invalid request ID")?;
|
||||
let request = request_repo.find_by_id(request_uuid).await
|
||||
.unwrap_or(None)
|
||||
.ok_or("Request not found")?;
|
||||
@@ -690,7 +731,7 @@ pub async fn handle_apply_server_access(
|
||||
// Clean up selected servers storage
|
||||
{
|
||||
let mut selected = get_selected_servers().lock().unwrap();
|
||||
selected.remove(request_id);
|
||||
selected.remove(&request_id);
|
||||
}
|
||||
|
||||
// Update message with success
|
||||
|
||||
@@ -105,6 +105,9 @@ pub async fn handle_callback_query(
|
||||
CallbackData::MyConfigs => {
|
||||
handle_my_configs_edit(bot, &q, &db).await?;
|
||||
}
|
||||
CallbackData::SubscriptionLink => {
|
||||
handle_subscription_link(bot, &q, &db).await?;
|
||||
}
|
||||
CallbackData::Support => {
|
||||
handle_support(bot, &q).await?;
|
||||
}
|
||||
@@ -124,13 +127,20 @@ pub async fn handle_callback_query(
|
||||
handle_show_server_configs(bot, &q, &encoded_server_name, &db).await?;
|
||||
}
|
||||
CallbackData::SelectServerAccess(request_id) => {
|
||||
handle_select_server_access(bot, &q, &request_id, &db).await?;
|
||||
// The request_id is now the full UUID from the mapping
|
||||
let short_id = types::generate_short_request_id(&request_id);
|
||||
handle_select_server_access(bot, &q, &short_id, &db).await?;
|
||||
}
|
||||
CallbackData::ToggleServer(request_id, server_id) => {
|
||||
handle_toggle_server(bot, &q, &request_id, &server_id, &db).await?;
|
||||
// Both IDs are now full UUIDs from the mapping
|
||||
let short_request_id = types::generate_short_request_id(&request_id);
|
||||
let short_server_id = types::generate_short_server_id(&server_id);
|
||||
handle_toggle_server(bot, &q, &short_request_id, &short_server_id, &db).await?;
|
||||
}
|
||||
CallbackData::ApplyServerAccess(request_id) => {
|
||||
handle_apply_server_access(bot, &q, &request_id, &db).await?;
|
||||
// The request_id is now the full UUID from the mapping
|
||||
let short_id = types::generate_short_request_id(&request_id);
|
||||
handle_apply_server_access(bot, &q, &short_id, &db).await?;
|
||||
}
|
||||
CallbackData::Back => {
|
||||
// Back to main menu - edit the existing message
|
||||
|
||||
@@ -24,6 +24,7 @@ pub enum Command {
|
||||
pub enum CallbackData {
|
||||
RequestAccess,
|
||||
MyConfigs,
|
||||
SubscriptionLink,
|
||||
Support,
|
||||
AdminRequests,
|
||||
ApproveRequest(String), // request_id
|
||||
@@ -43,6 +44,7 @@ impl CallbackData {
|
||||
match data {
|
||||
"request_access" => Some(CallbackData::RequestAccess),
|
||||
"my_configs" => Some(CallbackData::MyConfigs),
|
||||
"subscription_link" => Some(CallbackData::SubscriptionLink),
|
||||
"support" => Some(CallbackData::Support),
|
||||
"admin_requests" => Some(CallbackData::AdminRequests),
|
||||
"back" => Some(CallbackData::Back),
|
||||
@@ -57,12 +59,12 @@ impl CallbackData {
|
||||
Some(CallbackData::ViewRequest(id.to_string()))
|
||||
} else if let Some(server_name) = data.strip_prefix("server_configs:") {
|
||||
Some(CallbackData::ShowServerConfigs(server_name.to_string()))
|
||||
} else if let Some(id) = data.strip_prefix("s:") {
|
||||
restore_uuid(id).map(CallbackData::SelectServerAccess)
|
||||
} else if let Some(short_id) = data.strip_prefix("s:") {
|
||||
get_full_request_id(short_id).map(CallbackData::SelectServerAccess)
|
||||
} else if let Some(rest) = data.strip_prefix("t:") {
|
||||
let parts: Vec<&str> = rest.split(':').collect();
|
||||
if parts.len() == 2 {
|
||||
if let (Some(request_id), Some(server_id)) = (restore_uuid(parts[0]), restore_uuid(parts[1])) {
|
||||
if let (Some(request_id), Some(server_id)) = (get_full_request_id(parts[0]), get_full_server_id(parts[1])) {
|
||||
Some(CallbackData::ToggleServer(request_id, server_id))
|
||||
} else {
|
||||
None
|
||||
@@ -70,8 +72,8 @@ impl CallbackData {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(id) = data.strip_prefix("a:") {
|
||||
restore_uuid(id).map(CallbackData::ApplyServerAccess)
|
||||
} else if let Some(short_id) = data.strip_prefix("a:") {
|
||||
get_full_request_id(short_id).map(CallbackData::ApplyServerAccess)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -83,10 +85,86 @@ impl CallbackData {
|
||||
// Global storage for selected servers per request
|
||||
static SELECTED_SERVERS: OnceLock<Arc<Mutex<HashMap<String, Vec<String>>>>> = OnceLock::new();
|
||||
|
||||
// Global storage for request ID mappings (short ID -> full UUID)
|
||||
static REQUEST_ID_MAP: OnceLock<Arc<Mutex<HashMap<String, String>>>> = OnceLock::new();
|
||||
static REQUEST_COUNTER: OnceLock<Arc<Mutex<u32>>> = OnceLock::new();
|
||||
|
||||
// Global storage for server ID mappings (short ID -> full UUID)
|
||||
static SERVER_ID_MAP: OnceLock<Arc<Mutex<HashMap<String, String>>>> = OnceLock::new();
|
||||
static SERVER_COUNTER: OnceLock<Arc<Mutex<u32>>> = OnceLock::new();
|
||||
|
||||
pub fn get_selected_servers() -> &'static Arc<Mutex<HashMap<String, Vec<String>>>> {
|
||||
SELECTED_SERVERS.get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
|
||||
}
|
||||
|
||||
pub fn get_request_id_map() -> &'static Arc<Mutex<HashMap<String, String>>> {
|
||||
REQUEST_ID_MAP.get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
|
||||
}
|
||||
|
||||
pub fn get_request_counter() -> &'static Arc<Mutex<u32>> {
|
||||
REQUEST_COUNTER.get_or_init(|| Arc::new(Mutex::new(0)))
|
||||
}
|
||||
|
||||
pub fn get_server_id_map() -> &'static Arc<Mutex<HashMap<String, String>>> {
|
||||
SERVER_ID_MAP.get_or_init(|| Arc::new(Mutex::new(HashMap::new())))
|
||||
}
|
||||
|
||||
pub fn get_server_counter() -> &'static Arc<Mutex<u32>> {
|
||||
SERVER_COUNTER.get_or_init(|| Arc::new(Mutex::new(0)))
|
||||
}
|
||||
|
||||
/// Generate a short ID for a request UUID and store the mapping
|
||||
pub fn generate_short_request_id(request_uuid: &str) -> String {
|
||||
let mut counter = get_request_counter().lock().unwrap();
|
||||
let mut map = get_request_id_map().lock().unwrap();
|
||||
|
||||
// Check if we already have a short ID for this UUID
|
||||
for (short_id, uuid) in map.iter() {
|
||||
if uuid == request_uuid {
|
||||
return short_id.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new short ID
|
||||
*counter += 1;
|
||||
let short_id = format!("r{}", counter);
|
||||
map.insert(short_id.clone(), request_uuid.to_string());
|
||||
|
||||
short_id
|
||||
}
|
||||
|
||||
/// Get full UUID from short ID
|
||||
pub fn get_full_request_id(short_id: &str) -> Option<String> {
|
||||
let map = get_request_id_map().lock().unwrap();
|
||||
map.get(short_id).cloned()
|
||||
}
|
||||
|
||||
/// Generate a short ID for a server UUID and store the mapping
|
||||
pub fn generate_short_server_id(server_uuid: &str) -> String {
|
||||
let mut counter = get_server_counter().lock().unwrap();
|
||||
let mut map = get_server_id_map().lock().unwrap();
|
||||
|
||||
// Check if we already have a short ID for this UUID
|
||||
for (short_id, uuid) in map.iter() {
|
||||
if uuid == server_uuid {
|
||||
return short_id.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new short ID
|
||||
*counter += 1;
|
||||
let short_id = format!("s{}", counter);
|
||||
map.insert(short_id.clone(), server_uuid.to_string());
|
||||
|
||||
short_id
|
||||
}
|
||||
|
||||
/// Get full server UUID from short ID
|
||||
pub fn get_full_server_id(short_id: &str) -> Option<String> {
|
||||
let map = get_server_id_map().lock().unwrap();
|
||||
map.get(short_id).cloned()
|
||||
}
|
||||
|
||||
/// Helper function to get user language from Telegram user data
|
||||
pub fn get_user_language(user: &User) -> Language {
|
||||
Language::from_telegram_code(user.language_code.as_deref())
|
||||
@@ -97,6 +175,7 @@ pub fn get_main_keyboard(is_admin: bool, lang: Language) -> InlineKeyboardMarkup
|
||||
let l10n = LocalizationService::new();
|
||||
|
||||
let mut keyboard = vec![
|
||||
vec![InlineKeyboardButton::callback("🔗 Subscription Link", "subscription_link")],
|
||||
vec![InlineKeyboardButton::callback(l10n.get(lang.clone(), "my_configs"), "my_configs")],
|
||||
vec![InlineKeyboardButton::callback(l10n.get(lang.clone(), "support"), "support")],
|
||||
];
|
||||
|
||||
@@ -665,5 +665,70 @@ async fn notify_admins_new_request(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle subscription link request
|
||||
pub async fn handle_subscription_link(
|
||||
bot: Bot,
|
||||
q: &CallbackQuery,
|
||||
db: &DatabaseManager,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let from = q.from.clone();
|
||||
let telegram_id = from.id.0 as i64;
|
||||
let lang = get_user_language(&from);
|
||||
let l10n = LocalizationService::new();
|
||||
|
||||
// Get user from database
|
||||
let user_repo = UserRepository::new(db.connection());
|
||||
if let Ok(Some(user)) = user_repo.get_by_telegram_id(telegram_id).await {
|
||||
// Generate subscription URL
|
||||
let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost:8080".to_string());
|
||||
let subscription_url = format!("{}/sub/{}", base_url, user.id);
|
||||
|
||||
let message = match lang {
|
||||
Language::Russian => {
|
||||
format!(
|
||||
"🔗 <b>Ваша ссылка подписки</b>\n\n\
|
||||
Скопируйте эту ссылку и добавьте её в ваш VPN-клиент:\n\n\
|
||||
<code>{}</code>\n\n\
|
||||
💡 <i>Эта ссылка содержит все ваши конфигурации и автоматически обновляется при изменениях</i>",
|
||||
subscription_url
|
||||
)
|
||||
},
|
||||
Language::English => {
|
||||
format!(
|
||||
"🔗 <b>Your Subscription Link</b>\n\n\
|
||||
Copy this link and add it to your VPN client:\n\n\
|
||||
<code>{}</code>\n\n\
|
||||
💡 <i>This link contains all your configurations and updates automatically when changes are made</i>",
|
||||
subscription_url
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let keyboard = InlineKeyboardMarkup::new(vec![
|
||||
vec![InlineKeyboardButton::callback(l10n.get(lang, "back"), "back")],
|
||||
]);
|
||||
|
||||
// Edit the existing message
|
||||
if let Some(msg) = &q.message {
|
||||
if let teloxide::types::MaybeInaccessibleMessage::Regular(regular_msg) = msg {
|
||||
let chat_id = regular_msg.chat.id;
|
||||
bot.edit_message_text(chat_id, regular_msg.id, message)
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
.reply_markup(keyboard)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// User not found - this shouldn't happen for registered users
|
||||
bot.answer_callback_query(q.id.clone())
|
||||
.text("User not found")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
bot.answer_callback_query(q.id.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -144,6 +144,21 @@ impl TelegramService {
|
||||
Err(anyhow::anyhow!("Bot is not running"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Send message to user with inline keyboard
|
||||
pub async fn send_message_with_keyboard(&self, chat_id: i64, text: String, keyboard: teloxide::types::InlineKeyboardMarkup) -> Result<()> {
|
||||
let bot_guard = self.bot.read().await;
|
||||
|
||||
if let Some(bot) = bot_guard.as_ref() {
|
||||
bot.send_message(ChatId(chat_id), text)
|
||||
.parse_mode(teloxide::types::ParseMode::Html)
|
||||
.reply_markup(keyboard)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Bot is not running"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Send message to all admins
|
||||
pub async fn broadcast_to_admins(&self, text: String) -> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user