diff --git a/src/TelegramPanel.Core/Models/TelegramAccountStatusResult.cs b/src/TelegramPanel.Core/Models/TelegramAccountStatusResult.cs
new file mode 100644
index 0000000..355090b
--- /dev/null
+++ b/src/TelegramPanel.Core/Models/TelegramAccountStatusResult.cs
@@ -0,0 +1,61 @@
+using TelegramPanel.Data.Entities;
+
+namespace TelegramPanel.Core.Models;
+
+///
+/// 账号状态检测结果(用于诊断账号是否可正常连通 Telegram)
+///
+public record TelegramAccountStatusResult(
+ bool Ok,
+ string Summary,
+ string? Details,
+ DateTime CheckedAtUtc,
+ TelegramAccountProfile? Profile = null
+);
+
+///
+/// Telegram 账号资料快照
+///
+public record TelegramAccountProfile(
+ long UserId,
+ string? Phone,
+ string? Username,
+ string? FirstName,
+ string? LastName,
+ bool IsDeleted,
+ bool IsScam,
+ bool IsFake,
+ bool IsRestricted,
+ bool IsVerified,
+ bool IsPremium
+)
+{
+ public string DisplayName
+ {
+ get
+ {
+ var full = $"{FirstName} {LastName}".Trim();
+ if (!string.IsNullOrWhiteSpace(full))
+ return full;
+ if (!string.IsNullOrWhiteSpace(Username))
+ return $"@{Username}";
+ if (!string.IsNullOrWhiteSpace(Phone))
+ return Phone;
+ return UserId.ToString();
+ }
+ }
+
+ public void ApplyTo(Account account)
+ {
+ if (account.UserId <= 0 && UserId > 0)
+ account.UserId = UserId;
+
+ if (!string.IsNullOrWhiteSpace(Username))
+ account.Username = Username;
+
+ var nickname = DisplayName;
+ if (!string.IsNullOrWhiteSpace(nickname))
+ account.Nickname = nickname;
+ }
+}
+
diff --git a/src/TelegramPanel.Core/Models/TelegramAuthorizationInfo.cs b/src/TelegramPanel.Core/Models/TelegramAuthorizationInfo.cs
new file mode 100644
index 0000000..7cc3957
--- /dev/null
+++ b/src/TelegramPanel.Core/Models/TelegramAuthorizationInfo.cs
@@ -0,0 +1,32 @@
+namespace TelegramPanel.Core.Models;
+
+///
+/// 在线设备 / 会话信息(来自 account.getAuthorizations)
+///
+public record TelegramAuthorizationInfo(
+ long Hash,
+ bool Current,
+ int ApiId,
+ string? AppName,
+ string? AppVersion,
+ string? DeviceModel,
+ string? Platform,
+ string? SystemVersion,
+ string? Ip,
+ string? Country,
+ string? Region,
+ DateTime? CreatedAtUtc,
+ DateTime? LastActiveAtUtc
+)
+{
+ public string Title
+ {
+ get
+ {
+ var app = string.IsNullOrWhiteSpace(AppName) ? "UnknownApp" : AppName;
+ var device = string.IsNullOrWhiteSpace(DeviceModel) ? "UnknownDevice" : DeviceModel;
+ return $"{app} - {device}";
+ }
+ }
+}
+
diff --git a/src/TelegramPanel.Core/Models/TelegramSystemMessage.cs b/src/TelegramPanel.Core/Models/TelegramSystemMessage.cs
new file mode 100644
index 0000000..6593131
--- /dev/null
+++ b/src/TelegramPanel.Core/Models/TelegramSystemMessage.cs
@@ -0,0 +1,11 @@
+namespace TelegramPanel.Core.Models;
+
+///
+/// Telegram 系统通知(常用于接收登录验证码,来自 777000 对话)
+///
+public record TelegramSystemMessage(
+ int Id,
+ DateTime? DateUtc,
+ string Text
+);
+
diff --git a/src/TelegramPanel.Core/ServiceCollectionExtensions.cs b/src/TelegramPanel.Core/ServiceCollectionExtensions.cs
index 0239835..3428514 100644
--- a/src/TelegramPanel.Core/ServiceCollectionExtensions.cs
+++ b/src/TelegramPanel.Core/ServiceCollectionExtensions.cs
@@ -20,6 +20,7 @@ public static class ServiceCollectionExtensions
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// 注册账号导入协调服务
services.AddScoped();
diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs
new file mode 100644
index 0000000..52ae355
--- /dev/null
+++ b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs
@@ -0,0 +1,336 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using TelegramPanel.Core.Interfaces;
+using TelegramPanel.Core.Models;
+using TelegramPanel.Data.Entities;
+using TL;
+using WTelegram;
+
+namespace TelegramPanel.Core.Services.Telegram;
+
+///
+/// 账号诊断 / 系统通知 / 在线设备管理
+///
+public class AccountTelegramToolsService
+{
+ private const long TelegramSystemUserId = 777000;
+
+ private readonly AccountManagementService _accountManagement;
+ private readonly ITelegramClientPool _clientPool;
+ private readonly IConfiguration _configuration;
+ private readonly ILogger _logger;
+
+ public AccountTelegramToolsService(
+ AccountManagementService accountManagement,
+ ITelegramClientPool clientPool,
+ IConfiguration configuration,
+ ILogger logger)
+ {
+ _accountManagement = accountManagement;
+ _clientPool = clientPool;
+ _configuration = configuration;
+ _logger = logger;
+ }
+
+ public async Task RefreshAccountStatusAsync(int accountId)
+ {
+ var checkedAt = DateTime.UtcNow;
+ try
+ {
+ var client = await GetOrCreateConnectedClientAsync(accountId);
+ var users = await client.Users_GetUsers(InputUser.Self);
+ var self = users.OfType().FirstOrDefault();
+
+ if (self == null)
+ {
+ return new TelegramAccountStatusResult(
+ Ok: false,
+ Summary: "无法获取账号资料",
+ Details: "Users_GetUsers(Self) 未返回 User",
+ CheckedAtUtc: checkedAt);
+ }
+
+ var profile = new TelegramAccountProfile(
+ UserId: self.id,
+ Phone: self.phone,
+ Username: self.MainUsername,
+ FirstName: self.first_name,
+ LastName: self.last_name,
+ IsDeleted: self.flags.HasFlag(User.Flags.deleted),
+ IsScam: self.flags.HasFlag(User.Flags.scam),
+ IsFake: self.flags.HasFlag(User.Flags.fake),
+ IsRestricted: self.flags.HasFlag(User.Flags.restricted),
+ IsVerified: self.flags.HasFlag(User.Flags.verified),
+ IsPremium: self.flags.HasFlag(User.Flags.premium)
+ );
+
+ var account = await _accountManagement.GetAccountAsync(accountId);
+ if (account != null)
+ {
+ profile.ApplyTo(account);
+ await _accountManagement.UpdateAccountAsync(account);
+ }
+
+ var summary = "正常";
+ if (profile.IsDeleted)
+ summary = "账号已注销/被删除";
+ else if (profile.IsRestricted)
+ summary = "账号受限(Restricted)";
+
+ return new TelegramAccountStatusResult(
+ Ok: true,
+ Summary: summary,
+ Details: BuildProfileDetails(profile),
+ CheckedAtUtc: checkedAt,
+ Profile: profile);
+ }
+ catch (Exception ex)
+ {
+ var (summary, details) = MapTelegramException(ex);
+ _logger.LogWarning(ex, "RefreshAccountStatus failed for account {AccountId}", accountId);
+ return new TelegramAccountStatusResult(
+ Ok: false,
+ Summary: summary,
+ Details: details,
+ CheckedAtUtc: checkedAt);
+ }
+ }
+
+ public async Task> GetLatestSystemMessagesAsync(int accountId, int limit = 20)
+ {
+ if (limit <= 0) limit = 20;
+ if (limit > 100) limit = 100;
+
+ var client = await GetOrCreateConnectedClientAsync(accountId);
+ var peer = await TryResolveSystemPeerAsync(client);
+ if (peer == null)
+ return Array.Empty();
+
+ var history = await client.Messages_GetHistory(peer, limit: limit);
+ var list = new List(history.Messages.Length);
+ foreach (var msgBase in history.Messages)
+ {
+ if (msgBase is not Message m)
+ continue;
+
+ var text = m.message ?? string.Empty;
+ if (string.IsNullOrWhiteSpace(text))
+ continue;
+
+ list.Add(new TelegramSystemMessage(
+ Id: m.id,
+ DateUtc: m.Date.ToUniversalTime(),
+ Text: text.Trim()
+ ));
+ }
+
+ return list
+ .OrderByDescending(x => x.DateUtc ?? DateTime.MinValue)
+ .Take(limit)
+ .ToList();
+ }
+
+ public async Task> GetAuthorizationsAsync(int accountId)
+ {
+ var client = await GetOrCreateConnectedClientAsync(accountId);
+ var auths = await client.Account_GetAuthorizations();
+
+ var list = new List(auths.authorizations.Length);
+ foreach (var a in auths.authorizations)
+ {
+ list.Add(new TelegramAuthorizationInfo(
+ Hash: a.hash,
+ Current: a.flags.HasFlag(Authorization.Flags.current),
+ ApiId: a.api_id,
+ AppName: a.app_name,
+ AppVersion: a.app_version,
+ DeviceModel: a.device_model,
+ Platform: a.platform,
+ SystemVersion: a.system_version,
+ Ip: a.ip,
+ Country: a.country,
+ Region: a.region,
+ CreatedAtUtc: a.date_created == default ? null : a.date_created.ToUniversalTime(),
+ LastActiveAtUtc: a.date_active == default ? null : a.date_active.ToUniversalTime()
+ ));
+ }
+
+ return list
+ .OrderByDescending(x => x.Current)
+ .ThenByDescending(x => x.LastActiveAtUtc ?? DateTime.MinValue)
+ .ToList();
+ }
+
+ public async Task KickAuthorizationAsync(int accountId, long authorizationHash)
+ {
+ var client = await GetOrCreateConnectedClientAsync(accountId);
+ var ok = await client.Account_ResetAuthorization(authorizationHash);
+ return ok;
+ }
+
+ public async Task KickAllOtherAuthorizationsAsync(int accountId)
+ {
+ var client = await GetOrCreateConnectedClientAsync(accountId);
+ var ok = await client.Auth_ResetAuthorizations();
+ return ok;
+ }
+
+ private async Task TryResolveSystemPeerAsync(Client client)
+ {
+ try
+ {
+ var dialogs = await client.Messages_GetAllDialogs();
+ if (!dialogs.users.TryGetValue(TelegramSystemUserId, out var userBase))
+ return null;
+
+ if (userBase is not User u || u.access_hash == 0)
+ return null;
+
+ return new InputPeerUser(u.id, u.access_hash);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogDebug(ex, "Failed to resolve system peer");
+ return null;
+ }
+ }
+
+ private async Task GetOrCreateConnectedClientAsync(int accountId)
+ {
+ var existing = _clientPool.GetClient(accountId);
+ if (existing?.User != null)
+ return existing;
+
+ var account = await _accountManagement.GetAccountAsync(accountId)
+ ?? throw new InvalidOperationException($"账号不存在:{accountId}");
+
+ var apiId = ResolveApiId(account);
+ var apiHash = ResolveApiHash(account);
+ var sessionKey = ResolveSessionKey(account, apiHash);
+
+ if (string.IsNullOrWhiteSpace(account.SessionPath))
+ throw new InvalidOperationException("账号缺少 SessionPath,无法创建 Telegram 客户端");
+
+ var absoluteSessionPath = Path.GetFullPath(account.SessionPath);
+ if (File.Exists(absoluteSessionPath) && SessionDataConverter.LooksLikeSqliteSession(absoluteSessionPath))
+ {
+ var ok = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync(
+ phone: account.Phone,
+ apiId: account.ApiId,
+ apiHash: account.ApiHash,
+ sqliteSessionPath: absoluteSessionPath,
+ logger: _logger
+ );
+
+ if (!ok)
+ {
+ throw new InvalidOperationException(
+ $"该账号的 Session 文件为 SQLite 格式:{account.SessionPath},无法自动转换为可用 session。" +
+ "建议:重新导入包含 session_string 的 json,或到【账号-手机号登录】重新登录生成新的 sessions/*.session。");
+ }
+ }
+
+ await _clientPool.RemoveClientAsync(accountId);
+ var client = await _clientPool.GetOrCreateClientAsync(
+ accountId: accountId,
+ apiId: apiId,
+ apiHash: apiHash,
+ sessionPath: account.SessionPath,
+ sessionKey: sessionKey,
+ phoneNumber: account.Phone,
+ userId: account.UserId > 0 ? account.UserId : null);
+
+ try
+ {
+ await client.ConnectAsync();
+ if (client.User == null && (client.UserId != 0 || account.UserId != 0))
+ await client.LoginUserIfNeeded(reloginOnFailedResume: false);
+ }
+ catch (Exception ex)
+ {
+ if (LooksLikeSessionApiMismatchOrCorrupted(ex))
+ {
+ throw new InvalidOperationException(
+ "该账号的 Session 文件无法解析(通常是 ApiId/ApiHash 与生成 session 时不一致,或 session 文件已损坏)。" +
+ "请到【账号-手机号登录】重新登录生成新的 sessions/*.session 后再试。",
+ ex);
+ }
+
+ throw new InvalidOperationException($"Telegram 会话加载失败:{ex.Message}", ex);
+ }
+
+ if (client.User == null)
+ throw new InvalidOperationException("账号未登录或 session 已失效,请重新登录生成新的 session");
+
+ return client;
+ }
+
+ private int ResolveApiId(Account account)
+ {
+ if (int.TryParse(_configuration["Telegram:ApiId"], out var globalApiId) && globalApiId > 0)
+ return globalApiId;
+ if (account.ApiId > 0)
+ return account.ApiId;
+ throw new InvalidOperationException("未配置全局 ApiId,且账号缺少 ApiId");
+ }
+
+ private string ResolveApiHash(Account account)
+ {
+ var global = _configuration["Telegram:ApiHash"];
+ if (!string.IsNullOrWhiteSpace(global))
+ return global.Trim();
+ if (!string.IsNullOrWhiteSpace(account.ApiHash))
+ return account.ApiHash.Trim();
+ throw new InvalidOperationException("未配置全局 ApiHash,且账号缺少 ApiHash");
+ }
+
+ private static string ResolveSessionKey(Account account, string apiHash)
+ {
+ return !string.IsNullOrWhiteSpace(account.ApiHash) ? account.ApiHash.Trim() : apiHash.Trim();
+ }
+
+ private static bool LooksLikeSessionApiMismatchOrCorrupted(Exception ex)
+ {
+ var msg = ex.Message ?? string.Empty;
+ return msg.Contains("Can't read session block", StringComparison.OrdinalIgnoreCase)
+ || msg.Contains("Use the correct api_hash", StringComparison.OrdinalIgnoreCase)
+ || msg.Contains("Use the correct api_hash/id/key", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static string BuildProfileDetails(TelegramAccountProfile profile)
+ {
+ var flags = new List();
+ if (profile.IsPremium) flags.Add("Premium");
+ if (profile.IsVerified) flags.Add("Verified");
+ if (profile.IsRestricted) flags.Add("Restricted");
+ if (profile.IsScam) flags.Add("Scam");
+ if (profile.IsFake) flags.Add("Fake");
+ if (profile.IsDeleted) flags.Add("Deleted");
+
+ var flagText = flags.Count == 0 ? "无" : string.Join(", ", flags);
+ return $"昵称:{profile.DisplayName};用户名:{profile.Username ?? "-"};UserId:{profile.UserId};标记:{flagText}";
+ }
+
+ private static (string summary, string details) MapTelegramException(Exception ex)
+ {
+ var msg = ex.Message ?? string.Empty;
+
+ if (msg.Contains("FROZEN_METHOD_INVALID", StringComparison.OrdinalIgnoreCase))
+ return ("接口被冻结(账号/ApiId 受限)", msg);
+
+ if (msg.Contains("AUTH_KEY_UNREGISTERED", StringComparison.OrdinalIgnoreCase))
+ return ("Session 失效(AUTH_KEY_UNREGISTERED)", msg);
+
+ if (msg.Contains("SESSION_PASSWORD_NEEDED", StringComparison.OrdinalIgnoreCase))
+ return ("需要两步验证密码(SESSION_PASSWORD_NEEDED)", msg);
+
+ if (msg.Contains("PHONE_NUMBER_BANNED", StringComparison.OrdinalIgnoreCase)
+ || msg.Contains("USER_DEACTIVATED_BAN", StringComparison.OrdinalIgnoreCase))
+ return ("账号被封禁/停用", msg);
+
+ if (msg.Contains("Can't read session block", StringComparison.OrdinalIgnoreCase))
+ return ("Session 无法读取(ApiHash/Key 不匹配或损坏)", msg);
+
+ return ("连接失败", msg);
+ }
+}