mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2025-12-06 06:52:01 +01:00
Support for connecting to Telegram via on-demand HTTP requests instead of permanent TCP connection: client.HttpMode (experimental)
This commit is contained in:
parent
4f9accdfc8
commit
a19db86c1d
|
|
@ -187,10 +187,10 @@ namespace WTelegram
|
||||||
/// <param name="entities">Text formatting entities for the caption. You can use <see cref="Markdown.MarkdownToEntities">MarkdownToEntities</see> to create these</param>
|
/// <param name="entities">Text formatting entities for the caption. You can use <see cref="Markdown.MarkdownToEntities">MarkdownToEntities</see> to create these</param>
|
||||||
/// <param name="schedule_date">UTC timestamp when the message should be sent</param>
|
/// <param name="schedule_date">UTC timestamp when the message should be sent</param>
|
||||||
/// <param name="videoUrlAsFile">Any <see cref="InputMediaDocumentExternal"/> URL pointing to a video should be considered as non-streamable</param>
|
/// <param name="videoUrlAsFile">Any <see cref="InputMediaDocumentExternal"/> URL pointing to a video should be considered as non-streamable</param>
|
||||||
/// <returns>The last of the media group messages, confirmed by Telegram</returns>
|
/// <returns>The media group messages as received by Telegram</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// * The caption/entities are set on the last media<br/>
|
/// * The caption/entities are set on the first media<br/>
|
||||||
/// * <see cref="InputMediaDocumentExternal"/> and <see cref="InputMediaPhotoExternal"/> are supported by downloading the file from the web via HttpClient and sending it to Telegram.
|
/// * <see cref="InputMediaDocumentExternal"/> and <see cref="InputMediaPhotoExternal"/> 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<br/>
|
/// WTelegramClient proxy settings don't apply to HttpClient<br/>
|
||||||
/// * You may run into errors if you mix, in the same album, photos and file documents having no thumbnails/video attributes
|
/// * You may run into errors if you mix, in the same album, photos and file documents having no thumbnails/video attributes
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
@ -28,8 +29,6 @@ namespace WTelegram
|
||||||
/// <summary>This event will be called when unsollicited updates/messages are sent by Telegram servers</summary>
|
/// <summary>This event will be called when unsollicited updates/messages are sent by Telegram servers</summary>
|
||||||
/// <remarks>Make your handler <see langword="async"/>, or return <see cref="Task.CompletedTask"/> or <see langword="null"/><br/>See <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ReactorError.cs?ts=4#L30">Examples/Program_ReactorError.cs</see> for how to use this<br/>or <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21">Examples/Program_ListenUpdate.cs</see> using the UpdateManager class instead</remarks>
|
/// <remarks>Make your handler <see langword="async"/>, or return <see cref="Task.CompletedTask"/> or <see langword="null"/><br/>See <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ReactorError.cs?ts=4#L30">Examples/Program_ReactorError.cs</see> for how to use this<br/>or <see href="https://github.com/wiz0u/WTelegramClient/blob/master/Examples/Program_ListenUpdates.cs?ts=4#L21">Examples/Program_ListenUpdate.cs</see> using the UpdateManager class instead</remarks>
|
||||||
public event Func<UpdatesBase, Task> OnUpdates;
|
public event Func<UpdatesBase, Task> 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<UpdatesBase, Task> OnUpdate { add { OnUpdates += value; } remove { OnUpdates -= value; } }
|
|
||||||
/// <summary>This event is called for other types of notifications (login states, reactor errors, ...)</summary>
|
/// <summary>This event is called for other types of notifications (login states, reactor errors, ...)</summary>
|
||||||
public event Func<IObject, Task> OnOther;
|
public event Func<IObject, Task> OnOther;
|
||||||
/// <summary>Use this handler to intercept Updates that resulted from your own API calls</summary>
|
/// <summary>Use this handler to intercept Updates that resulted from your own API calls</summary>
|
||||||
|
|
@ -67,6 +66,7 @@ namespace WTelegram
|
||||||
private Session.DCSession _dcSession;
|
private Session.DCSession _dcSession;
|
||||||
private TcpClient _tcpClient;
|
private TcpClient _tcpClient;
|
||||||
private Stream _networkStream;
|
private Stream _networkStream;
|
||||||
|
private HttpClient _httpClient;
|
||||||
private IObject _lastSentMsg;
|
private IObject _lastSentMsg;
|
||||||
private long _lastRecvMsgId;
|
private long _lastRecvMsgId;
|
||||||
private readonly List<long> _msgsToAck = [];
|
private readonly List<long> _msgsToAck = [];
|
||||||
|
|
@ -199,6 +199,10 @@ namespace WTelegram
|
||||||
|
|
||||||
public void DisableUpdates(bool disable = true) => _dcSession.DisableUpdates(disable);
|
public void DisableUpdates(bool disable = true) => _dcSession.DisableUpdates(disable);
|
||||||
|
|
||||||
|
/// <summary>Enable connecting to Telegram via on-demand HTTP requests instead of permanent TCP connection</summary>
|
||||||
|
/// <param name="httpClient">HttpClient to use. Leave <see langword="null"/> for a default one</param>
|
||||||
|
public void HttpMode(HttpClient httpClient = null) => _httpClient = httpClient ?? new();
|
||||||
|
|
||||||
/// <summary>Disconnect from Telegram <i>(shouldn't be needed in normal usage)</i></summary>
|
/// <summary>Disconnect from Telegram <i>(shouldn't be needed in normal usage)</i></summary>
|
||||||
/// <param name="resetUser">Forget about logged-in user</param>
|
/// <param name="resetUser">Forget about logged-in user</param>
|
||||||
/// <param name="resetSessions">Disconnect secondary sessions with other DCs</param>
|
/// <param name="resetSessions">Disconnect secondary sessions with other DCs</param>
|
||||||
|
|
@ -513,16 +517,16 @@ namespace WTelegram
|
||||||
return null;
|
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()
|
internal void CheckSalt()
|
||||||
{
|
{
|
||||||
lock (_session)
|
lock (_session)
|
||||||
|
|
@ -871,6 +875,8 @@ namespace WTelegram
|
||||||
throw new Exception("Library was not compiled with OBFUSCATION symbol");
|
throw new Exception("Library was not compiled with OBFUSCATION symbol");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
if (_httpClient != null)
|
||||||
|
_reactorTask = Task.CompletedTask;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
endpoint = _dcSession?.EndPoint ?? GetDefaultEndpoint(out int defaultDc);
|
endpoint = _dcSession?.EndPoint ?? GetDefaultEndpoint(out int defaultDc);
|
||||||
|
|
@ -930,16 +936,19 @@ namespace WTelegram
|
||||||
_networkStream = _tcpClient.GetStream();
|
_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);
|
_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();
|
_sendSemaphore.Release();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -947,7 +956,7 @@ namespace WTelegram
|
||||||
if (_dcSession.AuthKeyID == 0)
|
if (_dcSession.AuthKeyID == 0)
|
||||||
await CreateAuthorizationKey(this, _dcSession);
|
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)
|
if (quickResume && _dcSession.Layer == Layer.Version && _dcSession.DataCenter != null && _session.MainDC != 0)
|
||||||
TLConfig = new Config { this_dc = _session.MainDC, dc_options = _session.DcOptions };
|
TLConfig = new Config { this_dc = _session.MainDC, dc_options = _session.DcOptions };
|
||||||
else
|
else
|
||||||
|
|
@ -1467,10 +1476,11 @@ namespace WTelegram
|
||||||
int frameLength = (int)memStream.Length;
|
int frameLength = (int)memStream.Length;
|
||||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value
|
BinaryPrimitives.WriteInt32LittleEndian(buffer, frameLength - 4); // patch payload_len with correct value
|
||||||
#if OBFUSCATION
|
#if OBFUSCATION
|
||||||
_sendCtr.EncryptDecrypt(buffer, frameLength);
|
_sendCtr?.EncryptDecrypt(buffer, frameLength);
|
||||||
#endif
|
#endif
|
||||||
await _networkStream.WriteAsync(buffer, 0, frameLength);
|
var sending = SendFrame(buffer, frameLength);
|
||||||
_lastSentMsg = msg;
|
_lastSentMsg = msg;
|
||||||
|
await sending;
|
||||||
}
|
}
|
||||||
finally
|
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<T> InvokeBare<T>(IMethod<T> request)
|
internal async Task<T> InvokeBare<T>(IMethod<T> request)
|
||||||
{
|
{
|
||||||
if (_bareRpc != null) throw new WTException("A bare request is already undergoing");
|
if (_bareRpc != null) throw new WTException("A bare request is already undergoing");
|
||||||
|
|
@ -1501,6 +1529,12 @@ namespace WTelegram
|
||||||
retry:
|
retry:
|
||||||
var rpc = new Rpc { type = typeof(T) };
|
var rpc = new Rpc { type = typeof(T) };
|
||||||
await SendAsync(query, true, rpc);
|
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;
|
var result = await rpc.Task;
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue