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)