From 1dfb80cdd2bfcfa446cd37eb55417dee55b54409 Mon Sep 17 00:00:00 2001 From: meoacgx Date: Tue, 3 Mar 2026 04:38:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(accounts):=20=E6=89=B9=E9=87=8F=E5=8A=A0?= =?UTF-8?q?=E9=80=80=E7=BE=A4=E6=95=B4=E5=90=88=20Bot=20=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=90=AF=E5=81=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 批量加群/订阅流程可选批量启用 Bot,批量退群/退订可选批量停用 Bot - 支持 Bot 多选、推荐选择、全选/清空,进度统计合并账号与 Bot 操作 - 批量入口文案改为 批量加群/订阅/启用Bot 与 批量退群/退订/停用Bot --- .../Dialogs/BatchChatMembershipDialog.razor | 286 ++++++++++++++---- .../Components/Pages/AccountImport.razor | 8 +- .../Components/Pages/Accounts.razor | 8 +- 3 files changed, 243 insertions(+), 59 deletions(-) diff --git a/src/TelegramPanel.Web/Components/Dialogs/BatchChatMembershipDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/BatchChatMembershipDialog.razor index 74576e0..d5ccec2 100644 --- a/src/TelegramPanel.Web/Components/Dialogs/BatchChatMembershipDialog.razor +++ b/src/TelegramPanel.Web/Components/Dialogs/BatchChatMembershipDialog.razor @@ -1,5 +1,6 @@ @inject AccountManagementService AccountManagement @inject AccountTelegramToolsService AccountTelegramTools +@inject BotManagementService BotManagement @inject TelegramPanel.Core.Services.AccountRiskService RiskService @inject ISnackbar Snackbar @inject IDialogService DialogService @@ -11,11 +12,11 @@ @if (IsJoin) { - 将使用所选账号订阅频道/加入群组。支持输入多个链接/用户名,换行分隔。 + 将使用所选账号订阅频道/加入群组,并可选批量启用 Bot。 } else { - 将使用所选账号退出群组/取消订阅频道。支持输入多个链接/用户名,换行分隔。 + 将使用所选账号退出群组/取消订阅频道,并可选批量停用 Bot。 } 支持:https://t.me/xxx、t.me/+hash、@@username、username、tg://join?invite=hash @@ -25,11 +26,65 @@ 选中账号:@accounts.Count 个 + HelperText="每行一个链接或用户名;如仅执行 Bot 启停可留空。" /> + + + Bot 操作(可选) + + @(IsJoin ? "批量启用" : "批量停用") + + + + @if (bots.Count == 0) + { + 暂无 Bot,已自动跳过 Bot 批量操作。 + } + else + { + + + @if (enableBotOperation) + { + + 已选 Bot:@selectedBotIds.Count / @bots.Count + 选中推荐 + 全选 + 清空 + + + + + @foreach (var bot in orderedBots) + { + + + + } + + + + @if (IsJoin) + { + + } + } + } + @if (running) { @@ -60,7 +115,12 @@ [Parameter] public string Operation { get; set; } = "join"; // join|leave private List accounts = new(); + private List bots = new(); + private List orderedBots = new(); private string linksText = ""; + private string botStartParameters = ""; + private bool enableBotOperation; + private HashSet selectedBotIds = new(); private int delayMs = 2000; private bool running; private int total; @@ -76,11 +136,25 @@ protected override async Task OnInitializedAsync() { var ids = (AccountIds ?? new List()).Where(x => x > 0).Distinct().ToList(); - if (ids.Count == 0) - return; - var all = (await AccountManagement.GetAllAccountsAsync()).ToList(); - accounts = all.Where(a => ids.Contains(a.Id)).ToList(); + var loadAccountsTask = AccountManagement.GetAllAccountsAsync(); + var loadBotsTask = BotManagement.GetAllBotsAsync(); + + await Task.WhenAll(loadAccountsTask, loadBotsTask); + + if (ids.Count > 0) + { + var all = loadAccountsTask.Result.ToList(); + accounts = all.Where(a => ids.Contains(a.Id)).ToList(); + } + + bots = loadBotsTask.Result.ToList(); + orderedBots = bots + .OrderBy(b => b.Name, StringComparer.OrdinalIgnoreCase) + .ThenBy(b => b.Id) + .ToList(); + + SelectRecommendedBots(); } private async Task Submit() @@ -89,52 +163,82 @@ return; var links = ParseLines(linksText); - if (links.Count == 0) + var targetAccounts = accounts.ToList(); + var runMembershipOps = links.Count > 0; + var selectedBots = GetSelectedBots(); + var runBotOps = enableBotOperation && selectedBots.Count > 0; + + if (enableBotOperation && bots.Count > 0 && selectedBots.Count == 0) { - Snackbar.Add("请至少输入一个频道/群链接", Severity.Warning); + Snackbar.Add("已启用 Bot 批量操作,请至少选择 1 个 Bot", Severity.Warning); return; } - // 风控检查:加群/退群 都是敏感操作 - var batchCheck = RiskService.CheckBatchAccounts(accounts); - if (batchCheck.HasRiskyAccounts) + if (!runMembershipOps && !runBotOps) { - var parameters = new DialogParameters - { - ["Message"] = $"检测到 {batchCheck.RiskyCount} 个风险账号", - ["DetailedMessage"] = batchCheck.GetRiskySummary(), - ["ShowRecommendations"] = true, - ["ShowExcludeOption"] = true, - ["RiskyAccounts"] = batchCheck.RiskyAccounts.ToList() - }; - var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large }; - var dialog = DialogService.Show("批量操作风控警告", parameters, options); - var r = await dialog.Result; - if (r.Canceled) - return; + Snackbar.Add("请至少输入一个频道/群链接,或选择 Bot 批量操作", Severity.Warning); + return; + } - if (r.Data is TelegramPanel.Core.Services.RiskWarningAction action - && action == TelegramPanel.Core.Services.RiskWarningAction.ExcludeRisky) + // 风控检查:加群/退群是敏感操作(仅在执行账号加入/退出时检查) + if (runMembershipOps) + { + var batchCheck = RiskService.CheckBatchAccounts(targetAccounts); + if (batchCheck.HasRiskyAccounts) { - accounts = batchCheck.SafeAccounts.ToList(); - if (accounts.Count == 0) + var parameters = new DialogParameters { - Snackbar.Add("排除风险账号后无剩余账号", Severity.Info); + ["Message"] = $"检测到 {batchCheck.RiskyCount} 个风险账号", + ["DetailedMessage"] = batchCheck.GetRiskySummary(), + ["ShowRecommendations"] = true, + ["ShowExcludeOption"] = true, + ["RiskyAccounts"] = batchCheck.RiskyAccounts.ToList() + }; + var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large }; + var dialog = DialogService.Show("批量操作风控警告", parameters, options); + var r = await dialog.Result; + if (r is null || r.Canceled) return; + + if (r.Data is TelegramPanel.Core.Services.RiskWarningAction action + && action == TelegramPanel.Core.Services.RiskWarningAction.ExcludeRisky) + { + targetAccounts = batchCheck.SafeAccounts.ToList(); + if (targetAccounts.Count == 0) + { + Snackbar.Add("排除风险账号后无剩余账号", Severity.Info); + return; + } } } } + var membershipTotal = runMembershipOps ? targetAccounts.Count * links.Count : 0; + var botTotal = runBotOps ? selectedBots.Count : 0; + var confirmLines = new List(); + + if (membershipTotal > 0) + confirmLines.Add($"{(IsJoin ? "加群/订阅" : "退群/退订")}:{targetAccounts.Count} 个账号 × {links.Count} 个目标(共 {membershipTotal} 次操作)"); + + if (botTotal > 0) + confirmLines.Add($"{(IsJoin ? "启用" : "停用")} Bot:{selectedBots.Count} 个"); + + if (IsJoin && runBotOps && !string.IsNullOrWhiteSpace(botStartParameters)) + confirmLines.Add($"启动参数:{botStartParameters.Trim()}(当前版本暂不生效)"); + bool? confirm = await DialogService.ShowMessageBox( "确认执行", - $"{(IsJoin ? "加群/订阅" : "退群/退订")}:{accounts.Count} 个账号 × {links.Count} 个目标(共 {accounts.Count * links.Count} 次操作)。是否继续?", + $"{string.Join(Environment.NewLine, confirmLines)}{Environment.NewLine}{Environment.NewLine}是否继续?", yesText: "继续", cancelText: "取消"); if (confirm != true) return; + if (IsJoin && runBotOps && !string.IsNullOrWhiteSpace(botStartParameters)) + Snackbar.Add("已填写启动参数,但当前版本仅执行 Bot 启用,参数暂不生效。", Severity.Info); + running = true; - total = accounts.Count * links.Count; + total = membershipTotal + botTotal; done = 0; failed = 0; currentHint = null; @@ -144,35 +248,65 @@ try { - foreach (var a in accounts) + if (runMembershipOps) { - foreach (var link in links) + foreach (var a in targetAccounts) + { + foreach (var link in links) + { + _cts.Token.ThrowIfCancellationRequested(); + + currentHint = $"{a.DisplayPhone} => {link}"; + StateHasChanged(); + + (bool Success, string? Error, string? Title) result; + if (IsJoin) + result = await AccountTelegramTools.JoinChatOrChannelAsync(a.Id, link, _cts.Token); + else + result = await AccountTelegramTools.LeaveChatOrChannelAsync(a.Id, link, _cts.Token); + + done++; + if (!result.Success) + { + failed++; + failures.Add($"{a.DisplayPhone} => {link}:{result.Error}"); + } + + StateHasChanged(); + + var wait = delayMs; + if (wait < 0) wait = 0; + if (wait > 10000) wait = 10000; + var jitter = _rand.Next(0, 350); + await Task.Delay(TimeSpan.FromMilliseconds(wait + jitter), _cts.Token); + } + } + } + + if (runBotOps) + { + foreach (var bot in selectedBots) { _cts.Token.ThrowIfCancellationRequested(); - currentHint = $"{a.DisplayPhone} => {link}"; + currentHint = $"{(IsJoin ? "启用" : "停用")} Bot => {bot.Name}"; StateHasChanged(); - (bool Success, string? Error, string? Title) result; - if (IsJoin) - result = await AccountTelegramTools.JoinChatOrChannelAsync(a.Id, link, _cts.Token); - else - result = await AccountTelegramTools.LeaveChatOrChannelAsync(a.Id, link, _cts.Token); - - done++; - if (!result.Success) + try + { + await BotManagement.SetBotActiveStatusAsync(bot.Id, IsJoin); + } + catch (Exception ex) { failed++; - failures.Add($"{a.DisplayPhone} => {link}:{result.Error}"); + failures.Add($"{(IsJoin ? "启用" : "停用")} Bot {bot.Name} 失败:{ex.Message}"); + } + finally + { + done++; } StateHasChanged(); - - var wait = delayMs; - if (wait < 0) wait = 0; - if (wait > 10000) wait = 10000; - var jitter = _rand.Next(0, 350); - await Task.Delay(TimeSpan.FromMilliseconds(wait + jitter), _cts.Token); } } @@ -216,6 +350,57 @@ MudDialog.Close(); } + private List GetSelectedBots() + { + if (selectedBotIds.Count == 0 || bots.Count == 0) + return new List(); + + return bots + .Where(b => selectedBotIds.Contains(b.Id)) + .OrderBy(b => b.Name, StringComparer.OrdinalIgnoreCase) + .ThenBy(b => b.Id) + .ToList(); + } + + private void SelectRecommendedBots() + { + if (bots.Count == 0) + { + selectedBotIds.Clear(); + return; + } + + selectedBotIds = bots + .Where(b => IsJoin ? !b.IsActive : b.IsActive) + .Select(b => b.Id) + .ToHashSet(); + } + + private void SelectAllBots() + { + selectedBotIds = bots.Select(b => b.Id).ToHashSet(); + } + + private void ClearSelectedBots() + { + selectedBotIds.Clear(); + } + + private void SetBotSelection(int botId, bool selected) + { + if (selected) + selectedBotIds.Add(botId); + else + selectedBotIds.Remove(botId); + } + + private static string BuildBotLabel(Bot bot) + { + var username = string.IsNullOrWhiteSpace(bot.Username) ? "-" : $"@{bot.Username}"; + var status = bot.IsActive ? "启用" : "停用"; + return $"{bot.Name}({username},当前{status})"; + } + private static List ParseLines(string? text) { if (string.IsNullOrWhiteSpace(text)) @@ -229,4 +414,3 @@ .ToList(); } } - diff --git a/src/TelegramPanel.Web/Components/Pages/AccountImport.razor b/src/TelegramPanel.Web/Components/Pages/AccountImport.razor index 570d592..ad04068 100644 --- a/src/TelegramPanel.Web/Components/Pages/AccountImport.razor +++ b/src/TelegramPanel.Web/Components/Pages/AccountImport.razor @@ -207,8 +207,8 @@ - 批量加群/订阅(已选) - 批量退群/退订(已选) + 批量加群/订阅/启用Bot(已选) + 批量退群/退订/停用Bot(已选) 修改二级密码(已选) 批量换绑邮箱(找回+登录)(Cloud Mail)(已选) 批量修改分类(已选) @@ -746,7 +746,7 @@ ["Operation"] = "join" }; var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true }; - DialogService.Show("批量加群/订阅", parameters, options); + DialogService.Show("批量加群/订阅/启用Bot", parameters, options); await Task.CompletedTask; } @@ -768,7 +768,7 @@ ["Operation"] = "leave" }; var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true }; - DialogService.Show("批量退群/退订", parameters, options); + DialogService.Show("批量退群/退订/停用Bot", parameters, options); await Task.CompletedTask; } diff --git a/src/TelegramPanel.Web/Components/Pages/Accounts.razor b/src/TelegramPanel.Web/Components/Pages/Accounts.razor index fbbb826..293b8c2 100644 --- a/src/TelegramPanel.Web/Components/Pages/Accounts.razor +++ b/src/TelegramPanel.Web/Components/Pages/Accounts.razor @@ -77,8 +77,8 @@ - 批量加群/订阅(已选) - 批量退群/退订(已选) + 批量加群/订阅/启用Bot(已选) + 批量退群/退订/停用Bot(已选) 修改二级密码(已选) 批量换绑邮箱(找回+登录)(Cloud Mail)(已选) 批量踢出所有其他设备(已选) @@ -560,7 +560,7 @@ ["Operation"] = "join" }; var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true }; - DialogService.Show("批量加群/订阅", parameters, options); + DialogService.Show("批量加群/订阅/启用Bot", parameters, options); await Task.CompletedTask; } @@ -582,7 +582,7 @@ ["Operation"] = "leave" }; var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true }; - DialogService.Show("批量退群/退订", parameters, options); + DialogService.Show("批量退群/退订/停用Bot", parameters, options); await Task.CompletedTask; }