- Add Gitea Actions workflow for manual AlexHost deployment - Add docker-compose.alexhost.yml for production deployment - Add deploy-alexhost.sh script with server-side build support - Add Bot Control feature (Start/Stop/Restart) for remote bot management - Add discovery control endpoint in TeleBot - Update TeleBot with StartPollingAsync/StopPolling/RestartPollingAsync - Fix platform architecture issues by building on target server - Update docker-compose configurations for all environments Deployment tested successfully: - TeleShop: healthy at https://teleshop.silentmary.mywire.org - TeleBot: healthy with discovery integration - SilverPay: connectivity verified 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
923 lines
32 KiB
C#
923 lines
32 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using LittleShop.DTOs;
|
|
using LittleShop.Enums;
|
|
using LittleShop.Services;
|
|
using LittleShop.Models;
|
|
|
|
namespace LittleShop.Areas.Admin.Controllers;
|
|
|
|
[Area("Admin")]
|
|
[Authorize(Policy = "AdminOnly")]
|
|
public class BotsController : Controller
|
|
{
|
|
private readonly IBotService _botService;
|
|
private readonly IBotMetricsService _metricsService;
|
|
private readonly ITelegramBotManagerService _telegramManager;
|
|
private readonly IBotDiscoveryService _discoveryService;
|
|
private readonly ILogger<BotsController> _logger;
|
|
|
|
public BotsController(
|
|
IBotService botService,
|
|
IBotMetricsService metricsService,
|
|
ITelegramBotManagerService telegramManager,
|
|
IBotDiscoveryService discoveryService,
|
|
ILogger<BotsController> logger)
|
|
{
|
|
_botService = botService;
|
|
_metricsService = metricsService;
|
|
_telegramManager = telegramManager;
|
|
_discoveryService = discoveryService;
|
|
_logger = logger;
|
|
}
|
|
|
|
// GET: Admin/Bots
|
|
public async Task<IActionResult> Index()
|
|
{
|
|
var bots = await _botService.GetAllBotsAsync();
|
|
return View(bots);
|
|
}
|
|
|
|
// GET: Admin/Bots/Details/5
|
|
public async Task<IActionResult> Details(Guid id)
|
|
{
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
return NotFound();
|
|
|
|
// Get metrics summary for the last 30 days
|
|
var metricsSummary = await _metricsService.GetMetricsSummaryAsync(id, DateTime.UtcNow.AddDays(-30), DateTime.UtcNow);
|
|
ViewData["MetricsSummary"] = metricsSummary;
|
|
|
|
// Get active sessions
|
|
var activeSessions = await _metricsService.GetBotSessionsAsync(id, activeOnly: true);
|
|
ViewData["ActiveSessions"] = activeSessions;
|
|
|
|
return View(bot);
|
|
}
|
|
|
|
// GET: Admin/Bots/Create
|
|
public IActionResult Create()
|
|
{
|
|
return View(new BotRegistrationDto());
|
|
}
|
|
|
|
// GET: Admin/Bots/Wizard
|
|
public IActionResult Wizard()
|
|
{
|
|
return View(new BotWizardDto());
|
|
}
|
|
|
|
// POST: Admin/Bots/Wizard
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Wizard(BotWizardDto dto)
|
|
{
|
|
_logger.LogInformation("Wizard POST received - BotName: '{BotName}', BotUsername: '{BotUsername}'", dto.BotName, dto.BotUsername);
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
_logger.LogWarning("Validation failed");
|
|
foreach (var error in ModelState)
|
|
{
|
|
_logger.LogWarning("Field {Field}: {Errors}", error.Key, string.Join(", ", error.Value.Errors.Select(e => e.ErrorMessage)));
|
|
}
|
|
return View(dto);
|
|
}
|
|
|
|
// Auto-assign personality if not selected
|
|
if (string.IsNullOrEmpty(dto.PersonalityName))
|
|
{
|
|
var personalities = new[] { "Alan", "Dave", "Sarah", "Mike", "Emma", "Tom" };
|
|
var random = new Random();
|
|
dto.PersonalityName = personalities[random.Next(personalities.Length)];
|
|
}
|
|
|
|
// Generate BotFather commands
|
|
var commands = GenerateBotFatherCommands(dto);
|
|
ViewData["BotFatherCommands"] = commands;
|
|
ViewData["ShowCommands"] = true;
|
|
|
|
_logger.LogInformation("Generated BotFather commands successfully for bot '{BotName}' with personality '{PersonalityName}'", dto.BotName, dto.PersonalityName);
|
|
|
|
return View(dto);
|
|
}
|
|
|
|
// POST: Admin/Bots/CompleteWizard
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> CompleteWizard(BotWizardDto dto)
|
|
{
|
|
if (string.IsNullOrEmpty(dto.BotToken))
|
|
{
|
|
ModelState.AddModelError("BotToken", "Bot token is required");
|
|
ViewData["BotFatherCommands"] = GenerateBotFatherCommands(dto);
|
|
ViewData["ShowCommands"] = true;
|
|
return View("Wizard", dto);
|
|
}
|
|
|
|
// Validate token first
|
|
if (!await ValidateTelegramToken(dto.BotToken))
|
|
{
|
|
ModelState.AddModelError("BotToken", "Invalid bot token");
|
|
ViewData["BotFatherCommands"] = GenerateBotFatherCommands(dto);
|
|
ViewData["ShowCommands"] = true;
|
|
return View("Wizard", dto);
|
|
}
|
|
|
|
// Create the bot
|
|
var registrationDto = new BotRegistrationDto
|
|
{
|
|
Name = dto.BotName,
|
|
Description = dto.Description,
|
|
Type = BotType.Telegram,
|
|
Version = "1.0.0",
|
|
PersonalityName = dto.PersonalityName,
|
|
InitialSettings = new Dictionary<string, object>
|
|
{
|
|
["telegram"] = new { botToken = dto.BotToken },
|
|
["personality"] = new { name = dto.PersonalityName }
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
var result = await _botService.RegisterBotAsync(registrationDto);
|
|
|
|
// Add bot to Telegram manager
|
|
var telegramAdded = await _telegramManager.AddBotAsync(result.BotId, dto.BotToken);
|
|
|
|
if (telegramAdded)
|
|
{
|
|
TempData["Success"] = $"Bot '{result.Name}' created successfully and is now running on Telegram!";
|
|
}
|
|
else
|
|
{
|
|
TempData["Warning"] = $"Bot '{result.Name}' created but failed to connect to Telegram. Check token.";
|
|
}
|
|
|
|
return RedirectToAction(nameof(Details), new { id = result.BotId });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to create bot");
|
|
ModelState.AddModelError("", $"Failed to create bot: {ex.Message}");
|
|
return View("Wizard", dto);
|
|
}
|
|
}
|
|
|
|
// POST: Admin/Bots/Create
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Create(BotRegistrationDto dto)
|
|
{
|
|
_logger.LogInformation("Received bot registration: Name={Name}, Type={Type}, Version={Version}",
|
|
dto?.Name, dto?.Type, dto?.Version);
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
_logger.LogWarning("Model validation failed for bot registration");
|
|
foreach (var error in ModelState.Values.SelectMany(v => v.Errors))
|
|
{
|
|
_logger.LogWarning("Validation error: {Error}", error.ErrorMessage);
|
|
}
|
|
return View(dto);
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await _botService.RegisterBotAsync(dto);
|
|
_logger.LogInformation("Bot registered successfully: {BotId}, Key: {KeyPrefix}...",
|
|
result.BotId, result.BotKey.Substring(0, 8));
|
|
|
|
TempData["BotKey"] = result.BotKey;
|
|
TempData["Success"] = $"Bot '{result.Name}' created successfully. Save the API key securely!";
|
|
return RedirectToAction(nameof(Details), new { id = result.BotId });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to create bot");
|
|
ModelState.AddModelError("", $"Failed to create bot: {ex.Message}");
|
|
return View(dto);
|
|
}
|
|
}
|
|
|
|
// GET: Admin/Bots/Edit/5
|
|
public async Task<IActionResult> Edit(Guid id)
|
|
{
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
return NotFound();
|
|
|
|
ViewData["BotSettings"] = JsonSerializer.Serialize(bot.Settings, new JsonSerializerOptions { WriteIndented = true });
|
|
return View(bot);
|
|
}
|
|
|
|
// POST: Admin/Bots/Edit/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Edit(Guid id, string settingsJson, BotStatus status)
|
|
{
|
|
try
|
|
{
|
|
// Parse and update settings
|
|
var settings = JsonSerializer.Deserialize<Dictionary<string, object>>(settingsJson) ?? new Dictionary<string, object>();
|
|
var updateDto = new UpdateBotSettingsDto { Settings = settings };
|
|
|
|
await _botService.UpdateBotSettingsAsync(id, updateDto);
|
|
await _botService.UpdateBotStatusAsync(id, status);
|
|
|
|
TempData["Success"] = "Bot updated successfully";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to update bot");
|
|
TempData["Error"] = "Failed to update bot";
|
|
return RedirectToAction(nameof(Edit), new { id });
|
|
}
|
|
}
|
|
|
|
// GET: Admin/Bots/Metrics/5
|
|
public async Task<IActionResult> Metrics(Guid id, DateTime? startDate, DateTime? endDate)
|
|
{
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
return NotFound();
|
|
|
|
var start = startDate ?? DateTime.UtcNow.AddDays(-7);
|
|
var end = endDate ?? DateTime.UtcNow;
|
|
|
|
var metricsSummary = await _metricsService.GetMetricsSummaryAsync(id, start, end);
|
|
var sessionSummary = await _metricsService.GetSessionSummaryAsync(id, start, end);
|
|
|
|
ViewData["Bot"] = bot;
|
|
ViewData["SessionSummary"] = sessionSummary;
|
|
ViewData["StartDate"] = start;
|
|
ViewData["EndDate"] = end;
|
|
|
|
return View(metricsSummary);
|
|
}
|
|
|
|
// POST: Admin/Bots/Delete/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Delete(Guid id)
|
|
{
|
|
var success = await _botService.DeleteBotAsync(id);
|
|
if (!success)
|
|
{
|
|
TempData["Error"] = "Failed to delete bot";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
TempData["Success"] = "Bot deleted successfully";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
// POST: Admin/Bots/Suspend/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Suspend(Guid id)
|
|
{
|
|
await _botService.UpdateBotStatusAsync(id, BotStatus.Suspended);
|
|
TempData["Success"] = "Bot suspended";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// POST: Admin/Bots/Activate/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> Activate(Guid id)
|
|
{
|
|
await _botService.UpdateBotStatusAsync(id, BotStatus.Active);
|
|
TempData["Success"] = "Bot activated";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// POST: Admin/Bots/UpdateWallets/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> UpdateWallets(Guid id, Dictionary<string, string> wallets)
|
|
{
|
|
try
|
|
{
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
{
|
|
TempData["Error"] = "Bot not found";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
// Get current settings
|
|
var settings = bot.Settings ?? new Dictionary<string, object>();
|
|
|
|
// Filter out empty wallet addresses
|
|
var validWallets = wallets
|
|
.Where(w => !string.IsNullOrWhiteSpace(w.Value))
|
|
.ToDictionary(w => w.Key, w => w.Value.Trim());
|
|
|
|
// Update or add wallets section
|
|
settings["wallets"] = validWallets;
|
|
|
|
// Save settings
|
|
await _botService.UpdateBotSettingsAsync(id, new UpdateBotSettingsDto { Settings = settings });
|
|
|
|
_logger.LogInformation("Updated {Count} wallet addresses for bot {BotId}", validWallets.Count, id);
|
|
TempData["Success"] = $"Updated {validWallets.Count} wallet address(es) successfully";
|
|
|
|
return RedirectToAction(nameof(Edit), new { id });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to update wallets for bot {BotId}", id);
|
|
TempData["Error"] = "Failed to update wallet addresses";
|
|
return RedirectToAction(nameof(Edit), new { id });
|
|
}
|
|
}
|
|
|
|
// GET: Admin/Bots/RegenerateKey/5
|
|
public IActionResult RegenerateKey(Guid id)
|
|
{
|
|
// This would require updating the bot model to support key regeneration
|
|
TempData["Error"] = "Key regeneration not yet implemented";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// GET: Admin/Bots/ShareCard/5
|
|
public async Task<IActionResult> ShareCard(Guid id)
|
|
{
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
return NotFound();
|
|
|
|
// Build the tg.me link
|
|
var telegramLink = !string.IsNullOrEmpty(bot.PlatformUsername)
|
|
? $"https://t.me/{bot.PlatformUsername}"
|
|
: null;
|
|
|
|
ViewData["TelegramLink"] = telegramLink;
|
|
|
|
return View(bot);
|
|
}
|
|
|
|
private string GenerateBotFatherCommands(BotWizardDto dto)
|
|
{
|
|
var commands = new List<string>
|
|
{
|
|
"1. Open Telegram and find @BotFather",
|
|
"2. Send: /newbot",
|
|
$"3. Send bot name: {dto.BotName}",
|
|
$"4. Send bot username: {dto.BotUsername}",
|
|
"5. Copy the token from BotFather's response",
|
|
"6. Paste the token in the field below"
|
|
};
|
|
|
|
if (!string.IsNullOrEmpty(dto.Description))
|
|
{
|
|
commands.Add($"7. Optional: Send /setdescription and then: {dto.Description}");
|
|
}
|
|
|
|
return string.Join("\n", commands);
|
|
}
|
|
|
|
|
|
private async Task<bool> ValidateTelegramToken(string token)
|
|
{
|
|
try
|
|
{
|
|
using var httpClient = new HttpClient();
|
|
var response = await httpClient.GetAsync($"https://api.telegram.org/bot{token}/getMe");
|
|
return response.IsSuccessStatusCode;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#region Remote Bot Discovery
|
|
|
|
// GET: Admin/Bots/DiscoverRemote
|
|
public IActionResult DiscoverRemote()
|
|
{
|
|
return View(new DiscoveryWizardViewModel());
|
|
}
|
|
|
|
// POST: Admin/Bots/ProbeRemote
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ProbeRemote(DiscoveryWizardViewModel model)
|
|
{
|
|
_logger.LogInformation("Probing remote TeleBot at {IpAddress}:{Port}", model.IpAddress, model.Port);
|
|
|
|
var result = await _discoveryService.ProbeRemoteBotAsync(model.IpAddress, model.Port);
|
|
|
|
if (result.Success && result.ProbeResponse != null)
|
|
{
|
|
model.ProbeResponse = result.ProbeResponse;
|
|
model.BotName = result.ProbeResponse.Name;
|
|
model.CurrentStep = 2;
|
|
model.SuccessMessage = "TeleBot discovered successfully!";
|
|
|
|
// Auto-select a personality if not already configured
|
|
if (string.IsNullOrEmpty(model.PersonalityName))
|
|
{
|
|
var personalities = new[] { "Alan", "Dave", "Sarah", "Mike", "Emma", "Tom" };
|
|
model.PersonalityName = personalities[new Random().Next(personalities.Length)];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
model.ErrorMessage = result.Message;
|
|
model.CurrentStep = 1;
|
|
}
|
|
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
|
|
// POST: Admin/Bots/RegisterRemote
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> RegisterRemote(DiscoveryWizardViewModel model)
|
|
{
|
|
_logger.LogInformation("Registering remote bot: {BotName} at {IpAddress}:{Port}",
|
|
model.BotName, model.IpAddress, model.Port);
|
|
|
|
if (string.IsNullOrEmpty(model.BotName))
|
|
{
|
|
model.ErrorMessage = "Bot name is required";
|
|
model.CurrentStep = 2;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Create the bot in the database
|
|
var registrationDto = new BotRegistrationDto
|
|
{
|
|
Name = model.BotName,
|
|
Description = model.Description,
|
|
Type = BotType.Telegram,
|
|
Version = model.ProbeResponse?.Version ?? "1.0.0",
|
|
PersonalityName = model.PersonalityName,
|
|
InitialSettings = new Dictionary<string, object>
|
|
{
|
|
["discovery"] = new { remoteAddress = model.IpAddress, remotePort = model.Port }
|
|
}
|
|
};
|
|
|
|
var botResult = await _botService.RegisterBotAsync(registrationDto);
|
|
|
|
// Update the bot with remote discovery info
|
|
var bot = await _botService.GetBotByIdAsync(botResult.BotId);
|
|
if (bot != null)
|
|
{
|
|
// Update remote fields directly (we'll need to add this method to IBotService)
|
|
await UpdateBotRemoteInfoAsync(botResult.BotId, model.IpAddress, model.Port,
|
|
model.ProbeResponse?.InstanceId, DiscoveryStatus.Discovered);
|
|
}
|
|
|
|
// Initialize the remote TeleBot with the BotKey
|
|
var initResult = await _discoveryService.InitializeRemoteBotAsync(botResult.BotId, model.IpAddress, model.Port);
|
|
|
|
if (initResult.Success)
|
|
{
|
|
// Update status to Initialized
|
|
await UpdateBotRemoteInfoAsync(botResult.BotId, model.IpAddress, model.Port,
|
|
model.ProbeResponse?.InstanceId, DiscoveryStatus.Initialized);
|
|
|
|
model.BotId = botResult.BotId;
|
|
model.BotKey = botResult.BotKey;
|
|
model.CurrentStep = 3;
|
|
model.SuccessMessage = "Bot registered and initialized! Now enter the Telegram bot token.";
|
|
|
|
_logger.LogInformation("Remote bot registered and initialized: {BotId}", botResult.BotId);
|
|
}
|
|
else
|
|
{
|
|
model.ErrorMessage = $"Bot registered but initialization failed: {initResult.Message}";
|
|
model.BotId = botResult.BotId;
|
|
model.BotKey = botResult.BotKey;
|
|
model.CurrentStep = 3;
|
|
}
|
|
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to register remote bot");
|
|
model.ErrorMessage = $"Registration failed: {ex.Message}";
|
|
model.CurrentStep = 2;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
}
|
|
|
|
// POST: Admin/Bots/ConfigureRemote
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ConfigureRemote(DiscoveryWizardViewModel model)
|
|
{
|
|
_logger.LogInformation("Configuring remote bot {BotId} with Telegram token", model.BotId);
|
|
|
|
if (!model.BotId.HasValue)
|
|
{
|
|
model.ErrorMessage = "Bot ID is missing";
|
|
model.CurrentStep = 1;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(model.BotToken))
|
|
{
|
|
model.ErrorMessage = "Telegram bot token is required";
|
|
model.CurrentStep = 3;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
|
|
// Validate the token first
|
|
if (!await ValidateTelegramToken(model.BotToken))
|
|
{
|
|
model.ErrorMessage = "Invalid Telegram bot token. Please check and try again.";
|
|
model.CurrentStep = 3;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Push configuration to the remote TeleBot
|
|
var configResult = await _discoveryService.PushConfigurationAsync(model.BotId.Value, model.BotToken);
|
|
|
|
if (configResult.Success)
|
|
{
|
|
// Update bot settings with the token
|
|
var bot = await _botService.GetBotByIdAsync(model.BotId.Value);
|
|
if (bot != null)
|
|
{
|
|
var settings = bot.Settings ?? new Dictionary<string, object>();
|
|
settings["telegram"] = new { botToken = model.BotToken };
|
|
await _botService.UpdateBotSettingsAsync(model.BotId.Value,
|
|
new UpdateBotSettingsDto { Settings = settings });
|
|
|
|
// Update discovery status to Configured
|
|
await UpdateBotRemoteInfoAsync(model.BotId.Value,
|
|
bot.RemoteAddress ?? model.IpAddress,
|
|
bot.RemotePort ?? model.Port,
|
|
bot.RemoteInstanceId,
|
|
DiscoveryStatus.Configured);
|
|
|
|
// Activate the bot
|
|
await _botService.UpdateBotStatusAsync(model.BotId.Value, BotStatus.Active);
|
|
}
|
|
|
|
TempData["Success"] = $"Remote bot configured successfully! Telegram: @{configResult.TelegramUsername}";
|
|
return RedirectToAction(nameof(Details), new { id = model.BotId.Value });
|
|
}
|
|
else
|
|
{
|
|
model.ErrorMessage = $"Configuration failed: {configResult.Message}";
|
|
model.CurrentStep = 3;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to configure remote bot");
|
|
model.ErrorMessage = $"Configuration failed: {ex.Message}";
|
|
model.CurrentStep = 3;
|
|
return View("DiscoverRemote", model);
|
|
}
|
|
}
|
|
|
|
// POST: Admin/Bots/CheckRemoteStatus/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> CheckRemoteStatus(Guid id)
|
|
{
|
|
_logger.LogInformation("Checking remote status for bot {BotId}", id);
|
|
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
{
|
|
TempData["Error"] = "Bot not found";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
if (!bot.IsRemote)
|
|
{
|
|
TempData["Error"] = "This is not a remote bot";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await _discoveryService.ProbeRemoteBotAsync(bot.RemoteAddress!, bot.RemotePort ?? 5000);
|
|
|
|
if (result.Success && result.ProbeResponse != null)
|
|
{
|
|
// Update discovery status
|
|
await UpdateBotRemoteInfoAsync(id, bot.RemoteAddress!, bot.RemotePort ?? 5000,
|
|
result.ProbeResponse.InstanceId, result.ProbeResponse.Status);
|
|
|
|
var statusMessage = result.ProbeResponse.IsConfigured
|
|
? $"Bot is online and configured. Telegram: @{result.ProbeResponse.TelegramUsername}"
|
|
: "Bot is online but not yet configured with a Telegram token.";
|
|
|
|
TempData["Success"] = statusMessage;
|
|
}
|
|
else
|
|
{
|
|
// Update status to indicate offline
|
|
await UpdateBotRemoteInfoAsync(id, bot.RemoteAddress!, bot.RemotePort ?? 5000,
|
|
bot.RemoteInstanceId, DiscoveryStatus.Offline);
|
|
|
|
TempData["Error"] = $"Remote bot is not responding: {result.Message}";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to check remote status for bot {BotId}", id);
|
|
TempData["Error"] = $"Failed to check status: {ex.Message}";
|
|
}
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// GET: Admin/Bots/RepushConfig/5
|
|
public async Task<IActionResult> RepushConfig(Guid id)
|
|
{
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
return NotFound();
|
|
|
|
if (!bot.IsRemote)
|
|
{
|
|
TempData["Error"] = "This is not a remote bot";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// Try to get existing token from settings
|
|
string? existingToken = null;
|
|
if (bot.Settings.TryGetValue("telegram", out var telegramObj))
|
|
{
|
|
try
|
|
{
|
|
var telegramJson = JsonSerializer.Serialize(telegramObj);
|
|
var telegramDict = JsonSerializer.Deserialize<Dictionary<string, object>>(telegramJson);
|
|
if (telegramDict?.TryGetValue("botToken", out var tokenObj) == true)
|
|
{
|
|
existingToken = tokenObj?.ToString();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
ViewData["ExistingToken"] = existingToken;
|
|
ViewData["HasExistingToken"] = !string.IsNullOrEmpty(existingToken);
|
|
|
|
return View(bot);
|
|
}
|
|
|
|
// POST: Admin/Bots/RepushConfig/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> RepushConfig(Guid id, string botToken, bool useExistingToken = false)
|
|
{
|
|
_logger.LogInformation("Re-pushing configuration to remote bot {BotId}", id);
|
|
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
{
|
|
TempData["Error"] = "Bot not found";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
if (!bot.IsRemote)
|
|
{
|
|
TempData["Error"] = "This is not a remote bot";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// If using existing token, retrieve it
|
|
if (useExistingToken)
|
|
{
|
|
if (bot.Settings.TryGetValue("telegram", out var telegramObj))
|
|
{
|
|
try
|
|
{
|
|
var telegramJson = JsonSerializer.Serialize(telegramObj);
|
|
var telegramDict = JsonSerializer.Deserialize<Dictionary<string, object>>(telegramJson);
|
|
if (telegramDict?.TryGetValue("botToken", out var tokenObj) == true)
|
|
{
|
|
botToken = tokenObj?.ToString() ?? string.Empty;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(botToken))
|
|
{
|
|
TempData["Error"] = "Bot token is required";
|
|
return RedirectToAction(nameof(RepushConfig), new { id });
|
|
}
|
|
|
|
// Validate the token
|
|
if (!await ValidateTelegramToken(botToken))
|
|
{
|
|
TempData["Error"] = "Invalid Telegram bot token";
|
|
return RedirectToAction(nameof(RepushConfig), new { id });
|
|
}
|
|
|
|
try
|
|
{
|
|
// First, re-initialize if needed
|
|
var probeResult = await _discoveryService.ProbeRemoteBotAsync(bot.RemoteAddress!, bot.RemotePort ?? 5000);
|
|
|
|
if (!probeResult.Success)
|
|
{
|
|
TempData["Error"] = $"Cannot reach remote bot: {probeResult.Message}";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// If bot is not initialized, initialize first
|
|
if (probeResult.ProbeResponse?.Status == DiscoveryStatus.AwaitingDiscovery ||
|
|
probeResult.ProbeResponse?.Status == DiscoveryStatus.Discovered)
|
|
{
|
|
var initResult = await _discoveryService.InitializeRemoteBotAsync(id, bot.RemoteAddress!, bot.RemotePort ?? 5000);
|
|
if (!initResult.Success)
|
|
{
|
|
TempData["Error"] = $"Failed to initialize remote bot: {initResult.Message}";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
}
|
|
|
|
// Push the configuration
|
|
var configResult = await _discoveryService.PushConfigurationAsync(id, botToken);
|
|
|
|
if (configResult.Success)
|
|
{
|
|
// Update bot settings with the token
|
|
var settings = bot.Settings ?? new Dictionary<string, object>();
|
|
settings["telegram"] = new { botToken = botToken };
|
|
await _botService.UpdateBotSettingsAsync(id, new UpdateBotSettingsDto { Settings = settings });
|
|
|
|
// Update discovery status
|
|
await UpdateBotRemoteInfoAsync(id, bot.RemoteAddress!, bot.RemotePort ?? 5000,
|
|
probeResult.ProbeResponse?.InstanceId, DiscoveryStatus.Configured);
|
|
|
|
TempData["Success"] = $"Configuration pushed successfully! Telegram: @{configResult.TelegramUsername}";
|
|
}
|
|
else
|
|
{
|
|
TempData["Error"] = $"Failed to push configuration: {configResult.Message}";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to re-push configuration to bot {BotId}", id);
|
|
TempData["Error"] = $"Failed to push configuration: {ex.Message}";
|
|
}
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// Helper method to update bot remote info
|
|
private async Task UpdateBotRemoteInfoAsync(Guid botId, string ipAddress, int port, string? instanceId, string status)
|
|
{
|
|
await _botService.UpdateRemoteInfoAsync(botId, ipAddress, port, instanceId, status);
|
|
}
|
|
|
|
// POST: Admin/Bots/StartBot/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> StartBot(Guid id)
|
|
{
|
|
_logger.LogInformation("Start bot requested for {BotId}", id);
|
|
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
{
|
|
TempData["Error"] = "Bot not found";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
if (!bot.IsRemote)
|
|
{
|
|
TempData["Error"] = "Bot control is only available for remote bots";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await _discoveryService.ControlBotAsync(id, "start");
|
|
|
|
if (result.Success)
|
|
{
|
|
TempData["Success"] = result.Message;
|
|
}
|
|
else
|
|
{
|
|
TempData["Error"] = result.Message;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to start bot {BotId}", id);
|
|
TempData["Error"] = $"Failed to start bot: {ex.Message}";
|
|
}
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// POST: Admin/Bots/StopBot/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> StopBot(Guid id)
|
|
{
|
|
_logger.LogInformation("Stop bot requested for {BotId}", id);
|
|
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
{
|
|
TempData["Error"] = "Bot not found";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
if (!bot.IsRemote)
|
|
{
|
|
TempData["Error"] = "Bot control is only available for remote bots";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await _discoveryService.ControlBotAsync(id, "stop");
|
|
|
|
if (result.Success)
|
|
{
|
|
TempData["Success"] = result.Message;
|
|
}
|
|
else
|
|
{
|
|
TempData["Error"] = result.Message;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to stop bot {BotId}", id);
|
|
TempData["Error"] = $"Failed to stop bot: {ex.Message}";
|
|
}
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
// POST: Admin/Bots/RestartBot/5
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> RestartBot(Guid id)
|
|
{
|
|
_logger.LogInformation("Restart bot requested for {BotId}", id);
|
|
|
|
var bot = await _botService.GetBotByIdAsync(id);
|
|
if (bot == null)
|
|
{
|
|
TempData["Error"] = "Bot not found";
|
|
return RedirectToAction(nameof(Index));
|
|
}
|
|
|
|
if (!bot.IsRemote)
|
|
{
|
|
TempData["Error"] = "Bot control is only available for remote bots";
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
try
|
|
{
|
|
var result = await _discoveryService.ControlBotAsync(id, "restart");
|
|
|
|
if (result.Success)
|
|
{
|
|
TempData["Success"] = result.Message;
|
|
}
|
|
else
|
|
{
|
|
TempData["Error"] = result.Message;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to restart bot {BotId}", id);
|
|
TempData["Error"] = $"Failed to restart bot: {ex.Message}";
|
|
}
|
|
|
|
return RedirectToAction(nameof(Details), new { id });
|
|
}
|
|
|
|
#endregion
|
|
} |