Improve session import diagnostics
This commit is contained in:
1 parent
8ef277bb78
commit
0acfc0ffdb
6 files changed
+316
-54
No files matched your search
@@ -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
|
||||
|
||||
@@ -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。");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 后再操作。");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 后再操作。");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,15 @@ using WTelegram;
|
||||
|
||||
namespace TelegramPanel.Core.Services.Telegram;
|
||||
|
||||
internal static class SessionDataConverter
|
||||
public static class SessionDataConverter
|
||||
{
|
||||
public static async Task<bool> 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<SessionConvertResult> 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<bool> TryCreateWTelegramSessionFromSessionStringAsync(
|
||||
public static async Task<SessionConvertResult> 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<bool> TryCreateWTelegramSessionFromTelethonSqliteFileAsync(
|
||||
public static async Task<SessionConvertResult> 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<bool> WriteWTelegramSessionFileAsync(
|
||||
private static async Task<SessionConvertResult> 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<User>().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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user