diff --git a/src/Client.Helpers.cs b/src/Client.Helpers.cs index c8fc6c9..a3c55c4 100644 --- a/src/Client.Helpers.cs +++ b/src/Client.Helpers.cs @@ -187,10 +187,10 @@ namespace WTelegram /// Text formatting entities for the caption. You can use MarkdownToEntities to create these /// UTC timestamp when the message should be sent /// Any URL pointing to a video should be considered as non-streamable - /// The last of the media group messages, confirmed by Telegram + /// The media group messages as received by Telegram /// - /// * The caption/entities are set on the last media
- /// * and are supported by downloading the file from the web via HttpClient and sending it to Telegram. + /// * The caption/entities are set on the first media
+ /// * and are supported natively for bot accounts, and for user accounts by downloading the file from the web via HttpClient and sending it to Telegram. /// WTelegramClient proxy settings don't apply to HttpClient
/// * You may run into errors if you mix, in the same album, photos and file documents having no thumbnails/video attributes ///
diff --git a/src/Client.cs b/src/Client.cs index dba4734..9ad0516 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography; @@ -28,8 +29,6 @@ namespace WTelegram /// This event will be called when unsollicited updates/messages are sent by Telegram servers /// Make your handler , or return or
See Examples/Program_ReactorError.cs for how to use this
or Examples/Program_ListenUpdate.cs using the UpdateManager class instead
public event Func OnUpdates; - [Obsolete("This event was renamed OnUpdates (plural). You may also want to consider using our new UpdateManager class instead (see FAQ)")] - public event Func OnUpdate { add { OnUpdates += value; } remove { OnUpdates -= value; } } /// This event is called for other types of notifications (login states, reactor errors, ...) public event Func OnOther; /// Use this handler to intercept Updates that resulted from your own API calls @@ -67,6 +66,7 @@ namespace WTelegram private Session.DCSession _dcSession; private TcpClient _tcpClient; private Stream _networkStream; + private HttpClient _httpClient; private IObject _lastSentMsg; private long _lastRecvMsgId; private readonly List _msgsToAck = []; @@ -198,6 +198,10 @@ namespace WTelegram } public void DisableUpdates(bool disable = true) => _dcSession.DisableUpdates(disable); + + /// Enable connecting to Telegram via on-demand HTTP requests instead of permanent TCP connection + /// HttpClient to use. Leave for a default one + public void HttpMode(HttpClient httpClient = null) => _httpClient = httpClient ?? new(); /// Disconnect from Telegram (shouldn't be needed in normal usage) /// Forget about logged-in user @@ -513,16 +517,16 @@ namespace WTelegram return null; } } - - static string TransportError(int error_code) => error_code switch - { - 404 => "Auth key not found", - 429 => "Transport flood", - 444 => "Invalid DC", - _ => Enum.GetName(typeof(HttpStatusCode), error_code) ?? "Transport error" - }; } + static string TransportError(int error_code) => error_code switch + { + 404 => "Auth key not found", + 429 => "Transport flood", + 444 => "Invalid DC", + _ => Enum.GetName(typeof(HttpStatusCode), error_code) ?? "Transport error" + }; + internal void CheckSalt() { lock (_session) @@ -871,6 +875,8 @@ namespace WTelegram throw new Exception("Library was not compiled with OBFUSCATION symbol"); #endif } + if (_httpClient != null) + _reactorTask = Task.CompletedTask; else { endpoint = _dcSession?.EndPoint ?? GetDefaultEndpoint(out int defaultDc); @@ -930,16 +936,19 @@ namespace WTelegram _networkStream = _tcpClient.GetStream(); } - byte protocolId = (byte)(_paddedMode ? 0xDD : 0xEE); -#if OBFUSCATION - (_sendCtr, _recvCtr, preamble) = InitObfuscation(secret, protocolId, dcId); -#else - preamble = new byte[] { protocolId, protocolId, protocolId, protocolId }; -#endif - await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token); - _dcSession.Salts?.Remove(DateTime.MaxValue); - _reactorTask = Reactor(_networkStream, _cts); + if (_networkStream != null) + { + byte protocolId = (byte)(_paddedMode ? 0xDD : 0xEE); +#if OBFUSCATION + (_sendCtr, _recvCtr, preamble) = InitObfuscation(secret, protocolId, dcId); +#else + preamble = new byte[] { protocolId, protocolId, protocolId, protocolId }; +#endif + await _networkStream.WriteAsync(preamble, 0, preamble.Length, _cts.Token); + + _reactorTask = Reactor(_networkStream, _cts); + } _sendSemaphore.Release(); try @@ -947,7 +956,7 @@ namespace WTelegram if (_dcSession.AuthKeyID == 0) await CreateAuthorizationKey(this, _dcSession); - var keepAliveTask = KeepAlive(_cts.Token); + if (_networkStream != null) _ = KeepAlive(_cts.Token); if (quickResume && _dcSession.Layer == Layer.Version && _dcSession.DataCenter != null && _session.MainDC != 0) TLConfig = new Config { this_dc = _session.MainDC, dc_options = _session.DcOptions }; else @@ -1467,10 +1476,11 @@ namespace WTelegram int frameLength = (int)memStream.Length; BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value #if OBFUSCATION - _sendCtr.EncryptDecrypt(buffer, frameLength); + _sendCtr?.EncryptDecrypt(buffer, frameLength); #endif - await _networkStream.WriteAsync(buffer, 0, frameLength); + var sending = SendFrame(buffer, frameLength); _lastSentMsg = msg; + await sending; } finally { @@ -1478,6 +1488,24 @@ namespace WTelegram } } + private async Task SendFrame(byte[] buffer, int frameLength) + { + if (_httpClient == null) + await _networkStream.WriteAsync(buffer, 0, frameLength); + else + { + var endpoint = _dcSession?.EndPoint ?? GetDefaultEndpoint(out _); + var content = new ByteArrayContent(buffer, 4, frameLength - 4); + var response = await _httpClient.PostAsync($"http://{endpoint}/api", content); + if (response.StatusCode != HttpStatusCode.OK) + throw new RpcException((int)response.StatusCode, TransportError((int)response.StatusCode)); + var data = await response.Content.ReadAsByteArrayAsync(); + var obj = ReadFrame(data, data.Length); + if (obj != null) + await HandleMessageAsync(obj); + } + } + internal async Task InvokeBare(IMethod request) { if (_bareRpc != null) throw new WTException("A bare request is already undergoing"); @@ -1501,6 +1529,12 @@ namespace WTelegram retry: var rpc = new Rpc { type = typeof(T) }; await SendAsync(query, true, rpc); + if (_httpClient != null && !rpc.Task.IsCompleted) + { + await SendAsync(new HttpWait { max_delay = 30, wait_after = 10, max_wait = 1000 * PingInterval }, true); + if (!rpc.Task.IsCompleted) rpc.tcs.TrySetException(new RpcException(417, "Missing RPC response via HTTP")); + } + var result = await rpc.Task; switch (result) {