From e6ae1bdfffbe9ba07c80739cb29b7d25beba3e4b Mon Sep 17 00:00:00 2001 From: meoacgx Date: Fri, 19 Dec 2025 12:48:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BB=8E=20Telethon=20SQLite=20session=20=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E4=B8=BA=20WTelegram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/Telegram/AccountImportService.cs | 33 ++-- .../Services/Telegram/SessionDataConverter.cs | 164 +++++++++++++++--- .../TelegramPanel.Core.csproj | 1 + 3 files changed, 166 insertions(+), 32 deletions(-) diff --git a/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs b/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs index 600f90e..7013805 100644 --- a/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs +++ b/src/TelegramPanel.Core/Services/Telegram/AccountImportService.cs @@ -343,23 +343,34 @@ public class AccountImportService // 优先尝试使用 json 里的 session_string(映射到 WTelegram 的 session_key)来生成可用 session 文件。 if (LooksLikeSqliteSession(sessionCandidate)) { + bool ok; if (string.IsNullOrWhiteSpace(sessionKey)) { - return new ImportResult(false, phone, userId, username, null, "该 .session 为 SQLite 格式且缺少 session_string,无法导入(请重新登录生成新 session)"); + // 没有 session_string 也不阻挡:直接从 sqlite 里取 dc/auth_key 转换为 WTelegram session + ok = await SessionDataConverter.TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + sqliteSessionPath: sessionCandidate, + apiId: apiId, + apiHash: apiHash.Trim(), + targetSessionPath: targetSessionPath, + phone: phone, + userId: userId, + logger: _logger); + } + else + { + ok = await SessionDataConverter.TryCreateWTelegramSessionFromSessionStringAsync( + sessionString: sessionKey, + apiId: apiId, + apiHash: apiHash.Trim(), + targetSessionPath: targetSessionPath, + phone: phone, + userId: userId, + logger: _logger); } - - var ok = await SessionDataConverter.TryCreateWTelegramSessionFromSessionStringAsync( - sessionString: sessionKey, - apiId: apiId, - apiHash: apiHash.Trim(), - targetSessionPath: targetSessionPath, - phone: phone, - userId: userId, - logger: _logger); if (!ok) { - return new ImportResult(false, phone, userId, username, null, "该 .session 为 SQLite 格式,且 session_string 无法转换为可用 session(请重新登录生成新 session)"); + return new ImportResult(false, phone, userId, username, null, "该 .session 为 SQLite 格式,且无法自动转换为可用 session(请重新登录生成新 session)"); } } else diff --git a/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs b/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs index 364481d..97aea69 100644 --- a/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs +++ b/src/TelegramPanel.Core/Services/Telegram/SessionDataConverter.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Text.Json; +using Microsoft.Data.Sqlite; using Microsoft.Extensions.Logging; using TL; using WTelegram; @@ -22,38 +23,58 @@ internal static class SessionDataConverter try { var absoluteSqliteSessionPath = Path.GetFullPath(sqliteSessionPath); + if (!File.Exists(absoluteSqliteSessionPath) || !LooksLikeSqliteSession(absoluteSqliteSessionPath)) + return false; + var jsonPath = TryFindAnySessionJsonPath(phone, absoluteSqliteSessionPath); - if (string.IsNullOrWhiteSpace(jsonPath)) - return false; + if (!string.IsNullOrWhiteSpace(jsonPath) && File.Exists(jsonPath)) + { + var jsonText = await File.ReadAllTextAsync(jsonPath); + using var doc = JsonDocument.Parse(jsonText); - 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) + { + var sessionString = sessionProp.GetString(); + if (!string.IsNullOrWhiteSpace(sessionString)) + { + _ = doc.RootElement.TryGetProperty("user_id", out var userIdProp); + _ = doc.RootElement.TryGetProperty("uid", out var uidProp); + long? userId = null; + 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; - if (!doc.RootElement.TryGetProperty("session_string", out var sessionProp) || sessionProp.ValueKind != JsonValueKind.String) - return false; + var ok = await TryCreateWTelegramSessionFromSessionStringAsync( + sessionString: sessionString.Trim(), + apiId: apiId, + apiHash: apiHash, + targetSessionPath: absoluteSqliteSessionPath, + phone: phone, + userId: userId, + logger: logger); - var sessionString = sessionProp.GetString(); - if (string.IsNullOrWhiteSpace(sessionString)) - return false; + if (ok) + { + logger.LogInformation("Converted sqlite session for {Phone} using json: {JsonPath}", phone, jsonPath); + return true; + } + } + } + } - _ = doc.RootElement.TryGetProperty("user_id", out var userIdProp); - _ = doc.RootElement.TryGetProperty("uid", out var uidProp); - long? userId = null; - 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( - sessionString: sessionString.Trim(), + // json 不存在/缺少 session_string/转换失败 → 直接从 sqlite 读取 dc/auth_key 进行转换 + var sqliteOk = await TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + sqliteSessionPath: absoluteSqliteSessionPath, apiId: apiId, apiHash: apiHash, targetSessionPath: absoluteSqliteSessionPath, phone: phone, - userId: userId, + userId: null, logger: logger); - if (!ok) return false; - logger.LogInformation("Converted sqlite session for {Phone} using json: {JsonPath}", phone, jsonPath); - return true; + if (sqliteOk) + logger.LogInformation("Converted sqlite session for {Phone} using sqlite content", phone); + + return sqliteOk; } catch (Exception ex) { @@ -146,6 +167,107 @@ internal static class SessionDataConverter } } + public static async Task TryCreateWTelegramSessionFromTelethonSqliteFileAsync( + string sqliteSessionPath, + int apiId, + string apiHash, + string targetSessionPath, + string phone, + long? userId, + ILogger logger) + { + string? backupPath = null; + try + { + var absoluteSqlitePath = Path.GetFullPath(sqliteSessionPath); + var absoluteTarget = Path.GetFullPath(targetSessionPath); + + if (!File.Exists(absoluteSqlitePath) || !LooksLikeSqliteSession(absoluteSqlitePath)) + return false; + + if (!TryReadTelethonSqliteSession(absoluteSqlitePath, out var telethon)) + return false; + + var normalizedPhone = NormalizePhone(phone); + if (string.IsNullOrWhiteSpace(normalizedPhone)) + normalizedPhone = NormalizePhone(Path.GetFileNameWithoutExtension(absoluteTarget)); + + if (File.Exists(absoluteTarget)) + { + var suffix = LooksLikeSqliteSession(absoluteTarget) ? "sqlite.bak" : "bak"; + backupPath = BuildBackupPath(absoluteTarget, suffix); + Directory.CreateDirectory(Path.GetDirectoryName(backupPath) ?? Directory.GetCurrentDirectory()); + File.Move(absoluteTarget, backupPath, overwrite: true); + } + + var ok = await WriteWTelegramSessionFileAsync( + apiId: apiId, + apiHash: apiHash, + sessionPath: absoluteTarget, + phoneDigits: normalizedPhone ?? string.Empty, + userId: userId, + dcId: telethon.DcId, + ipAddress: telethon.IpAddress, + port: telethon.Port, + authKey: telethon.AuthKey, + logger: logger + ); + + if (!ok) + throw new InvalidOperationException("WTelegram session 生成失败或校验失败"); + + return true; + } + catch (Exception ex) + { + logger.LogWarning(ex, "Failed to create WTelegram session from telethon sqlite session"); + try { if (File.Exists(targetSessionPath)) File.Delete(targetSessionPath); } catch { } + try + { + if (!string.IsNullOrWhiteSpace(backupPath) && File.Exists(backupPath) && !File.Exists(targetSessionPath)) + File.Move(backupPath, targetSessionPath, overwrite: true); + } + catch { } + return false; + } + } + + private static bool TryReadTelethonSqliteSession(string sqliteSessionPath, out TelethonSessionData data) + { + try + { + data = default; + using var connection = new SqliteConnection(new SqliteConnectionStringBuilder + { + DataSource = sqliteSessionPath, + Mode = SqliteOpenMode.ReadOnly + }.ToString()); + connection.Open(); + + using var cmd = connection.CreateCommand(); + cmd.CommandText = "SELECT dc_id, server_address, port, auth_key FROM sessions LIMIT 1"; + using var reader = cmd.ExecuteReader(); + if (!reader.Read()) + 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) + return false; + + data = new TelethonSessionData(dcId, serverAddress.Trim(), (ushort)port, authKey); + return true; + } + catch + { + data = default; + return false; + } + } + private static string? TryFindAnySessionJsonPath(string phone, string absoluteSessionPath) { var normalizedPhone = NormalizePhone(phone); diff --git a/src/TelegramPanel.Core/TelegramPanel.Core.csproj b/src/TelegramPanel.Core/TelegramPanel.Core.csproj index 2967738..096ad07 100644 --- a/src/TelegramPanel.Core/TelegramPanel.Core.csproj +++ b/src/TelegramPanel.Core/TelegramPanel.Core.csproj @@ -9,6 +9,7 @@ +