diff --git a/README.md b/README.md index 674f713..3a1e845 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,8 @@

- 📦 安装部署 | + 📚 文档站 | 🏪 API 账号购买 | - 🧯 防冻结指南 | 🖼️ 截图 | 💬 TG 频道 | 👥 站长交流群 diff --git a/src/TelegramPanel.Core/Models/TelegramAccountWasteJudge.cs b/src/TelegramPanel.Core/Models/TelegramAccountWasteJudge.cs index 34220b4..3383ca6 100644 --- a/src/TelegramPanel.Core/Models/TelegramAccountWasteJudge.cs +++ b/src/TelegramPanel.Core/Models/TelegramAccountWasteJudge.cs @@ -61,6 +61,21 @@ public static class TelegramAccountWasteJudge return true; } + // Session 冲突/撤销:实际使用中通常都需要重新登录生成新 session,否则无法稳定使用 + if (summary.Contains("Session 冲突", StringComparison.OrdinalIgnoreCase) + || summary.Contains("AUTH_KEY_DUPLICATED", StringComparison.OrdinalIgnoreCase)) + { + reason = "Session 冲突"; + return true; + } + + if (summary.Contains("Session 已被撤销", StringComparison.OrdinalIgnoreCase) + || summary.Contains("SESSION_REVOKED", StringComparison.OrdinalIgnoreCase)) + { + reason = "Session 已被撤销"; + return true; + } + if (summary.Contains("Session 无法读取", StringComparison.OrdinalIgnoreCase) || summary.Contains("Can't read session block", StringComparison.OrdinalIgnoreCase)) { diff --git a/src/TelegramPanel.Data/Repositories/AccountRepository.cs b/src/TelegramPanel.Data/Repositories/AccountRepository.cs index 7a12649..c8a797a 100644 --- a/src/TelegramPanel.Data/Repositories/AccountRepository.cs +++ b/src/TelegramPanel.Data/Repositories/AccountRepository.cs @@ -64,6 +64,10 @@ public class AccountRepository : Repository, IAccountRepository // Session 失效/损坏 || EF.Functions.Like(a.TelegramStatusSummary, "%Session 失效%") || EF.Functions.Like(a.TelegramStatusSummary, "%AUTH_KEY_UNREGISTERED%") + || EF.Functions.Like(a.TelegramStatusSummary, "%Session 冲突%") + || EF.Functions.Like(a.TelegramStatusSummary, "%AUTH_KEY_DUPLICATED%") + || EF.Functions.Like(a.TelegramStatusSummary, "%Session 已被撤销%") + || EF.Functions.Like(a.TelegramStatusSummary, "%SESSION_REVOKED%") || EF.Functions.Like(a.TelegramStatusSummary, "%Session 无法读取%") || EF.Functions.Like(a.TelegramStatusSummary, "%Can't read session block%") @@ -147,7 +151,7 @@ public class AccountRepository : Repository, IAccountRepository { return await _dbSet .Include(a => a.Category) - .Where(a => a.IsActive) + .Where(a => a.IsActive && (a.Category == null || !a.Category.ExcludeFromOperations)) .ToListAsync(); } diff --git a/src/TelegramPanel.Web/Components/Pages/Accounts.razor b/src/TelegramPanel.Web/Components/Pages/Accounts.razor index f1dabe1..e6d80c6 100644 --- a/src/TelegramPanel.Web/Components/Pages/Accounts.razor +++ b/src/TelegramPanel.Web/Components/Pages/Accounts.razor @@ -31,7 +31,6 @@ @bind-Value="onlyWaste" @bind-Value:after="OnFiltersChanged" Dense="true" - Disabled="@loading" Label="只看废号" Class="tp-input-full-xs" Style="min-width: 140px; flex: 0 1 160px;" /> @@ -64,6 +63,13 @@ Disabled="@(loading || selectedAccounts.Count == 0)" OnClick="CleanupSelectedWasteAccounts" Size="Size.Small"> 清理废号(已选) + @if (onlyWaste) + { + + 清理废号(筛选) + + } 清理所有废号 @@ -1132,6 +1138,133 @@ } } + private async Task CleanupFilteredWasteAccounts() + { + if (loading) + return; + + var categoryId = filterCategoryId <= 0 ? (int?)null : filterCategoryId; + var targets = (await AccountManagement.QueryAccountsAsync( + categoryId: categoryId, + search: searchString, + onlyWaste: true, + cancellationToken: _pageCts.Token)) + .Where(a => a.Id > 0) + .GroupBy(a => a.Id) + .Select(g => g.First()) + .ToList(); + + if (targets.Count == 0) + { + Snackbar.Add("当前筛选条件下没有可清理的废号(需要先执行一次状态检测)", Severity.Info); + return; + } + + loading = true; + try + { + bool? probeResult = await DialogService.ShowMessageBox( + "清理筛选废号", + $"将对筛选结果 {targets.Count} 个账号执行 Telegram 状态检测,并删除判定为废号的账号与 session 文件。\n\n是否进行深度探测(将对每个账号创建并删除一个测试频道,用于判断【创建频道接口是否被冻结】)?", + yesText: "深度检测并清理", + cancelText: "普通检测并清理"); + + var probe = probeResult == true; + + // 批量风控检查:深度探测是敏感操作 + if (probe && targets.Count > 0) + { + var batchCheck = RiskService.CheckBatchAccounts(targets); + if (batchCheck.HasRiskyAccounts) + { + 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 dialogResult = await dialog.Result; + + if (dialogResult.Canceled) + { + loading = false; + return; + } + + if (dialogResult.Data is TelegramPanel.Core.Services.RiskWarningAction action + && action == TelegramPanel.Core.Services.RiskWarningAction.ExcludeRisky) + { + targets = batchCheck.SafeAccounts.ToList(); + if (targets.Count == 0) + { + Snackbar.Add("排除风险账号后无剩余账号", Severity.Info); + loading = false; + return; + } + } + } + } + + var deleted = 0; + var skipped = 0; + var failures = new List(); + + foreach (var a in targets) + { + try + { + var status = await AccountTelegramTools.RefreshAccountStatusAsync( + a.Id, + probeCreateChannel: probe, + cancellationToken: _pageCts.Token); + telegramStatus[a.Id] = status; + + if (!TelegramAccountWasteJudge.TryGetWasteReason(status, out _)) + { + skipped++; + continue; + } + + await AccountManagement.PurgeAccountAsync(a.Id, _pageCts.Token); + deleted++; + } + catch (Exception ex) + { + failures.Add($"{a.DisplayPhone}:{ex.Message}"); + } + + StateHasChanged(); + } + + selectedAccounts.Clear(); + await ReloadTable(); + + var summary = $"清理完成:删除 {deleted},跳过 {skipped},失败 {failures.Count}(共 {targets.Count})"; + Snackbar.Add(summary, failures.Count == 0 ? Severity.Success : Severity.Warning); + + if (failures.Count > 0) + { + var details = string.Join(Environment.NewLine, failures.Take(80)); + if (failures.Count > 80) + details += Environment.NewLine + $"... 仅展示前 80 条(共 {failures.Count} 条)"; + + await DialogService.ShowMessageBox("失败明细(前 80 条)", details, "关闭"); + } + } + catch (Exception ex) + { + Snackbar.Add($"批量清理失败:{ex.Message}", Severity.Error); + } + finally + { + loading = false; + } + } + private async Task CleanupAllWasteAccounts() { if (loading)