From 0acfc0ffdbc33b2554d06742f06f4e69a79a40ce Mon Sep 17 00:00:00 2001 From: meoacgx Date: Sat, 20 Dec 2025 07:52:01 +0800 Subject: [PATCH] Improve session import diagnostics --- .../Services/Telegram/AccountImportService.cs | 20 +- .../Telegram/AccountTelegramToolsService.cs | 6 +- .../Services/Telegram/ChannelService.cs | 6 +- .../Services/Telegram/GroupService.cs | 6 +- .../Services/Telegram/SessionDataConverter.cs | 141 +++++++++---- src/TelegramPanel.Web/Program.cs | 191 ++++++++++++++++++ 6 files changed, 316 insertions(+), 54 deletions(-) diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs index e5c7268..e02c486 100644 --- a/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs @@ -332,18 +332,18 @@ public class AccountImportService using var doc = System.Text.Json.JsonDocument.Parse(json); var root = doc.RootElement; - if (!TryGetInt(root, out var apiId, "api_id", "app_id")) + if (!TryGetInt(root, out var apiId, "api_id", "app_id", "apiId", "appId")) return new ImportResult(false, null, null, null, null, $"json 缺少 api_id: {jsonPath}"); - if (!TryGetString(root, out var apiHash, "api_hash", "app_hash") || string.IsNullOrWhiteSpace(apiHash)) + if (!TryGetString(root, out var apiHash, "api_hash", "app_hash", "apiHash", "appHash") || string.IsNullOrWhiteSpace(apiHash)) return new ImportResult(false, null, null, null, null, $"json 缺少 api_hash: {jsonPath}"); - if (!TryGetString(root, out var phone, "phone") || string.IsNullOrWhiteSpace(phone)) + if (!TryGetString(root, out var phone, "phone", "phone_number", "phoneNumber") || string.IsNullOrWhiteSpace(phone)) return new ImportResult(false, null, null, null, null, $"json 缺少 phone: {jsonPath}"); phone = phone.Trim(); - _ = TryGetLong(root, out var userId, "user_id", "uid"); + _ = TryGetLong(root, out var userId, "user_id", "uid", "userId"); _ = TryGetString(root, out var username, "username"); username = string.IsNullOrWhiteSpace(username) ? null : username.Trim(); _ = TryGetString(root, out var firstName, "first_name", "firstName"); @@ -374,11 +374,11 @@ public class AccountImportService // 优先尝试使用 json 里的 session_string(映射到 WTelegram 的 session_key)来生成可用 session 文件。 if (LooksLikeSqliteSession(sessionCandidate)) { - bool ok; + SessionDataConverter.SessionConvertResult converted; if (string.IsNullOrWhiteSpace(sessionKey)) { // 没有 session_string 也不阻挡:直接从 sqlite 里取 dc/auth_key 转换为 WTelegram session - ok = await SessionDataConverter.TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + converted = await SessionDataConverter.TryCreateWTelegramSessionFromTelethonSqliteFileAsync( sqliteSessionPath: sessionCandidate, apiId: apiId, apiHash: apiHash.Trim(), @@ -389,7 +389,7 @@ public class AccountImportService } else { - ok = await SessionDataConverter.TryCreateWTelegramSessionFromSessionStringAsync( + converted = await SessionDataConverter.TryCreateWTelegramSessionFromSessionStringAsync( sessionString: sessionKey, apiId: apiId, apiHash: apiHash.Trim(), @@ -399,9 +399,11 @@ public class AccountImportService logger: _logger); } - if (!ok) + if (!converted.Ok) { - return new ImportResult(false, phone, userId, username, null, "该 .session 为 SQLite 格式,且无法自动转换为可用 session(请重新登录生成新 session)"); + var reason = converted.Reason ?? "未知原因"; + return new ImportResult(false, phone, userId, username, null, + $"该 .session 为 SQLite 格式,但转换/校验失败:{reason}(通常表示账号已掉线/被登出/会话失效,需要重新登录生成新 session)"); } } else diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs index 0f28d4b..9471eb7 100644 --- a/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/AccountTelegramToolsService.cs @@ -705,7 +705,7 @@ public class AccountTelegramToolsService { cancellationToken.ThrowIfCancellationRequested(); - var ok = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync( + var converted = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync( phone: account.Phone, apiId: account.ApiId, apiHash: account.ApiHash, @@ -713,11 +713,11 @@ public class AccountTelegramToolsService logger: _logger ); - if (!ok) + if (!converted.Ok) { throw new InvalidOperationException( $"该账号的 Session 文件为 SQLite 格式:{account.SessionPath},无法自动转换为可用 session。" + - "建议:重新导入包含 session_string 的 json,或到【账号-手机号登录】重新登录生成新的 sessions/*.session。"); + $"原因:{converted.Reason}。建议:重新导入包含 session_string 的 json,或到【账号-手机号登录】重新登录生成新的 sessions/*.session。"); } } diff --git a/src/TelegramPanel.Core/Services/Telegram/ChannelService.cs b/src/TelegramPanel.Core/Services/Telegram/ChannelService.cs index a054be1..464a6ce 100644 --- a/src/TelegramPanel.Core/Services/Telegram/ChannelService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/ChannelService.cs @@ -576,7 +576,7 @@ public class ChannelService : IChannelService var absoluteSessionPath = Path.GetFullPath(account.SessionPath); if (File.Exists(absoluteSessionPath) && LooksLikeSqliteSession(absoluteSessionPath)) { - var ok = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync( + var converted = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync( phone: account.Phone, apiId: account.ApiId, apiHash: account.ApiHash, @@ -584,12 +584,12 @@ public class ChannelService : IChannelService logger: _logger ); - if (!ok) + if (!converted.Ok) { throw new InvalidOperationException( $"该账号的 Session 文件为 SQLite 格式:{account.SessionPath},本项目无法直接复用。" + "已尝试从本地 json(例如 sessions/<手机号>.json 或 session数据/<手机号>/*.json)读取 session_string 自动转换但失败;" + - "请到【账号-手机号登录】重新登录生成新的 sessions/*.session 后再操作。"); + $"原因:{converted.Reason}。请到【账号-手机号登录】重新登录生成新的 sessions/*.session 后再操作。"); } } diff --git a/src/TelegramPanel.Core/Services/Telegram/GroupService.cs b/src/TelegramPanel.Core/Services/Telegram/GroupService.cs index 9c24492..27762b8 100644 --- a/src/TelegramPanel.Core/Services/Telegram/GroupService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/GroupService.cs @@ -272,7 +272,7 @@ public class GroupService : IGroupService var absoluteSessionPath = Path.GetFullPath(account.SessionPath); if (System.IO.File.Exists(absoluteSessionPath) && LooksLikeSqliteSession(absoluteSessionPath)) { - var ok = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync( + var converted = await SessionDataConverter.TryConvertSqliteSessionFromJsonAsync( phone: account.Phone, apiId: account.ApiId, apiHash: account.ApiHash, @@ -280,12 +280,12 @@ public class GroupService : IGroupService logger: _logger ); - if (!ok) + if (!converted.Ok) { throw new InvalidOperationException( $"该账号的 Session 文件为 SQLite 格式:{account.SessionPath},本项目无法直接复用。" + "已尝试从本地 json(例如 sessions/<手机号>.json 或 session数据/<手机号>/*.json)读取 session_string 自动转换但失败;" + - "请到【账号-手机号登录】重新登录生成新的 sessions/*.session 后再操作。"); + $"原因:{converted.Reason}。请到【账号-手机号登录】重新登录生成新的 sessions/*.session 后再操作。"); } } diff --git a/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs b/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs index 97aea69..9eb5e37 100644 --- a/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs +++ b/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs @@ -11,9 +11,15 @@ using WTelegram; namespace TelegramPanel.Core.Services.Telegram; -internal static class SessionDataConverter +public static class SessionDataConverter { - public static async Task TryConvertSqliteSessionFromJsonAsync( + public readonly record struct SessionConvertResult(bool Ok, string? Reason) + { + public static SessionConvertResult Success() => new(true, null); + public static SessionConvertResult Fail(string? reason) => new(false, string.IsNullOrWhiteSpace(reason) ? "未知原因" : reason.Trim()); + } + + public static async Task TryConvertSqliteSessionFromJsonAsync( string phone, int apiId, string apiHash, @@ -24,7 +30,7 @@ internal static class SessionDataConverter { var absoluteSqliteSessionPath = Path.GetFullPath(sqliteSessionPath); if (!File.Exists(absoluteSqliteSessionPath) || !LooksLikeSqliteSession(absoluteSqliteSessionPath)) - return false; + return SessionConvertResult.Fail("不是有效的 SQLite .session 文件"); var jsonPath = TryFindAnySessionJsonPath(phone, absoluteSqliteSessionPath); if (!string.IsNullOrWhiteSpace(jsonPath) && File.Exists(jsonPath)) @@ -32,7 +38,12 @@ internal static class SessionDataConverter var jsonText = await File.ReadAllTextAsync(jsonPath); using var doc = JsonDocument.Parse(jsonText); - if (doc.RootElement.TryGetProperty("session_string", out var sessionProp) && sessionProp.ValueKind == JsonValueKind.String) + JsonElement sessionProp; + var hasSessionProp = doc.RootElement.TryGetProperty("session_string", out sessionProp); + if (!hasSessionProp || sessionProp.ValueKind != JsonValueKind.String) + hasSessionProp = doc.RootElement.TryGetProperty("sessionString", out sessionProp); + + if (hasSessionProp && sessionProp.ValueKind == JsonValueKind.String) { var sessionString = sessionProp.GetString(); if (!string.IsNullOrWhiteSpace(sessionString)) @@ -43,7 +54,7 @@ internal static class SessionDataConverter if (userIdProp.ValueKind == JsonValueKind.Number && userIdProp.TryGetInt64(out var uid1)) userId = uid1; if (userId == null && uidProp.ValueKind == JsonValueKind.Number && uidProp.TryGetInt64(out var uid2)) userId = uid2; - var ok = await TryCreateWTelegramSessionFromSessionStringAsync( + var converted = await TryCreateWTelegramSessionFromSessionStringAsync( sessionString: sessionString.Trim(), apiId: apiId, apiHash: apiHash, @@ -52,17 +63,19 @@ internal static class SessionDataConverter userId: userId, logger: logger); - if (ok) + if (converted.Ok) { logger.LogInformation("Converted sqlite session for {Phone} using json: {JsonPath}", phone, jsonPath); - return true; + return SessionConvertResult.Success(); } + + logger.LogWarning("Failed to convert sqlite session for {Phone} using json session_string: {Reason}", phone, converted.Reason); } } } // json 不存在/缺少 session_string/转换失败 → 直接从 sqlite 读取 dc/auth_key 进行转换 - var sqliteOk = await TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + var sqliteConverted = await TryCreateWTelegramSessionFromTelethonSqliteFileAsync( sqliteSessionPath: absoluteSqliteSessionPath, apiId: apiId, apiHash: apiHash, @@ -71,19 +84,21 @@ internal static class SessionDataConverter userId: null, logger: logger); - if (sqliteOk) + if (sqliteConverted.Ok) logger.LogInformation("Converted sqlite session for {Phone} using sqlite content", phone); + else + logger.LogWarning("Failed to convert sqlite session for {Phone} using sqlite content: {Reason}", phone, sqliteConverted.Reason); - return sqliteOk; + return sqliteConverted; } catch (Exception ex) { logger.LogWarning(ex, "Failed to convert sqlite session for {Phone} from json", phone); - return false; + return SessionConvertResult.Fail($"{ex.GetType().Name}: {ex.Message}"); } } - public static async Task TryCreateWTelegramSessionFromSessionStringAsync( + public static async Task TryCreateWTelegramSessionFromSessionStringAsync( string sessionString, int apiId, string apiHash, @@ -96,15 +111,17 @@ internal static class SessionDataConverter try { if (string.IsNullOrWhiteSpace(sessionString)) - return false; + return SessionConvertResult.Fail("session_string 为空"); var absoluteTargetSessionPath = Path.GetFullPath(targetSessionPath); var normalizedPhone = NormalizePhone(phone); if (string.IsNullOrWhiteSpace(normalizedPhone)) normalizedPhone = NormalizePhone(Path.GetFileNameWithoutExtension(absoluteTargetSessionPath)); + if (string.IsNullOrWhiteSpace(normalizedPhone)) + return SessionConvertResult.Fail("无法从手机号/文件名解析出 phoneDigits"); if (!TryParseTelethonStringSession(sessionString.Trim(), out var telethon)) - return false; + return SessionConvertResult.Fail("session_string 无法解析为 Telethon StringSession(可能格式不兼容或已损坏)"); // 先备份旧 sqlite session,再生成 WTelegram session 覆盖原路径 if (File.Exists(absoluteTargetSessionPath)) @@ -119,7 +136,7 @@ internal static class SessionDataConverter Directory.CreateDirectory(sessionsDir); // 使用 WTelegram 的 Session 存储格式生成可用 session 文件(加密 JSON) - var ok = await WriteWTelegramSessionFileAsync( + var written = await WriteWTelegramSessionFileAsync( apiId: apiId, apiHash: apiHash, sessionPath: absoluteTargetSessionPath, @@ -131,10 +148,10 @@ internal static class SessionDataConverter authKey: telethon.AuthKey, logger: logger ); - if (!ok) - throw new InvalidOperationException("WTelegram session 生成失败或校验失败"); + if (!written.Ok) + return written; - return true; + return SessionConvertResult.Success(); } catch (Exception ex) { @@ -146,7 +163,7 @@ internal static class SessionDataConverter File.Move(backupPath, targetSessionPath, overwrite: true); } catch { } - return false; + return SessionConvertResult.Fail($"{ex.GetType().Name}: {ex.Message}"); } } @@ -167,7 +184,7 @@ internal static class SessionDataConverter } } - public static async Task TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + public static async Task TryCreateWTelegramSessionFromTelethonSqliteFileAsync( string sqliteSessionPath, int apiId, string apiHash, @@ -183,14 +200,16 @@ internal static class SessionDataConverter var absoluteTarget = Path.GetFullPath(targetSessionPath); if (!File.Exists(absoluteSqlitePath) || !LooksLikeSqliteSession(absoluteSqlitePath)) - return false; + return SessionConvertResult.Fail("不是有效的 SQLite .session 文件"); - if (!TryReadTelethonSqliteSession(absoluteSqlitePath, out var telethon)) - return false; + if (!TryReadTelethonSqliteSession(absoluteSqlitePath, out var telethon, out var readReason)) + return SessionConvertResult.Fail($"SQLite session 读取失败:{readReason}"); var normalizedPhone = NormalizePhone(phone); if (string.IsNullOrWhiteSpace(normalizedPhone)) normalizedPhone = NormalizePhone(Path.GetFileNameWithoutExtension(absoluteTarget)); + if (string.IsNullOrWhiteSpace(normalizedPhone)) + return SessionConvertResult.Fail("无法从手机号/文件名解析出 phoneDigits"); if (File.Exists(absoluteTarget)) { @@ -200,7 +219,7 @@ internal static class SessionDataConverter File.Move(absoluteTarget, backupPath, overwrite: true); } - var ok = await WriteWTelegramSessionFileAsync( + var written = await WriteWTelegramSessionFileAsync( apiId: apiId, apiHash: apiHash, sessionPath: absoluteTarget, @@ -213,10 +232,10 @@ internal static class SessionDataConverter logger: logger ); - if (!ok) - throw new InvalidOperationException("WTelegram session 生成失败或校验失败"); + if (!written.Ok) + return written; - return true; + return SessionConvertResult.Success(); } catch (Exception ex) { @@ -228,15 +247,16 @@ internal static class SessionDataConverter File.Move(backupPath, targetSessionPath, overwrite: true); } catch { } - return false; + return SessionConvertResult.Fail($"{ex.GetType().Name}: {ex.Message}"); } } - private static bool TryReadTelethonSqliteSession(string sqliteSessionPath, out TelethonSessionData data) + private static bool TryReadTelethonSqliteSession(string sqliteSessionPath, out TelethonSessionData data, out string reason) { try { data = default; + reason = "未知原因"; using var connection = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = sqliteSessionPath, @@ -248,22 +268,60 @@ internal static class SessionDataConverter cmd.CommandText = "SELECT dc_id, server_address, port, auth_key FROM sessions LIMIT 1"; using var reader = cmd.ExecuteReader(); if (!reader.Read()) + { + reason = "sessions 表为空或无记录"; return false; + } var dcId = reader.GetInt32(0); var serverAddress = reader.IsDBNull(1) ? null : reader.GetString(1); var port = reader.IsDBNull(2) ? 0 : reader.GetInt32(2); var authKey = reader.IsDBNull(3) ? null : (byte[])reader[3]; - if (dcId <= 0 || string.IsNullOrWhiteSpace(serverAddress) || port <= 0 || authKey == null || authKey.Length != 256) + if (dcId <= 0) + { + reason = $"dc_id 无效:{dcId}"; return false; + } + + if (string.IsNullOrWhiteSpace(serverAddress)) + { + reason = "server_address 为空"; + return false; + } + + if (port <= 0) + { + reason = $"port 无效:{port}"; + return false; + } + + if (authKey == null || authKey.Length == 0) + { + reason = "auth_key 为空"; + return false; + } + + if (authKey.Length != 256) + { + reason = $"auth_key 长度不符合预期:{authKey.Length}(期望 256)"; + return false; + } data = new TelethonSessionData(dcId, serverAddress.Trim(), (ushort)port, authKey); + reason = "OK"; return true; } - catch + catch (SqliteException ex) { data = default; + reason = $"SqliteException: {ex.Message}"; + return false; + } + catch (Exception ex) + { + data = default; + reason = $"{ex.GetType().Name}: {ex.Message}"; return false; } } @@ -380,7 +438,7 @@ internal static class SessionDataConverter } } - private static async Task WriteWTelegramSessionFileAsync( + private static async Task WriteWTelegramSessionFileAsync( int apiId, string apiHash, string sessionPath, @@ -404,8 +462,9 @@ internal static class SessionDataConverter }; // 1) 创建空 session 文件 - await using (var builder = new Client(Config)) + try { + await using var builder = new Client(Config); var clientType = typeof(Client); var sessionField = clientType.GetField("_session", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException("无法访问 WTelegram.Client._session"); @@ -456,6 +515,11 @@ internal static class SessionDataConverter ?? throw new InvalidOperationException("无法访问 Session.Save()"); lock (sessionObj) save.Invoke(sessionObj, null); } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to write WTelegram session file for {Phone}", phoneDigits); + return SessionConvertResult.Fail($"WTelegram session 文件写入失败:{ex.GetType().Name}: {ex.Message}"); + } // 2) 验证:使用已导入的 AuthKey 直接请求 Self(避免进入 LoginUserIfNeeded 的“发验证码”分支) await using var probe = new Client(Config); @@ -465,7 +529,7 @@ internal static class SessionDataConverter var users = await probe.Users_GetUsers(InputUser.Self); var self = users.OfType().FirstOrDefault(); if (self == null) - return false; + return SessionConvertResult.Fail("已连接但无法获取 Self(可能 session 未授权/已失效)"); // 写回 UserId(让后续服务端能用 LoginUserIfNeeded 快速恢复 User) var clientType = typeof(Client); @@ -486,12 +550,17 @@ internal static class SessionDataConverter lock (sessionObj) save.Invoke(sessionObj, null); logger.LogInformation("WTelegram session validated for {Phone} (user_id={UserId}) on DC {DcId}", phoneDigits, self.id, dcId); - return true; + return SessionConvertResult.Success(); + } + catch (RpcException ex) + { + logger.LogWarning(ex, "WTelegram session validation failed for {Phone}", phoneDigits); + return SessionConvertResult.Fail($"Telegram RPC 错误:{ex.Message}"); } catch (Exception ex) { logger.LogWarning(ex, "WTelegram session validation failed for {Phone}", phoneDigits); - return false; + return SessionConvertResult.Fail($"验证失败:{ex.GetType().Name}: {ex.Message}"); } } diff --git a/src/TelegramPanel.Web/Program.cs b/src/TelegramPanel.Web/Program.cs index affa17f..c3105dc 100644 --- a/src/TelegramPanel.Web/Program.cs +++ b/src/TelegramPanel.Web/Program.cs @@ -10,6 +10,197 @@ using TelegramPanel.Core.Services; using TelegramPanel.Core.Services.Telegram; using TelegramPanel.Data; using TelegramPanel.Web.Services; +using Microsoft.Extensions.Logging; + +// 诊断:对某个目录下的 *.json/*.session 做一次“可转换/可校验”检查(不写数据库) +// 用法:dotnet run --project src/TelegramPanel.Web -- --diag-session-dir "D:/path/to/dir" +if (args.Length >= 2 && string.Equals(args[0], "--diag-session-dir", StringComparison.OrdinalIgnoreCase)) +{ + var dir = args[1]; + if (!Directory.Exists(dir)) + { + Console.Error.WriteLine($"目录不存在:{dir}"); + return; + } + + using var loggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(b => + { + b.AddSimpleConsole(o => + { + o.SingleLine = true; + o.TimestampFormat = "HH:mm:ss "; + }); + b.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Information); + }); + var logger = loggerFactory.CreateLogger("SessionDiag"); + + var jsonFiles = Directory.EnumerateFiles(dir, "*.json", SearchOption.TopDirectoryOnly) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) + .ToList(); + + if (jsonFiles.Count == 0) + { + Console.Error.WriteLine("目录内未找到任何 .json 文件"); + return; + } + + var tempOutDir = Path.Combine(Path.GetTempPath(), "telegram-panel-diag-sessions"); + Directory.CreateDirectory(tempOutDir); + + foreach (var jsonPath in jsonFiles) + { + try + { + var json = await File.ReadAllTextAsync(jsonPath); + using var doc = System.Text.Json.JsonDocument.Parse(json); + var root = doc.RootElement; + + if (!TryGetInt(root, out var apiId, "api_id", "app_id")) + { + Console.WriteLine($"[FAIL] {Path.GetFileName(jsonPath)}: 缺少 api_id/app_id"); + continue; + } + + if (!TryGetString(root, out var apiHash, "api_hash", "app_hash") || string.IsNullOrWhiteSpace(apiHash)) + { + Console.WriteLine($"[FAIL] {Path.GetFileName(jsonPath)}: 缺少 api_hash/app_hash"); + continue; + } + + if (!TryGetString(root, out var phone, "phone") || string.IsNullOrWhiteSpace(phone)) + { + Console.WriteLine($"[FAIL] {Path.GetFileName(jsonPath)}: 缺少 phone"); + continue; + } + + _ = TryGetLong(root, out var userId, "user_id", "uid"); + _ = TryGetString(root, out var sessionString, "session_string", "sessionString"); + + phone = phone.Trim(); + apiHash = apiHash.Trim(); + sessionString = string.IsNullOrWhiteSpace(sessionString) ? null : sessionString.Trim(); + + var baseName = Path.GetFileNameWithoutExtension(jsonPath); + var sessionPath = Path.Combine(dir, $"{baseName}.session"); + if (!File.Exists(sessionPath)) + sessionPath = Directory.EnumerateFiles(dir, "*.session", SearchOption.TopDirectoryOnly).FirstOrDefault() ?? string.Empty; + + if (string.IsNullOrWhiteSpace(sessionPath) || !File.Exists(sessionPath)) + { + Console.WriteLine($"[FAIL] {phone}: 未找到 .session 文件(json={Path.GetFileName(jsonPath)})"); + continue; + } + + var targetSessionPath = Path.Combine(tempOutDir, $"{phone}.session"); + + TelegramPanel.Core.Services.Telegram.SessionDataConverter.SessionConvertResult converted; + if (TelegramPanel.Core.Services.Telegram.SessionDataConverter.LooksLikeSqliteSession(sessionPath)) + { + if (!string.IsNullOrWhiteSpace(sessionString)) + { + converted = await TelegramPanel.Core.Services.Telegram.SessionDataConverter.TryCreateWTelegramSessionFromSessionStringAsync( + sessionString: sessionString, + apiId: apiId, + apiHash: apiHash, + targetSessionPath: targetSessionPath, + phone: phone, + userId: userId, + logger: logger); + } + else + { + converted = await TelegramPanel.Core.Services.Telegram.SessionDataConverter.TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + sqliteSessionPath: sessionPath, + apiId: apiId, + apiHash: apiHash, + targetSessionPath: targetSessionPath, + phone: phone, + userId: userId, + logger: logger); + } + } + else + { + File.Copy(sessionPath, targetSessionPath, overwrite: true); + converted = TelegramPanel.Core.Services.Telegram.SessionDataConverter.SessionConvertResult.Success(); + } + + if (converted.Ok) + Console.WriteLine($"[OK] {phone}: 可用(输出={targetSessionPath})"); + else + Console.WriteLine($"[FAIL] {phone}: {converted.Reason}"); + } + catch (Exception ex) + { + Console.WriteLine($"[FAIL] {Path.GetFileName(jsonPath)}: {ex.GetType().Name}: {ex.Message}"); + } + } + + return; + + static bool TryGetInt(System.Text.Json.JsonElement root, out int value, params string[] names) + { + foreach (var name in names) + { + if (root.TryGetProperty(name, out var prop)) + { + if (prop.ValueKind == System.Text.Json.JsonValueKind.Number && prop.TryGetInt32(out var i)) + { + value = i; + return true; + } + + if (prop.ValueKind == System.Text.Json.JsonValueKind.String && int.TryParse(prop.GetString(), out var isv)) + { + value = isv; + return true; + } + } + } + + value = 0; + return false; + } + + static bool TryGetLong(System.Text.Json.JsonElement root, out long? value, params string[] names) + { + foreach (var name in names) + { + if (root.TryGetProperty(name, out var prop)) + { + if (prop.ValueKind == System.Text.Json.JsonValueKind.Number && prop.TryGetInt64(out var l)) + { + value = l; + return true; + } + + if (prop.ValueKind == System.Text.Json.JsonValueKind.String && long.TryParse(prop.GetString(), out var ls)) + { + value = ls; + return true; + } + } + } + + value = null; + return false; + } + + static bool TryGetString(System.Text.Json.JsonElement root, out string? value, params string[] names) + { + foreach (var name in names) + { + if (root.TryGetProperty(name, out var prop) && prop.ValueKind == System.Text.Json.JsonValueKind.String) + { + value = prop.GetString(); + return true; + } + } + + value = null; + return false; + } +} var builder = WebApplication.CreateBuilder(args);