diff --git a/Directory.Build.props b/Directory.Build.props index 8a5271f..c2b7135 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,10 +5,10 @@ - 1.2.4 - 1.2.4.0 - 1.2.4.0 - 1.2.4 + 1.2.5 + 1.2.5.0 + 1.2.5.0 + 1.2.5 Telegram Panel Contributors diff --git a/src/TelegramPanel.Core/Services/AccountManagementService.cs b/src/TelegramPanel.Core/Services/AccountManagementService.cs index cd9758e..5539dac 100644 --- a/src/TelegramPanel.Core/Services/AccountManagementService.cs +++ b/src/TelegramPanel.Core/Services/AccountManagementService.cs @@ -63,9 +63,10 @@ public class AccountManagementService string? search, int pageIndex, int pageSize, + bool onlyWaste = false, CancellationToken cancellationToken = default) { - var (items, total) = await _accountRepository.QueryPagedAsync(categoryId, search, pageIndex, pageSize, cancellationToken); + var (items, total) = await _accountRepository.QueryPagedAsync(categoryId, search, pageIndex, pageSize, onlyWaste, cancellationToken); foreach (var a in items) FormatPhoneForDisplay(a); return (items, total); @@ -74,9 +75,10 @@ public class AccountManagementService public async Task> QueryAccountsAsync( int? categoryId, string? search, + bool onlyWaste = false, CancellationToken cancellationToken = default) { - var list = await _accountRepository.QueryAsync(categoryId, search, cancellationToken); + var list = await _accountRepository.QueryAsync(categoryId, search, onlyWaste, cancellationToken); foreach (var a in list) FormatPhoneForDisplay(a); return list; diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs index ad76e23..441aca7 100644 --- a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs @@ -1047,6 +1047,22 @@ public class AccountTelegramToolsService { var msg = ex.Message ?? string.Empty; + if (msg.Contains("EMAIL_HASH_EXPIRED", StringComparison.OrdinalIgnoreCase)) + return ( + "邮箱验证码已过期(EMAIL_HASH_EXPIRED)", + "请点击“重发验证码”,并使用最新邮件中的验证码。" + Environment.NewLine + msg); + + if (msg.Contains("EMAIL_UNCONFIRMED", StringComparison.OrdinalIgnoreCase)) + return ( + "邮箱未确认(EMAIL_UNCONFIRMED)", + "请在面板输入邮箱收到的验证码进行确认;如提示过期请重发并使用最新验证码。" + Environment.NewLine + msg); + + if (msg.Contains("EMAIL_INVALID", StringComparison.OrdinalIgnoreCase)) + return ("邮箱无效(EMAIL_INVALID)", msg); + + if (msg.Contains("EMAIL_NOT_ALLOWED", StringComparison.OrdinalIgnoreCase)) + return ("邮箱不允许使用(EMAIL_NOT_ALLOWED)", msg); + if (msg.Contains("FROZEN_METHOD_INVALID", StringComparison.OrdinalIgnoreCase)) return ("账号被冻结(FROZEN_METHOD_INVALID)", "Telegram 提示该账号/ApiId 的某些接口被冻结(常见为创建频道接口)。" + Environment.NewLine + msg); diff --git a/src/TelegramPanel.Data/Repositories/AccountRepository.cs b/src/TelegramPanel.Data/Repositories/AccountRepository.cs index b157279..7a12649 100644 --- a/src/TelegramPanel.Data/Repositories/AccountRepository.cs +++ b/src/TelegramPanel.Data/Repositories/AccountRepository.cs @@ -12,7 +12,7 @@ public class AccountRepository : Repository, IAccountRepository { } - private IQueryable BuildQuery(int? categoryId, string? search) + private IQueryable BuildQuery(int? categoryId, string? search, bool onlyWaste) { var query = _dbSet .AsNoTracking() @@ -48,6 +48,44 @@ public class AccountRepository : Repository, IAccountRepository } } + if (onlyWaste) + { + // 仅废号:基于“上次 Telegram 状态检测摘要(Summary)”做筛选。 + // 注意:未检测的账号(Summary 为空)不会被包含。 + query = query.Where(a => + a.TelegramStatusSummary != null + && ( + // 账号封禁/停用 + EF.Functions.Like(a.TelegramStatusSummary, "%账号被封禁%") + || EF.Functions.Like(a.TelegramStatusSummary, "%被封禁/停用%") + || EF.Functions.Like(a.TelegramStatusSummary, "%USER_DEACTIVATED%") + || EF.Functions.Like(a.TelegramStatusSummary, "%PHONE_NUMBER_BANNED%") + + // 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, "%Can't read session block%") + + // 受限/冻结/未登录 + || EF.Functions.Like(a.TelegramStatusSummary, "%账号受限%") + || EF.Functions.Like(a.TelegramStatusSummary, "%Restricted%") + || EF.Functions.Like(a.TelegramStatusSummary, "%账号被冻结%") + || EF.Functions.Like(a.TelegramStatusSummary, "%FROZEN_METHOD_INVALID%") + || EF.Functions.Like(a.TelegramStatusSummary, "%需要两步验证密码%") + || EF.Functions.Like(a.TelegramStatusSummary, "%SESSION_PASSWORD_NEEDED%") + + // 注销/删除 + || EF.Functions.Like(a.TelegramStatusSummary, "%账号已注销%") + || EF.Functions.Like(a.TelegramStatusSummary, "%已注销/被删除%") + || EF.Functions.Like(a.TelegramStatusSummary, "%被删除%") + + // 连接/探测失败(按“废号”处理) + || EF.Functions.Like(a.TelegramStatusSummary, "%连接失败%") + || EF.Functions.Like(a.TelegramStatusSummary, "%创建频道探测失败%") + )); + } + return query.OrderByDescending(a => a.Id); } @@ -118,13 +156,14 @@ public class AccountRepository : Repository, IAccountRepository string? search, int pageIndex, int pageSize, + bool onlyWaste, CancellationToken cancellationToken = default) { if (pageIndex < 0) pageIndex = 0; if (pageSize <= 0) pageSize = 20; if (pageSize > 500) pageSize = 500; - var query = BuildQuery(categoryId, search); + var query = BuildQuery(categoryId, search, onlyWaste); var total = await query.CountAsync(cancellationToken); var items = await query .Skip(pageIndex * pageSize) @@ -137,9 +176,10 @@ public class AccountRepository : Repository, IAccountRepository public async Task> QueryAsync( int? categoryId, string? search, + bool onlyWaste, CancellationToken cancellationToken = default) { - var query = BuildQuery(categoryId, search); + var query = BuildQuery(categoryId, search, onlyWaste); return await query.ToListAsync(cancellationToken); } } diff --git a/src/TelegramPanel.Data/Repositories/IAccountRepository.cs b/src/TelegramPanel.Data/Repositories/IAccountRepository.cs index 400ef79..2637338 100644 --- a/src/TelegramPanel.Data/Repositories/IAccountRepository.cs +++ b/src/TelegramPanel.Data/Repositories/IAccountRepository.cs @@ -17,10 +17,12 @@ public interface IAccountRepository : IRepository string? search, int pageIndex, int pageSize, + bool onlyWaste = false, CancellationToken cancellationToken = default); Task> QueryAsync( int? categoryId, string? search, + bool onlyWaste = false, CancellationToken cancellationToken = default); } diff --git a/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor index 0dc8123..33048ef 100644 --- a/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor +++ b/src/TelegramPanel.Web/Components/Dialogs/ChangeTwoFactorRecoveryEmailDialog.razor @@ -43,18 +43,40 @@ 该账号未开启两步验证,无法绑定找回邮箱。请先设置二级密码。 } - else if (step == Step.SetEmail) + else { + 发送验证码 + - } - else - { + + + + @if (running) + { + + 发送中... + } + else + { + 发送验证码 + } + + + 提示:即使“发送/重发”提示失败,但你实际收到了邮件验证码,也可以直接在下方输入验证码确认。 + + + + + + 输入验证码确认 + + HelperText="请到邮箱中查看验证码(注意垃圾箱/延迟)。如提示过期,请先重发并使用最新验证码。" + Required="true" /> @@ -63,6 +85,17 @@ 取消验证 + + @if (running) + { + + 确认中... + } + else + { + 确认绑定 + } + } } @@ -77,37 +110,7 @@ 去设置二级密码 } - else if (loaded && account != null && hasTwoFactorPassword) - { - @if (step == Step.SetEmail) - { - - @if (running) - { - - 发送中... - } - else - { - 发送验证码 - } - - } - else - { - - @if (running) - { - - 确认中... - } - else - { - 确认绑定 - } - - } - } + @@ -124,7 +127,6 @@ private bool hasRecoveryEmail; private string? unconfirmedEmailPattern; - private Step step = Step.SetEmail; private string currentPassword = ""; private string email = ""; @@ -135,7 +137,7 @@ await ReloadStatus(); } - private async Task ReloadStatus() + private async Task ReloadStatus(bool silent = false) { loaded = false; try @@ -146,7 +148,7 @@ if (account != null) { var (ok, err, hasPwd, hasEmail, unconfirmed) = await AccountTelegramTools.GetTwoFactorRecoveryEmailStatusAsync(AccountId); - if (!ok) + if (!ok && !silent) { Snackbar.Add($"获取2FA邮箱状态失败:{err}", Severity.Warning); } @@ -154,7 +156,6 @@ hasRecoveryEmail = hasEmail; unconfirmedEmailPattern = unconfirmed; - step = string.IsNullOrWhiteSpace(unconfirmedEmailPattern) ? Step.SetEmail : Step.Confirm; } } finally @@ -203,11 +204,17 @@ if (!ok) { Snackbar.Add($"发送失败:{err}", Severity.Error); + await ReloadStatus(silent: true); + + if (!string.IsNullOrWhiteSpace(unconfirmedEmailPattern)) + { + Snackbar.Add("检测到账号存在待确认邮箱:你可以直接输入邮箱验证码完成确认。", Severity.Info); + } + return; } - unconfirmedEmailPattern = pattern; - step = Step.Confirm; + unconfirmedEmailPattern = pattern ?? unconfirmedEmailPattern; code = ""; Snackbar.Add("验证码已发送,请到邮箱中查看并输入验证码确认。", Severity.Success); } @@ -320,9 +327,4 @@ await Task.CompletedTask; } - private enum Step - { - SetEmail, - Confirm - } } diff --git a/src/TelegramPanel.Web/Components/Pages/Accounts.razor b/src/TelegramPanel.Web/Components/Pages/Accounts.razor index 3100161..c6a3ae7 100644 --- a/src/TelegramPanel.Web/Components/Pages/Accounts.razor +++ b/src/TelegramPanel.Web/Components/Pages/Accounts.razor @@ -27,6 +27,14 @@ @c.Name } + telegramStatus = new(); private HashSet selectedAccounts = new(); private readonly CancellationTokenSource _pageCts = new(); @@ -222,6 +231,7 @@ var (items, total) = await AccountManagement.QueryAccountsPagedAsync( categoryId: categoryId, search: searchString, + onlyWaste: onlyWaste, pageIndex: state.Page, pageSize: state.PageSize, cancellationToken: cancellationToken); @@ -1097,6 +1107,7 @@ var (_, total) = await AccountManagement.QueryAccountsPagedAsync( categoryId: null, search: null, + onlyWaste: false, pageIndex: 0, pageSize: 1, cancellationToken: _pageCts.Token);