mirror of
https://github.com/wiz0u/WTelegramClient.git
synced 2026-01-11 03:00:05 +01:00
feat: use json file
This commit is contained in:
parent
15b6346d2a
commit
729c2b694d
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -97,18 +97,13 @@ namespace WTelegram
|
|||
})
|
||||
{ }
|
||||
|
||||
public Client(Func<string, string> configProvider, byte[] startSession, Action<byte[]> saveSession)
|
||||
: this(configProvider, new ActionStore(startSession, saveSession)) { }
|
||||
|
||||
/// <summary>Welcome to WTelegramClient! 🙂</summary>
|
||||
/// <param name="configProvider">Config callback, is queried for: <b>api_id</b>, <b>api_hash</b>, <b>session_pathname</b></param>
|
||||
/// <param name="sessionStore">if specified, must support initial Length & Read() of a session, then calls to Write() the updated session. Other calls can be ignored</param>
|
||||
public Client(Func<string, string> configProvider = null, Stream sessionStore = null)
|
||||
public Client(Func<string, string> 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() };
|
||||
|
|
|
|||
182
src/Session.cs
182
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<Session>(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<Session>(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<Session>(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<byte[]> save) : MemoryStream(initial ?? [])
|
||||
{
|
||||
public override void Write(byte[] buffer, int offset, int count) => save(buffer[offset..(offset + count)]);
|
||||
public override void SetLength(long value) { }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<PackageId>WTelegramClient</PackageId>
|
||||
<Version>0.0.0</Version>
|
||||
<PackageId>JSONTelegramClient</PackageId>
|
||||
<Version>1.0.0</Version>
|
||||
<Authors>Wizou</Authors>
|
||||
<Description>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(" ", "%
|
|||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/wiz0u/WTelegramClient.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>Telegram;MTProto;Client;Api;UserBot</PackageTags>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageReleaseNotes>$(ReleaseNotes.Replace("|", "%0D%0A").Replace(" - ","%0D%0A- ").Replace(" ", "%0D%0A%0D%0A"))</PackageReleaseNotes>
|
||||
<NoWarn>NETSDK1138;CS0419;CS1573;CS1591</NoWarn>
|
||||
<NoWarn>NETSDK1138;CS0419;CS1573;CS1591;CS0649</NoWarn>
|
||||
<DefineConstants>TRACE;OBFUSCATION;MTPG</DefineConstants>
|
||||
<!--<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>-->
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
Loading…
Reference in a new issue