Files
Telegram-Panel/src/TelegramPanel.Web/Components/Pages/Bots.razor
T

294 lines
10 KiB
Plaintext

@page "/bots"
@inject BotManagementService BotManagement
@inject TelegramBotApiClient BotApi
@inject IConfiguration Configuration
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@inject NavigationManager Navigation
@using TelegramPanel.Data.Entities
@using TelegramPanel.Web.Components.Dialogs
<PageTitle>机器人管理 - Telegram Panel</PageTitle>
<MudText Typo="Typo.h4" Class="mb-4">机器人管理</MudText>
<MudGrid>
<MudItem xs="12" md="6">
<MudCard>
<MudCardContent>
<MudText Typo="Typo.h6" Class="mb-2">新增机器人</MudText>
<MudTextField @bind-Value="newName" Label="名称" Variant="Variant.Outlined" Required="true" />
<MudTextField @bind-Value="newToken" Label="Bot Token" Variant="Variant.Outlined" Required="true" Class="mt-3" />
<MudTextField @bind-Value="newUsername" Label="用户名(可选)" Variant="Variant.Outlined" Placeholder="bot_username" Class="mt-3" />
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@loading" Class="mt-3"
OnClick="@(async (MouseEventArgs _) => await CreateBotAsync())">
添加
</MudButton>
</MudCardContent>
</MudCard>
</MudItem>
<MudItem xs="12" md="12">
<MudCard Class="mt-4">
<MudCardContent>
<MudTable Items="@bots" Hover="true" Breakpoint="Breakpoint.Sm" Loading="@loading">
<ToolBarContent>
<MudText Typo="Typo.h6">机器人 (@bots.Count)</MudText>
<MudSpacer />
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
Disabled="@loading" OnClick="LoadBots">
刷新
</MudButton>
</ToolBarContent>
<HeaderContent>
<MudTh>名称</MudTh>
<MudTh>用户名</MudTh>
<MudTh>可管理聊天数</MudTh>
<MudTh>最后同步</MudTh>
<MudTh>状态</MudTh>
<MudTh>操作</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="名称">@context.Name</MudTd>
<MudTd DataLabel="用户名">@(!string.IsNullOrWhiteSpace(context.Username) ? $"@{context.Username}" : "-")</MudTd>
<MudTd DataLabel="可管理聊天数">@context.ChannelMembers.Count</MudTd>
<MudTd DataLabel="最后同步"><PanelTime Value="context.LastSyncAt" /></MudTd>
<MudTd DataLabel="状态">
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
<MudSwitch Value="@context.IsActive"
ValueChanged="@(async (bool v) => await SetBotActiveAsync(context, v))"
Disabled="@(loading || togglingBotIds.Contains(context.Id))"
Color="Color.Primary" />
<MudChip T="string" Size="Size.Small" Color="@(context.IsActive ? Color.Success : Color.Default)">
@(context.IsActive ? "启用" : "停用")
</MudChip>
</MudStack>
</MudTd>
<MudTd DataLabel="操作">
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small">
<MudMenuItem Icon="@Icons.Material.Filled.Edit"
OnClick="@(async () => await EditBotAsync(context))">
编辑
</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.List"
OnClick="@(() => Navigation.NavigateTo($"/bots/channels?botId={context.Id}"))">
频道列表
</MudMenuItem>
<MudDivider />
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error"
OnClick="@(async () => await DeleteBotAsync(context))">
删除
</MudMenuItem>
</MudMenu>
</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText>暂无机器人</MudText>
</NoRecordsContent>
</MudTable>
</MudCardContent>
</MudCard>
</MudItem>
</MudGrid>
@code {
private bool loading = true;
private List<Bot> bots = new();
private readonly HashSet<int> togglingBotIds = new();
private string newName = "";
private string newToken = "";
private string newUsername = "";
protected override async Task OnInitializedAsync()
{
await LoadBots();
}
private async Task LoadBots()
{
loading = true;
try
{
var list = await BotManagement.GetAllBotsAsync();
bots = list.ToList();
}
catch (Exception ex)
{
Snackbar.Add($"加载失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task CreateBotAsync()
{
if (loading) return;
if (string.IsNullOrWhiteSpace(newName) || string.IsNullOrWhiteSpace(newToken))
{
Snackbar.Add("请填写名称和 Token", Severity.Warning);
return;
}
loading = true;
try
{
await BotManagement.CreateBotAsync(newName, newToken, newUsername);
newName = "";
newToken = "";
newUsername = "";
await LoadBots();
Snackbar.Add("机器人已创建", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"创建失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task DeleteBotAsync(Bot bot)
{
bool? result = await DialogService.ShowMessageBox(
"确认删除",
$"确定要删除机器人 {bot.Name} 吗?该机器人加入的频道关联也会一并删除。",
yesText: "删除", cancelText: "取消");
if (result != true)
return;
loading = true;
try
{
await BotManagement.DeleteBotAsync(bot.Id);
await LoadBots();
Snackbar.Add("删除成功", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"删除失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task SetBotActiveAsync(Bot bot, bool isActive)
{
if (loading)
return;
if (togglingBotIds.Contains(bot.Id))
return;
togglingBotIds.Add(bot.Id);
try
{
await BotManagement.SetBotActiveStatusAsync(bot.Id, isActive);
bot.IsActive = isActive;
Snackbar.Add(isActive ? "已启用 Bot" : "已停用 Bot", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"更新失败:{ex.Message}", Severity.Error);
}
finally
{
togglingBotIds.Remove(bot.Id);
}
}
private async Task EditBotAsync(Bot bot)
{
if (loading)
return;
var parameters = new DialogParameters
{
["Bot"] = bot
};
var options = new DialogOptions
{
CloseButton = true,
MaxWidth = MaxWidth.Small,
FullWidth = true
};
var dialog = DialogService.Show<EditBotDialog>("编辑机器人", parameters, options)!;
var result = await dialog.Result;
if (result is null || result.Canceled)
return;
if (result.Data is not EditBotDialog.EditBotModel model)
return;
loading = true;
try
{
await BotManagement.UpdateBotProfileAsync(
botId: bot.Id,
name: model.Name,
username: model.Username,
newToken: model.NewToken);
if (!string.IsNullOrWhiteSpace(model.NewToken))
await TryRegisterWebhookForBotAsync(model.NewToken);
await LoadBots();
Snackbar.Add("已保存", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"保存失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task TryRegisterWebhookForBotAsync(string token)
{
var webhookEnabled = string.Equals(
Configuration["Telegram:WebhookEnabled"]?.Trim(),
"true",
StringComparison.OrdinalIgnoreCase);
if (!webhookEnabled)
return;
var baseUrl = (Configuration["Telegram:WebhookBaseUrl"] ?? "").Trim();
var secretToken = (Configuration["Telegram:WebhookSecretToken"] ?? "").Trim();
if (string.IsNullOrWhiteSpace(baseUrl) || string.IsNullOrWhiteSpace(secretToken))
{
Snackbar.Add("已更换 Token,但 WebhookBaseUrl/WebhookSecretToken 未配置,无法自动重新注册 Webhook。", Severity.Warning);
return;
}
try
{
var webhookUrl = $"{baseUrl.TrimEnd('/')}/api/bot/webhook/{Uri.EscapeDataString(token.Trim())}";
await BotApi.SetWebhookAsync(
token: token.Trim(),
url: webhookUrl,
secretToken: secretToken,
allowedUpdates: BotUpdateHub.AllowedUpdatesJson,
cancellationToken: CancellationToken.None);
Snackbar.Add("Webhook 已重新注册", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"Token 已更换,但重新注册 Webhook 失败:{ex.Message}", Severity.Warning);
}
}
}