Files
ClawdDotNet/src/ClawdDotNet.Tools.TelegramClient/TelegramClientManager.cs
T
RichardandClaude Opus 5.5 4e41fc12d7 #794: Telegram-Client-Anmeldung ueber die WebUI
Code und 2FA-Passwort wurden nur im Desktop-Dialog abgefragt; eine Instanz
ohne Fenster liess die Anmeldung aus. Jetzt haelt die Instanz die Frage
(TelegramLoginBroker), die Einstellungsseite beantwortet sie. Die Anmeldung
laeuft im Hintergrund, damit die HTTP-Flaeche, ueber die der Code kommt,
nicht auf ihn wartet. Eine offene Frage verfaellt nach 15 Minuten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:59:37 +02:00

149 lines
4.7 KiB
C#

using ClawdDotNet.Core.Config;
using Microsoft.Extensions.Logging;
using TL;
using WTelegram;
namespace ClawdDotNet.Tools.TelegramClient;
public sealed class TelegramClientManager : IAsyncDisposable
{
private Client? _client;
private User? _self;
private readonly SemaphoreSlim _gate = new(1, 1);
private readonly ILogger _logger;
private readonly int _apiId;
private readonly string _apiHash;
private readonly string _phoneNumber;
private readonly string _sessionPath;
private readonly string? _2faPassword;
private readonly Dictionary<long, User> _users = new();
private readonly Dictionary<long, ChatBase> _chats = new();
public event Func<string, Task<string>>? OnLoginCodeRequired;
public event Func<Task<string>>? On2FAPasswordRequired;
public bool IsConnected => _client?.User != null;
public User? Self => _self;
public TelegramClientManager(
InstanceConfig config,
string instancePath,
ILogger logger)
{
_logger = logger;
var tgConfig = config.TelegramClient
?? throw new InvalidOperationException("TelegramClient config missing in InstanceConfig");
_apiId = tgConfig.ApiId;
_apiHash = tgConfig.ApiHash;
_phoneNumber = tgConfig.PhoneNumber;
_sessionPath = Path.Combine(instancePath, "sessions", $"telegram_{config.InstanceId}.session");
_2faPassword = tgConfig.Password2FA;
}
public async Task ConnectAsync(CancellationToken ct)
{
var sessionDir = Path.GetDirectoryName(_sessionPath)!;
Directory.CreateDirectory(sessionDir);
// #794: A login can be started again after it failed; the old client goes first.
_client?.Dispose();
_client = new Client(ConfigCallback);
Helpers.Log = (lvl, msg) =>
_logger.Log((LogLevel)lvl, "WTelegram: {Message}", msg);
_self = await _client.LoginUserIfNeeded();
_logger.LogInformation(
"Telegram: logged in as {Name} (id {Id})",
_self.first_name, _self.id);
}
private string? ConfigCallback(string what) => what switch
{
"api_id" => _apiId.ToString(),
"api_hash" => _apiHash,
"phone_number" => _phoneNumber,
"session_pathname" => _sessionPath,
"verification_code" => OnLoginCodeRequired != null
? OnLoginCodeRequired("Please enter Telegram verification code:").Result
: throw new InvalidOperationException(
"Verification code required but no UI handler registered. " +
"Connect OnLoginCodeRequired to prompt the user."),
"password" => _2faPassword
?? (On2FAPasswordRequired != null
? On2FAPasswordRequired().Result
: throw new InvalidOperationException(
"2FA password required but not configured.")),
_ => null
};
public async Task<Messages_Dialogs> GetAllDialogsAsync(CancellationToken ct)
{
await _gate.WaitAsync(ct);
try
{
var dialogs = await _client!.Messages_GetAllDialogs();
dialogs.CollectUsersChats(_users, _chats);
return dialogs;
}
finally { _gate.Release(); }
}
public async Task<Messages_Chats> GetAllChatsAsync(CancellationToken ct)
{
await _gate.WaitAsync(ct);
try { return await _client!.Messages_GetAllChats(); }
finally { _gate.Release(); }
}
public async Task<Messages_MessagesBase> GetMessagesAsync(
InputPeer peer, int minId = 0, int limit = 50, CancellationToken ct = default)
{
await _gate.WaitAsync(ct);
try
{
return await _client!.Messages_GetHistory(
peer, offset_id: 0, offset_date: default,
add_offset: 0, limit: limit, max_id: 0, min_id: minId, hash: 0);
}
finally { _gate.Release(); }
}
public async Task<Contacts_ResolvedPeer> ResolveUsernameAsync(string username, CancellationToken ct)
{
await _gate.WaitAsync(ct);
try
{
var resolved = await _client!.Contacts_ResolveUsername(username.TrimStart('@'));
foreach (var u in resolved.users.Values)
_users[u.id] = u;
foreach (var c in resolved.chats.Values)
_chats[c.ID] = c;
return resolved;
}
finally { _gate.Release(); }
}
public InputPeer? GetInputPeerFromCache(long chatId)
{
if (_users.TryGetValue(chatId, out var user))
return user;
if (_chats.TryGetValue(chatId, out var chat))
return chat;
return null;
}
public async ValueTask DisposeAsync()
{
_client?.Dispose();
_gate.Dispose();
}
}