mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-24 17:29:08 +00:00
Added telegram
This commit is contained in:
175
src/services/telegram/mod.rs
Normal file
175
src/services/telegram/mod.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use teloxide::{Bot, prelude::*};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::DatabaseManager;
|
||||
use crate::database::repository::TelegramConfigRepository;
|
||||
use crate::database::entities::telegram_config::Model as TelegramConfig;
|
||||
|
||||
pub mod bot;
|
||||
pub mod handlers;
|
||||
pub mod error;
|
||||
|
||||
pub use error::TelegramError;
|
||||
|
||||
/// Main Telegram service that manages the bot lifecycle
|
||||
pub struct TelegramService {
|
||||
db: DatabaseManager,
|
||||
bot: Arc<RwLock<Option<Bot>>>,
|
||||
config: Arc<RwLock<Option<TelegramConfig>>>,
|
||||
shutdown_signal: Arc<RwLock<Option<tokio::sync::oneshot::Sender<()>>>>,
|
||||
}
|
||||
|
||||
impl TelegramService {
|
||||
/// Create a new Telegram service
|
||||
pub fn new(db: DatabaseManager) -> Self {
|
||||
Self {
|
||||
db,
|
||||
bot: Arc::new(RwLock::new(None)),
|
||||
config: Arc::new(RwLock::new(None)),
|
||||
shutdown_signal: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize and start the bot if active configuration exists
|
||||
pub async fn initialize(&self) -> Result<()> {
|
||||
let repo = TelegramConfigRepository::new(self.db.connection());
|
||||
|
||||
// Get active configuration
|
||||
if let Some(config) = repo.get_active().await? {
|
||||
self.start_with_config(config).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start bot with specific configuration
|
||||
pub async fn start_with_config(&self, config: TelegramConfig) -> Result<()> {
|
||||
// Stop existing bot if running
|
||||
self.stop().await?;
|
||||
|
||||
// Create new bot instance
|
||||
let bot = Bot::new(&config.bot_token);
|
||||
|
||||
// Verify token by calling getMe
|
||||
match bot.get_me().await {
|
||||
Ok(me) => {
|
||||
let username = me.user.username.unwrap_or_default();
|
||||
tracing::info!("Telegram bot started: @{}", username);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(anyhow::anyhow!("Invalid bot token: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
// Store bot and config
|
||||
*self.bot.write().await = Some(bot.clone());
|
||||
*self.config.write().await = Some(config.clone());
|
||||
|
||||
// Start polling in background
|
||||
if config.is_active {
|
||||
self.start_polling(bot).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start polling for updates
|
||||
async fn start_polling(&self, bot: Bot) -> Result<()> {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
*self.shutdown_signal.write().await = Some(tx);
|
||||
|
||||
let db = self.db.clone();
|
||||
|
||||
// Spawn polling task
|
||||
tokio::spawn(async move {
|
||||
bot::run_polling(bot, db, rx).await;
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop the bot
|
||||
pub async fn stop(&self) -> Result<()> {
|
||||
// Send shutdown signal if polling is running
|
||||
if let Some(tx) = self.shutdown_signal.write().await.take() {
|
||||
let _ = tx.send(()); // Ignore error if receiver is already dropped
|
||||
}
|
||||
|
||||
// Clear bot and config
|
||||
*self.bot.write().await = None;
|
||||
*self.config.write().await = None;
|
||||
|
||||
tracing::info!("Telegram bot stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update configuration and restart if needed
|
||||
pub async fn update_config(&self, config_id: Uuid) -> Result<()> {
|
||||
let repo = TelegramConfigRepository::new(self.db.connection());
|
||||
|
||||
if let Some(config) = repo.find_by_id(config_id).await? {
|
||||
if config.is_active {
|
||||
self.start_with_config(config).await?;
|
||||
} else {
|
||||
self.stop().await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get current bot status
|
||||
pub async fn get_status(&self) -> BotStatus {
|
||||
let bot_guard = self.bot.read().await;
|
||||
let config_guard = self.config.read().await;
|
||||
|
||||
BotStatus {
|
||||
is_running: bot_guard.is_some(),
|
||||
config: config_guard.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Send message to user
|
||||
pub async fn send_message(&self, chat_id: i64, text: String) -> Result<()> {
|
||||
let bot_guard = self.bot.read().await;
|
||||
|
||||
if let Some(bot) = bot_guard.as_ref() {
|
||||
bot.send_message(ChatId(chat_id), text).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<()> {
|
||||
let bot_guard = self.bot.read().await;
|
||||
|
||||
if let Some(bot) = bot_guard.as_ref() {
|
||||
let user_repo = crate::database::repository::UserRepository::new(self.db.connection());
|
||||
let admins = user_repo.get_telegram_admins().await?;
|
||||
|
||||
for admin in admins {
|
||||
if let Some(telegram_id) = admin.telegram_id {
|
||||
if let Err(e) = bot.send_message(ChatId(telegram_id), text.clone()).await {
|
||||
tracing::warn!("Failed to send message to admin {}: {}", telegram_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Bot is not running"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bot status information
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct BotStatus {
|
||||
pub is_running: bool,
|
||||
pub config: Option<TelegramConfig>,
|
||||
}
|
||||
Reference in New Issue
Block a user