mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-24 01:09:08 +00:00
371 lines
12 KiB
Rust
371 lines
12 KiB
Rust
use teloxide::{prelude::*, utils::command::BotCommands};
|
|
use teloxide::types::Me;
|
|
use uuid::Uuid;
|
|
|
|
use crate::database::DatabaseManager;
|
|
use crate::database::repository::UserRepository;
|
|
use crate::database::entities::user::CreateUserDto;
|
|
|
|
/// Available bot commands
|
|
#[derive(BotCommands, Clone)]
|
|
#[command(rename_rule = "lowercase", description = "Available commands:")]
|
|
pub enum Command {
|
|
#[command(description = "Start the bot and register")]
|
|
Start,
|
|
#[command(description = "Show help message")]
|
|
Help,
|
|
#[command(description = "Show your status")]
|
|
Status,
|
|
#[command(description = "List available configurations")]
|
|
Configs,
|
|
// Admin commands
|
|
#[command(description = "[Admin] List all users")]
|
|
Users,
|
|
#[command(description = "[Admin] List all servers")]
|
|
Servers,
|
|
#[command(description = "[Admin] Show statistics")]
|
|
Stats,
|
|
#[command(description = "[Admin] Broadcast message", parse_with = "split")]
|
|
Broadcast { message: String },
|
|
}
|
|
|
|
/// Handle command messages
|
|
pub async fn handle_command(
|
|
bot: Bot,
|
|
msg: Message,
|
|
cmd: Command,
|
|
db: DatabaseManager,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let chat_id = msg.chat.id;
|
|
let from = msg.from.as_ref().ok_or("No sender info")?;
|
|
let telegram_id = from.id.0 as i64;
|
|
|
|
let user_repo = UserRepository::new(db.connection());
|
|
|
|
match cmd {
|
|
Command::Start => {
|
|
handle_start(bot, chat_id, telegram_id, from, &user_repo).await?;
|
|
}
|
|
Command::Help => {
|
|
bot.send_message(chat_id, Command::descriptions().to_string()).await?;
|
|
}
|
|
Command::Status => {
|
|
handle_status(bot, chat_id, telegram_id, &user_repo, &db).await?;
|
|
}
|
|
Command::Configs => {
|
|
handle_configs(bot, chat_id, telegram_id, &user_repo, &db).await?;
|
|
}
|
|
// Admin commands
|
|
Command::Users => {
|
|
if !user_repo.is_telegram_id_admin(telegram_id).await.unwrap_or(false) {
|
|
bot.send_message(chat_id, "❌ You are not authorized to use this command").await?;
|
|
return Ok(());
|
|
}
|
|
handle_users(bot, chat_id, &user_repo).await?;
|
|
}
|
|
Command::Servers => {
|
|
if !user_repo.is_telegram_id_admin(telegram_id).await.unwrap_or(false) {
|
|
bot.send_message(chat_id, "❌ You are not authorized to use this command").await?;
|
|
return Ok(());
|
|
}
|
|
handle_servers(bot, chat_id, &db).await?;
|
|
}
|
|
Command::Stats => {
|
|
if !user_repo.is_telegram_id_admin(telegram_id).await.unwrap_or(false) {
|
|
bot.send_message(chat_id, "❌ You are not authorized to use this command").await?;
|
|
return Ok(());
|
|
}
|
|
handle_stats(bot, chat_id, &db).await?;
|
|
}
|
|
Command::Broadcast { message } => {
|
|
if !user_repo.is_telegram_id_admin(telegram_id).await.unwrap_or(false) {
|
|
bot.send_message(chat_id, "❌ You are not authorized to use this command").await?;
|
|
return Ok(());
|
|
}
|
|
handle_broadcast(bot, chat_id, message, &user_repo).await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle regular text messages
|
|
pub async fn handle_message(
|
|
bot: Bot,
|
|
msg: Message,
|
|
db: DatabaseManager,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
if let Some(text) = msg.text() {
|
|
if !text.starts_with('/') {
|
|
bot.send_message(
|
|
msg.chat.id,
|
|
"Please use /help to see available commands"
|
|
).await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /start command
|
|
async fn handle_start(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
telegram_id: i64,
|
|
from: &teloxide::types::User,
|
|
user_repo: &UserRepository,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
// Check if user already exists
|
|
if let Some(user) = user_repo.get_by_telegram_id(telegram_id).await.unwrap_or(None) {
|
|
let message = format!(
|
|
"👋 Welcome back, {}!\n\n\
|
|
You are already registered.\n\
|
|
Use /help to see available commands.",
|
|
user.name
|
|
);
|
|
bot.send_message(chat_id, message).await?;
|
|
} else {
|
|
// Create new user
|
|
let username = from.username.as_deref().unwrap_or("Unknown");
|
|
let full_name = format!(
|
|
"{} {}",
|
|
from.first_name,
|
|
from.last_name.as_deref().unwrap_or("")
|
|
).trim().to_string();
|
|
|
|
let dto = CreateUserDto {
|
|
name: if !full_name.is_empty() { full_name } else { username.to_string() },
|
|
comment: Some(format!("Telegram user: @{}", username)),
|
|
telegram_id: Some(telegram_id),
|
|
is_telegram_admin: false,
|
|
};
|
|
|
|
match user_repo.create(dto).await {
|
|
Ok(user) => {
|
|
let message = format!(
|
|
"✅ Registration successful!\n\n\
|
|
Name: {}\n\
|
|
User ID: {}\n\n\
|
|
Use /help to see available commands.",
|
|
user.name, user.id
|
|
);
|
|
bot.send_message(chat_id, message).await?;
|
|
}
|
|
Err(e) => {
|
|
bot.send_message(
|
|
chat_id,
|
|
format!("❌ Registration failed: {}", e)
|
|
).await?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /status command
|
|
async fn handle_status(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
telegram_id: i64,
|
|
user_repo: &UserRepository,
|
|
db: &DatabaseManager,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
if let Some(user) = user_repo.get_by_telegram_id(telegram_id).await.unwrap_or(None) {
|
|
let server_inbound_repo = crate::database::repository::ServerInboundRepository::new(db.connection());
|
|
let configs = server_inbound_repo.find_by_user_id(user.id).await.unwrap_or_default();
|
|
|
|
let admin_status = if user.is_telegram_admin { "Admin" } else { "User" };
|
|
|
|
let message = format!(
|
|
"📊 Your Status\n\n\
|
|
Name: {}\n\
|
|
User ID: {}\n\
|
|
Role: {}\n\
|
|
Active Configs: {}\n\
|
|
Registered: {}",
|
|
user.name,
|
|
user.id,
|
|
admin_status,
|
|
configs.len(),
|
|
user.created_at.format("%Y-%m-%d %H:%M UTC")
|
|
);
|
|
|
|
bot.send_message(chat_id, message).await?;
|
|
} else {
|
|
bot.send_message(
|
|
chat_id,
|
|
"❌ You are not registered. Use /start to register."
|
|
).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /configs command
|
|
async fn handle_configs(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
telegram_id: i64,
|
|
user_repo: &UserRepository,
|
|
db: &DatabaseManager,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
if let Some(user) = user_repo.get_by_telegram_id(telegram_id).await.unwrap_or(None) {
|
|
let server_inbound_repo = crate::database::repository::ServerInboundRepository::new(db.connection());
|
|
let configs = server_inbound_repo.find_by_user_id(user.id).await.unwrap_or_default();
|
|
|
|
if configs.is_empty() {
|
|
bot.send_message(chat_id, "You don't have any configurations yet.").await?;
|
|
} else {
|
|
let mut message = String::from("📋 Your Configurations:\n\n");
|
|
|
|
for (i, config) in configs.iter().enumerate() {
|
|
message.push_str(&format!(
|
|
"{}. {} (Port: {})\n",
|
|
i + 1,
|
|
config.tag,
|
|
config.port_override.unwrap_or(0)
|
|
));
|
|
}
|
|
|
|
bot.send_message(chat_id, message).await?;
|
|
}
|
|
} else {
|
|
bot.send_message(
|
|
chat_id,
|
|
"❌ You are not registered. Use /start to register."
|
|
).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /users command (admin only)
|
|
async fn handle_users(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
user_repo: &UserRepository,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let users = user_repo.get_all(1, 100).await.unwrap_or_default();
|
|
|
|
if users.is_empty() {
|
|
bot.send_message(chat_id, "No users found.").await?;
|
|
} else {
|
|
let mut message = String::from("👥 Users:\n\n");
|
|
|
|
for (i, user) in users.iter().enumerate() {
|
|
let telegram_status = if user.telegram_id.is_some() { "✅" } else { "❌" };
|
|
let admin_status = if user.is_telegram_admin { " (Admin)" } else { "" };
|
|
|
|
message.push_str(&format!(
|
|
"{}. {} {} {}{}\n",
|
|
i + 1,
|
|
user.name,
|
|
telegram_status,
|
|
user.id,
|
|
admin_status
|
|
));
|
|
}
|
|
|
|
bot.send_message(chat_id, message).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /servers command (admin only)
|
|
async fn handle_servers(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
db: &DatabaseManager,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let server_repo = crate::database::repository::ServerRepository::new(db.connection());
|
|
let servers = server_repo.get_all().await.unwrap_or_default();
|
|
|
|
if servers.is_empty() {
|
|
bot.send_message(chat_id, "No servers found.").await?;
|
|
} else {
|
|
let mut message = String::from("🖥️ Servers:\n\n");
|
|
|
|
for (i, server) in servers.iter().enumerate() {
|
|
let status = if server.status == "active" { "✅" } else { "❌" };
|
|
|
|
message.push_str(&format!(
|
|
"{}. {} {} - {}\n",
|
|
i + 1,
|
|
status,
|
|
server.name,
|
|
server.hostname
|
|
));
|
|
}
|
|
|
|
bot.send_message(chat_id, message).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /stats command (admin only)
|
|
async fn handle_stats(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
db: &DatabaseManager,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let user_repo = UserRepository::new(db.connection());
|
|
let server_repo = crate::database::repository::ServerRepository::new(db.connection());
|
|
let inbound_repo = crate::database::repository::ServerInboundRepository::new(db.connection());
|
|
|
|
let user_count = user_repo.count().await.unwrap_or(0);
|
|
let server_count = server_repo.count().await.unwrap_or(0);
|
|
let inbound_count = inbound_repo.count().await.unwrap_or(0);
|
|
|
|
let message = format!(
|
|
"📊 Statistics\n\n\
|
|
Total Users: {}\n\
|
|
Total Servers: {}\n\
|
|
Total Inbounds: {}",
|
|
user_count,
|
|
server_count,
|
|
inbound_count
|
|
);
|
|
|
|
bot.send_message(chat_id, message).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Handle /broadcast command (admin only)
|
|
async fn handle_broadcast(
|
|
bot: Bot,
|
|
chat_id: ChatId,
|
|
message: String,
|
|
user_repo: &UserRepository,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
let users = user_repo.get_all(1, 1000).await.unwrap_or_default();
|
|
let mut sent_count = 0;
|
|
let mut failed_count = 0;
|
|
|
|
for user in users {
|
|
if let Some(telegram_id) = user.telegram_id {
|
|
match bot.send_message(ChatId(telegram_id), &message).await {
|
|
Ok(_) => sent_count += 1,
|
|
Err(e) => {
|
|
tracing::warn!("Failed to send broadcast to {}: {}", telegram_id, e);
|
|
failed_count += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bot.send_message(
|
|
chat_id,
|
|
format!(
|
|
"✅ Broadcast complete\n\
|
|
Sent: {}\n\
|
|
Failed: {}",
|
|
sent_count, failed_count
|
|
)
|
|
).await?;
|
|
|
|
Ok(())
|
|
} |