using System.Text.Json; using Lagrange.Core.Common; using Lagrange.Core.Internal.Context.Attributes; using Lagrange.Core.Internal.Event.EventArg; using Lagrange.Core.Internal.Event.Protocol; using Lagrange.Core.Internal.Event.Protocol.Login; using Lagrange.Core.Internal.Event.Protocol.System; using Lagrange.Core.Internal.Packets.Login.NTLogin; using Lagrange.Core.Internal.Packets.Login.WtLogin.Entity; using Lagrange.Core.Internal.Service; using Lagrange.Core.Utility.Crypto; using Lagrange.Core.Utility.Network; // ReSharper disable AsyncVoidLambda namespace Lagrange.Core.Internal.Context.Logic.Implementation; [EventSubscribe(typeof(TransEmpEvent))] [EventSubscribe(typeof(LoginEvent))] [EventSubscribe(typeof(KickNTEvent))] [BusinessLogic("WtExchangeLogic", "Manage the online task of the Bot")] internal class WtExchangeLogic : LogicBase { private const string Tag = nameof(WtExchangeLogic); private readonly TaskCompletionSource _qrCodeTask; private readonly TaskCompletionSource _unusualTask; private TaskCompletionSource<(string, string)>? _captchaTask; private const string Interface = "https://ntlogin.qq.com/qr/getFace"; private const string QueryEvent = "wtlogin.trans_emp CMD0x12"; internal WtExchangeLogic(ContextCollection collection) : base(collection) { _qrCodeTask = new TaskCompletionSource(); _unusualTask = new TaskCompletionSource(); } public override async Task Incoming(ProtocolEvent e) { switch (e) { case KickNTEvent kick: Collection.Log.LogFatal(Tag, $"KickNTEvent: {kick.Tag}: {kick.Message}"); Collection.Log.LogFatal(Tag, "Bot will be offline in 5 seconds..."); await Task.Delay(5000); Collection.Invoker.PostEvent(new BotOfflineEvent()); // TODO: Fill in the reason of offline Collection.Scheduler.Dispose(); break; } } /// /// 1. resolve wtlogin.trans_emp CMD0x31 packet /// 2. Schedule wtlogin.trans_emp CMD0x12 Task /// public async Task FetchQrCode() { Collection.Log.LogInfo(Tag, "Connecting Servers..."); if (!await Collection.Socket.Connect()) return null; Collection.Scheduler.Interval("Heartbeat.Alive", 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create())); var transEmp = TransEmpEvent.Create(TransEmpEvent.State.FetchQrCode); var result = await Collection.Business.SendEvent(transEmp); if (result.Count != 0) { var @event = (TransEmpEvent)result[0]; Collection.Keystore.Session.QrString = @event.QrSig; Collection.Keystore.Session.QrSign = @event.Signature; Collection.Keystore.Session.QrUrl = @event.Url; Collection.Log.LogInfo(Tag, $"QrCode Fetched, Expiration: {@event.Expiration} seconds"); return @event.QrCode; } return null; } public Task LoginByQrCode() { Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryQrCodeState()); return _qrCodeTask.Task; } public async Task LoginByPassword() { if (!Collection.Socket.Connected) // if socket not connected, try to connect { if (!await Collection.Socket.Connect()) return false; Collection.Scheduler.Interval("Heartbeat.Alive", 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create())); } if (Collection.Keystore.Session.ExchangeKey == null) { if (!await KeyExchange()) { Collection.Log.LogInfo(Tag, "Key Exchange Failed, please try again later"); return false; } } if (Collection.Keystore.Session.TempPassword != null) // try EasyLogin { Collection.Log.LogInfo(Tag, "Trying to Login by EasyLogin..."); var easyLoginEvent = EasyLoginEvent.Create(); var easyLoginResult = await Collection.Business.SendEvent(easyLoginEvent); if (easyLoginResult.Count != 0) { switch ((LoginCommon.Error)easyLoginResult[0].ResultCode) { case LoginCommon.Error.Success: { Collection.Log.LogInfo(Tag, "Login Success"); await BotOnline(); return true; } case LoginCommon.Error.UnusualVerify: { Collection.Log.LogInfo(Tag, "Login Success, but need to verify"); if (!await FetchUnusual()) { Collection.Log.LogInfo(Tag, "Fetch unusual state failed"); return false; } Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryUnusualState()); bool result = await _unusualTask.Task; if (result) await BotOnline(); return result; } default: { Collection.Log.LogWarning(Tag, "Fast Login Failed, trying to Login by Password..."); Collection.Keystore.Session.TempPassword = null; // clear temp password return await LoginByPassword(); // try password login } } } } else { Collection.Log.LogInfo(Tag, "Trying to Login by Password..."); var passwordLoginEvent = PasswordLoginEvent.Create(); var passwordLoginResult = await Collection.Business.SendEvent(passwordLoginEvent); if (passwordLoginResult.Count != 0) { var @event = (PasswordLoginEvent)passwordLoginResult[0]; switch ((LoginCommon.Error)@event.ResultCode) { case LoginCommon.Error.Success: { Collection.Log.LogInfo(Tag, "Login Success"); await BotOnline(); return true; } case LoginCommon.Error.UnusualVerify: { Collection.Log.LogInfo(Tag, "Login Success, but need to verify"); await FetchUnusual(); Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryUnusualState()); return true; } case LoginCommon.Error.CaptchaVerify: { Collection.Log.LogInfo(Tag, "Login Success, but captcha is required, please follow the link from event"); if (Collection.Keystore.Session.CaptchaUrl != null) { var captchaEvent = new BotCaptchaEvent(Collection.Keystore.Session.CaptchaUrl); Collection.Invoker.PostEvent(captchaEvent); string aid = Collection.Keystore.Session.CaptchaUrl.Split("&sid=")[1].Split("&")[0]; _captchaTask = new TaskCompletionSource<(string, string)>(); var (ticket, randStr) = await _captchaTask.Task; Collection.Keystore.Session.Captcha = new ValueTuple(ticket, randStr, aid); return await LoginByPassword(); } Collection.Log.LogInfo(Tag, "Captcha Url is null, please try again later"); return false; } default: { Collection.Log.LogWarning(Tag, @event is { Message: not null, Tag: not null } ? $"Login Failed: {(LoginCommon.Error)@event.ResultCode} | {@event.Tag}: {@event.Message}" : $"Login Failed: {(LoginCommon.Error)@event.ResultCode}"); Collection.Invoker.Dispose(); return false; } } } } return false; } private async Task KeyExchange() { var keyExchangeEvent = KeyExchangeEvent.Create(); var exchangeResult = await Collection.Business.SendEvent(keyExchangeEvent); if (exchangeResult.Count != 0) { Collection.Log.LogInfo(Tag, "Key Exchange successfully!"); return true; } return false; } private async Task DoWtLogin() { Collection.Log.LogInfo(Tag, "Doing Login..."); Collection.Keystore.Session.Sequence = 0; Collection.Keystore.SecpImpl = new EcdhImpl(EcdhImpl.CryptMethod.Secp192K1); var loginEvent = LoginEvent.Create(); var result = await Collection.Business.SendEvent(loginEvent); if (result.Count != 0) { var @event = (LoginEvent)result[0]; if (@event.ResultCode == 0) { Collection.Log.LogInfo(Tag, "Login Success"); Collection.Keystore.Info = new BotKeystore.BotInfo(@event.Age, @event.Sex, @event.Name); Collection.Log.LogInfo(Tag, Collection.Keystore.Info.ToString()); await BotOnline(); return true; } Collection.Log.LogFatal(Tag, $"Login failed: {@event.ResultCode}"); Collection.Log.LogFatal(Tag, $"Tag: {@event.Tag}\nState: {@event.Message}"); } return false; } private async Task QueryQrCodeState() { if (Collection.Keystore.Session.QrString == null) { Collection.Log.LogFatal(Tag, "QrString is null, Please Fetch QrCode First"); _qrCodeTask.SetResult(false); return; } var request = new NTLoginHttpRequest { Appid = Collection.AppInfo.AppId, Qrsig = Collection.Keystore.Session.QrString, FaceUpdateTime = 0 }; var payload = JsonSerializer.SerializeToUtf8Bytes(request); var response = await Http.PostAsync(Interface, payload, "application/json"); var info = JsonSerializer.Deserialize(response); if (info != null) Collection.Keystore.Uin = info.Uin; var transEmp = TransEmpEvent.Create(TransEmpEvent.State.QueryResult); var result = await Collection.Business.SendEvent(transEmp); if (result.Count != 0) { var @event = (TransEmpEvent)result[0]; var state = (TransEmp12.State)@event.ResultCode; Collection.Log.LogInfo(Tag, $"QrCode State Queried: {state} Uin: {Collection.Keystore.Uin}"); switch (state) { case TransEmp12.State.Confirmed: { Collection.Log.LogInfo(Tag, "QrCode Confirmed, Logging in with A1 sig..."); Collection.Scheduler.Cancel(QueryEvent); // cancel query task if (@event.TgtgtKey != null) { Collection.Keystore.Stub.TgtgtKey = @event.TgtgtKey; Collection.Keystore.Session.TempPassword = @event.TempPassword; Collection.Keystore.Session.NoPicSig = @event.NoPicSig; _qrCodeTask.SetResult(await DoWtLogin()); } break; } case TransEmp12.State.CodeExpired: { Collection.Log.LogWarning(Tag, "QrCode Expired, Please Fetch QrCode Again"); Collection.Scheduler.Cancel(QueryEvent); Collection.Scheduler.Dispose(); _qrCodeTask.SetResult(false); return; } case TransEmp12.State.Canceled: { Collection.Log.LogWarning(Tag, "QrCode Canceled, Please Fetch QrCode Again"); Collection.Scheduler.Cancel(QueryEvent); Collection.Scheduler.Dispose(); _qrCodeTask.SetResult(false); return; } case TransEmp12.State.WaitingForConfirm: case TransEmp12.State.WaitingForScan: default: break; } } } private async Task BotOnline() { var onlineEvent = new BotOnlineEvent(); Collection.Invoker.PostEvent(onlineEvent); var registerEvent = StatusRegisterEvent.Create(); var registerResponse = await Collection.Business.SendEvent(registerEvent); var heartbeatDelegate = new Action(async () => await Collection.Business.PushEvent(SsoAliveEvent.Create())); Collection.Log.LogInfo(Tag, $"Register Status: {((StatusRegisterEvent)registerResponse[0]).Message}"); Collection.Scheduler.Interval("SsoHeartBeat", (int)(4.5 * 60 * 1000), heartbeatDelegate); await Collection.Business.PushEvent(InfoSyncEvent.Create()); } private async Task FetchUnusual() { var transEmp = TransEmpEvent.Create(TransEmpEvent.State.FetchQrCode); var result = await Collection.Business.SendEvent(transEmp); if (result.Count != 0) { Collection.Log.LogInfo(Tag, "Confirmation Request Send"); return true; } return false; } private async Task QueryUnusualState() { var transEmp = TransEmpEvent.Create(TransEmpEvent.State.QueryResult); var result = await Collection.Business.SendEvent(transEmp); if (result.Count != 0) { var @event = (TransEmpEvent)result[0]; var state = (TransEmp12.State)@event.ResultCode; Collection.Log.LogInfo(Tag, $"Confirmation State Queried: {state}"); switch (state) { case TransEmp12.State.Confirmed: { Collection.Log.LogInfo(Tag, "Verification Confirmed, Logging in Unusual Login Service..."); Collection.Scheduler.Cancel(QueryEvent); // cancel query task if (@event.TempPassword != null) Collection.Keystore.Session.TempPassword = @event.TempPassword; _unusualTask.SetResult(await DoUnusualEasyLogin()); break; } case TransEmp12.State.CodeExpired: { Collection.Log.LogWarning(Tag, "Verification Expired, Please Login Again"); Collection.Scheduler.Cancel(QueryEvent); Collection.Scheduler.Dispose(); _unusualTask.SetResult(false); break; } case TransEmp12.State.Canceled: { Collection.Log.LogWarning(Tag, "Verification Canceled, Please Login Again"); Collection.Scheduler.Cancel(QueryEvent); Collection.Scheduler.Dispose(); _unusualTask.SetResult(false); break; } case TransEmp12.State.WaitingForConfirm: default: break; } } } private async Task DoUnusualEasyLogin() { Collection.Log.LogInfo(Tag, "Trying to Login by EasyLogin..."); var unusualEvent = UnusualEasyLoginEvent.Create(); var result = await Collection.Business.SendEvent(unusualEvent); return result.Count != 0 && ((UnusualEasyLoginEvent)result[0]).Success; } public bool SubmitCaptcha(string ticket, string randStr) => _captchaTask?.TrySetResult((ticket, randStr)) ?? false; }