机器人管理:支持编辑 Bot 并更换 Token
This commit is contained in:
1 parent
0e94efab9f
commit
a510f3c136
3 files changed
+214
No files matched your search
@@ -73,6 +73,55 @@ public class BotManagementService
|
||||
await _botRepository.UpdateAsync(bot);
|
||||
}
|
||||
|
||||
public async Task UpdateBotProfileAsync(int botId, string name, string? username, string? newToken)
|
||||
{
|
||||
name = (name ?? string.Empty).Trim();
|
||||
username = string.IsNullOrWhiteSpace(username) ? null : username.Trim().TrimStart('@');
|
||||
newToken = string.IsNullOrWhiteSpace(newToken) ? null : newToken.Trim();
|
||||
|
||||
if (botId <= 0)
|
||||
throw new ArgumentException("BotId 无效", nameof(botId));
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("机器人名称不能为空", nameof(name));
|
||||
|
||||
var bot = await _botRepository.GetByIdAsync(botId)
|
||||
?? throw new InvalidOperationException($"机器人不存在:{botId}");
|
||||
|
||||
if (!string.Equals(bot.Name, name, StringComparison.Ordinal))
|
||||
{
|
||||
var existing = await _botRepository.GetByNameAsync(name);
|
||||
if (existing != null && existing.Id != botId)
|
||||
throw new InvalidOperationException("机器人名称已存在");
|
||||
}
|
||||
|
||||
bot.Name = name;
|
||||
bot.Username = username;
|
||||
if (newToken != null)
|
||||
bot.Token = newToken;
|
||||
|
||||
try
|
||||
{
|
||||
await _botRepository.UpdateAsync(bot);
|
||||
}
|
||||
catch (DbUpdateException ex)
|
||||
{
|
||||
var msg = GetInnermostMessage(ex);
|
||||
if (msg.Contains("UNIQUE constraint failed: Bots.Name", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("机器人名称已存在");
|
||||
|
||||
if (msg.Contains("database is locked", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("database is busy", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("SQLite Error 5", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("数据库正被占用(SQLite database is locked),请稍后重试;若云端部署了多个实例共享同一个 sqlite 文件,请改为单实例或换用 MySQL/PostgreSQL。");
|
||||
|
||||
if (msg.Contains("no such table: Bots", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("no such column", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException($"数据库结构过旧或未完成迁移,请重启主程序以自动迁移数据库。详情:{msg}");
|
||||
|
||||
throw new InvalidOperationException($"更新机器人失败:{msg}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetBotActiveStatusAsync(int botId, bool isActive)
|
||||
{
|
||||
var bot = await _botRepository.GetByIdAsync(botId);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
@namespace TelegramPanel.Web.Components.Dialogs
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.subtitle1">编辑机器人</MudText>
|
||||
|
||||
<MudTextField T="string" @bind-Value="name" Label="名称" Variant="Variant.Outlined" Required="true" />
|
||||
<MudTextField T="string" @bind-Value="username" Label="用户名(可选)" Variant="Variant.Outlined" Placeholder="bot_username" />
|
||||
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||||
不展示当前 Token;如需更换请输入新 Token(留空则不修改)。
|
||||
</MudText>
|
||||
<MudTextField T="string" @bind-Value="newToken" Label="新的 Bot Token(可选)" Variant="Variant.Outlined"
|
||||
InputType="@(showToken ? InputType.Text : InputType.Password)"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@(showToken ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility)"
|
||||
OnAdornmentClick="ToggleTokenVisibility" />
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Cancel">取消</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@string.IsNullOrWhiteSpace(name)" OnClick="Confirm">
|
||||
保存
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
|
||||
|
||||
[Parameter] public Bot Bot { get; set; } = default!;
|
||||
|
||||
private string name = "";
|
||||
private string? username;
|
||||
private string? newToken;
|
||||
private bool showToken;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
name = (Bot?.Name ?? "").Trim();
|
||||
username = string.IsNullOrWhiteSpace(Bot?.Username) ? null : Bot!.Username!.Trim().TrimStart('@');
|
||||
newToken = "";
|
||||
showToken = false;
|
||||
}
|
||||
|
||||
private void ToggleTokenVisibility()
|
||||
{
|
||||
showToken = !showToken;
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
MudDialog.Cancel();
|
||||
}
|
||||
|
||||
private void Confirm()
|
||||
{
|
||||
var model = new EditBotModel(
|
||||
Name: name.Trim(),
|
||||
Username: string.IsNullOrWhiteSpace(username) ? null : username!.Trim().TrimStart('@'),
|
||||
NewToken: string.IsNullOrWhiteSpace(newToken) ? null : newToken!.Trim());
|
||||
|
||||
MudDialog.Close(DialogResult.Ok(model));
|
||||
}
|
||||
|
||||
public sealed record EditBotModel(string Name, string? Username, string? NewToken);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
@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>
|
||||
|
||||
@@ -63,6 +66,10 @@
|
||||
</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}"))">
|
||||
频道列表
|
||||
@@ -196,4 +203,91 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user