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)
{