diff --git a/README.md b/README.md index 9a6e007..ac91d31 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ ## *_Telegram Client API library written 100% in C# and .NET_* +**THIS IS A FORK OF `WTelegramClient` ALL CREDITS GO TO https://github.com/wiz0u/WTelegramClient** + This library allows you to connect to Telegram and control a user programmatically (or a bot, but [WTelegramBot](https://www.nuget.org/packages/WTelegramBot) is much easier for that). All the Telegram Client APIs (MTProto) are supported so you can do everything the user could do with a full Telegram GUI client. diff --git a/src/Client.cs b/src/Client.cs index 5bb497f..7cee12c 100644 --- a/src/Client.cs +++ b/src/Client.cs @@ -97,18 +97,13 @@ namespace WTelegram }) { } - public Client(Func configProvider, byte[] startSession, Action saveSession) - : this(configProvider, new ActionStore(startSession, saveSession)) { } - /// Welcome to WTelegramClient! 🙂 /// Config callback, is queried for: api_id, api_hash, session_pathname - /// if specified, must support initial Length & Read() of a session, then calls to Write() the updated session. Other calls can be ignored - public Client(Func configProvider = null, Stream sessionStore = null) + public Client(Func configProvider = null, string json = null) { _config = configProvider ?? DefaultConfigOrAsk; - var session_key = _config("session_key") ?? (_apiHash = Config("api_hash")); - sessionStore ??= new SessionStore(Config("session_pathname")); - _session = Session.LoadOrCreate(sessionStore, Convert.FromHexString(session_key)); + _apiHash = Config("api_hash"); + _session = Session.LoadOrCreate(json); if (_session.ApiId == 0) _session.ApiId = int.Parse(Config("api_id")); if (_session.MainDC != 0) _session.DCSessions.TryGetValue(_session.MainDC, out _dcSession); _dcSession ??= new() { Id = Helpers.RandomLong() }; diff --git a/src/Session.cs b/src/Session.cs index 86a0fec..4aaae30 100644 --- a/src/Session.cs +++ b/src/Session.cs @@ -1,17 +1,14 @@ using System; -using System.Buffers.Binary; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net; -using System.Security.Cryptography; using System.Text.Json; // Don't change this code to lower the security. It's following Telegram security recommendations https://corefork.telegram.org/mtproto/description namespace WTelegram { - internal sealed partial class Session : IDisposable + public sealed partial class Session : IDisposable { public int ApiId; public long UserId; @@ -81,127 +78,82 @@ namespace WTelegram public DateTime SessionStart => _sessionStart; private readonly DateTime _sessionStart = DateTime.UtcNow; - private readonly SHA256 _sha256 = SHA256.Create(); private Stream _store; - private byte[] _reuseKey; // used only if AES Encryptor.CanReuseTransform = false (Mono) - private byte[] _encrypted = new byte[16]; - private ICryptoTransform _encryptor; - private Utf8JsonWriter _jsonWriter; - private readonly MemoryStream _jsonStream = new(4096); public void Dispose() { - _sha256.Dispose(); - _store.Dispose(); - _encryptor.Dispose(); - _jsonWriter.Dispose(); - _jsonStream.Dispose(); + _store?.Dispose(); } - internal static Session LoadOrCreate(Stream store, byte[] rgbKey) + public static Session LoadOrCreate(string json) { - using var aes = Aes.Create(); - Session session = null; - try - { - var length = (int)store.Length; - if (length > 0) - { - var input = new byte[length]; - if (store.Read(input, 0, length) != length) - throw new WTException($"Can't read session block ({store.Position}, {length})"); - using var sha256 = SHA256.Create(); - using var decryptor = aes.CreateDecryptor(rgbKey, input[0..16]); - var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16); - if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32])) - throw new WTException("Integrity check failed in session loading"); - session = JsonSerializer.Deserialize(utf8Json.AsSpan(32), Helpers.JsonOptions); - Helpers.Log(2, "Loaded previous session"); - } - session ??= new Session(); - session._store = store; - Encryption.RNG.GetBytes(session._encrypted, 0, 16); - session._encryptor = aes.CreateEncryptor(rgbKey, session._encrypted); - if (!session._encryptor.CanReuseTransform) session._reuseKey = rgbKey; - session._jsonWriter = new Utf8JsonWriter(session._jsonStream, default); - return session; - } - catch (Exception ex) - { - store.Dispose(); - throw new WTException($"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session", ex); - } + if (string.IsNullOrEmpty(json)) return new Session(); + var ret = JsonSerializer.Deserialize(json, Helpers.JsonOptions); + return ret; + // using var aes = Aes.Create(); + // Session session = null; + // try + // { + // var length = (int)store.Length; + // if (length > 0) + // { + // var input = new byte[length]; + // if (store.Read(input, 0, length) != length) + // throw new WTException($"Can't read session block ({store.Position}, {length})"); + // using var sha256 = SHA256.Create(); + // using var decryptor = aes.CreateDecryptor(rgbKey, input[0..16]); + // var utf8Json = decryptor.TransformFinalBlock(input, 16, input.Length - 16); + // if (!sha256.ComputeHash(utf8Json, 32, utf8Json.Length - 32).SequenceEqual(utf8Json[0..32])) + // throw new WTException("Integrity check failed in session loading"); + // session = JsonSerializer.Deserialize(utf8Json.AsSpan(32), Helpers.JsonOptions); + // Helpers.Log(2, "Loaded previous session"); + // } + // session ??= new Session(); + // session._store = store; + // Encryption.RNG.GetBytes(session._encrypted, 0, 16); + // session._encryptor = aes.CreateEncryptor(rgbKey, session._encrypted); + // if (!session._encryptor.CanReuseTransform) session._reuseKey = rgbKey; + // session._jsonWriter = new Utf8JsonWriter(session._jsonStream, default); + // return session; + // } + // catch (Exception ex) + // { + // store.Dispose(); + // throw new WTException($"Exception while reading session file: {ex.Message}\nUse the correct api_hash/id/key, or delete the file to start a new session", ex); + // } } - internal void Save() // must be called with lock(session) + public string Save() // must be called with lock(session) { - JsonSerializer.Serialize(_jsonWriter, this, Helpers.JsonOptions); - var utf8Json = _jsonStream.GetBuffer(); - var utf8JsonLen = (int)_jsonStream.Position; - int encryptedLen = 64 + (utf8JsonLen & ~15); - lock (_store) // while updating _encrypted buffer and writing to store - { - if (encryptedLen > _encrypted.Length) - Array.Copy(_encrypted, _encrypted = new byte[encryptedLen + 256], 16); - _encryptor.TransformBlock(_sha256.ComputeHash(utf8Json, 0, utf8JsonLen), 0, 32, _encrypted, 16); - _encryptor.TransformBlock(utf8Json, 0, encryptedLen - 64, _encrypted, 48); - _encryptor.TransformFinalBlock(utf8Json, encryptedLen - 64, utf8JsonLen & 15).CopyTo(_encrypted, encryptedLen - 16); - if (!_encryptor.CanReuseTransform) // under Mono, AES encryptor is not reusable - using (var aes = Aes.Create()) - _encryptor = aes.CreateEncryptor(_reuseKey, _encrypted[0..16]); - try - { - _store.Position = 0; - _store.Write(_encrypted, 0, encryptedLen); - _store.SetLength(encryptedLen); - } - catch (Exception ex) - { - Helpers.Log(4, $"{_store} raised {ex}"); - } - } - _jsonStream.Position = 0; - _jsonWriter.Reset(); + var ret = JsonSerializer.Serialize(this, Helpers.JsonOptions); + return ret; + // JsonSerializer.Serialize(_jsonWriter, this, Helpers.JsonOptions); + // var utf8Json = _jsonStream.GetBuffer(); + // var utf8JsonLen = (int)_jsonStream.Position; + // int encryptedLen = 64 + (utf8JsonLen & ~15); + // lock (_store) // while updating _encrypted buffer and writing to store + // { + // if (encryptedLen > _encrypted.Length) + // Array.Copy(_encrypted, _encrypted = new byte[encryptedLen + 256], 16); + // _encryptor.TransformBlock(_sha256.ComputeHash(utf8Json, 0, utf8JsonLen), 0, 32, _encrypted, 16); + // _encryptor.TransformBlock(utf8Json, 0, encryptedLen - 64, _encrypted, 48); + // _encryptor.TransformFinalBlock(utf8Json, encryptedLen - 64, utf8JsonLen & 15).CopyTo(_encrypted, encryptedLen - 16); + // if (!_encryptor.CanReuseTransform) // under Mono, AES encryptor is not reusable + // using (var aes = Aes.Create()) + // _encryptor = aes.CreateEncryptor(_reuseKey, _encrypted[0..16]); + // try + // { + // _store.Position = 0; + // _store.Write(_encrypted, 0, encryptedLen); + // _store.SetLength(encryptedLen); + // } + // catch (Exception ex) + // { + // Helpers.Log(4, $"{_store} raised {ex}"); + // } + // } + // _jsonStream.Position = 0; + // _jsonWriter.Reset(); } } - - internal sealed class SessionStore : FileStream // This class is designed to be high-performance and failure-resilient with Writes (but when you're Andrei, you can't understand that) - { - public override long Length { get; } - public override long Position { get => base.Position; set { } } - public override void SetLength(long value) { } - private readonly byte[] _header = new byte[8]; - private int _nextPosition = 8; - - public SessionStore(string pathname) - : base(pathname, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 1) // no in-app buffering - { - if (base.Read(_header, 0, 8) == 8) - { - var position = BinaryPrimitives.ReadInt32LittleEndian(_header); - var length = BinaryPrimitives.ReadInt32LittleEndian(_header.AsSpan(4)); - base.Position = position; - Length = length; - _nextPosition = position + length; - } - } - - public override void Write(byte[] buffer, int offset, int count) - { - if (_nextPosition > count * 3) _nextPosition = 8; - base.Position = _nextPosition; - base.Write(buffer, offset, count); - BinaryPrimitives.WriteInt32LittleEndian(_header, _nextPosition); - BinaryPrimitives.WriteInt32LittleEndian(_header.AsSpan(4), count); - _nextPosition += count; - base.Position = 0; - base.Write(_header, 0, 8); - } - } - - internal sealed class ActionStore(byte[] initial, Action save) : MemoryStream(initial ?? []) - { - public override void Write(byte[] buffer, int offset, int count) => save(buffer[offset..(offset + count)]); - public override void SetLength(long value) { } - } } \ No newline at end of file diff --git a/src/WTelegramClient.csproj b/src/WTelegramClient.csproj index 3d71cd9..a7cc5f6 100644 --- a/src/WTelegramClient.csproj +++ b/src/WTelegramClient.csproj @@ -10,8 +10,8 @@ true snupkg true - WTelegramClient - 0.0.0 + JSONTelegramClient + 1.0.0 Wizou Telegram Client API (MTProto) library written 100% in C# and .NET Standard | Latest API layer: 186 @@ -24,10 +24,9 @@ $(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "% true https://github.com/wiz0u/WTelegramClient.git git - Telegram;MTProto;Client;Api;UserBot README.md $(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A")) - NETSDK1138;CS0419;CS1573;CS1591 + NETSDK1138;CS0419;CS1573;CS1591;CS0649 TRACE;OBFUSCATION;MTPG