Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
sisi0318 committed Jan 15, 2025
2 parents bcdcd74 + ea4474d commit 1e2a675
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 81 deletions.
7 changes: 3 additions & 4 deletions Lagrange.Core/Common/BotAppInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,16 @@ public class BotAppInfo
Os = "Linux",
Kernel = "Linux",
VendorOs = "linux",
CurrentVersion = "3.2.13-28788",
CurrentVersion = "3.2.15-30366",
MiscBitmap = 32764,
PtVersion = "2.0.0",
SsoVersion = 19,
PackageName = "com.tencent.qq",
WtLoginSdk = "nt.wtlogin.0.0.1",
AppId = 1600001615,
SubAppId = 537249787,
SubAppId = 537258424,
AppIdQrCode = 13697054,
AppClientVersion = 28788,

AppClientVersion = 30366,
MainSigMap = 169742560,
SubSigMap = 0,
NTLoginType = 1
Expand Down
20 changes: 13 additions & 7 deletions Lagrange.Core/Common/Interface/Api/BotExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,35 @@ public static class BotExt
/// <returns>return url and qrcode image in PNG format</returns>
public static Task<(string Url, byte[] QrCode)?> FetchQrCode(this BotContext bot)
=> bot.ContextCollection.Business.WtExchangeLogic.FetchQrCode();

/// <summary>
/// Use this method to login by QrCode, you should call <see cref="FetchQrCode"/> first
/// </summary>
public static Task LoginByQrCode(this BotContext bot, CancellationToken cancellationToken = default)
=> bot.ContextCollection.Business.WtExchangeLogic.LoginByQrCode(cancellationToken);

/// <summary>
/// Use this method to login by password, EasyLogin may be preformed if there is sig in <see cref="BotKeystore"/>
/// </summary>
public static Task<bool> LoginByPassword(this BotContext bot, CancellationToken cancellationToken = default)
=> bot.ContextCollection.Business.WtExchangeLogic.LoginByPassword(cancellationToken);

=> bot.ContextCollection.Business.WtExchangeLogic.LoginByEasy(true, cancellationToken);

/// <summary>
/// Use this method to login by easy, no fallback to password login/>
/// </summary>
public static Task<bool> LoginByEasy(this BotContext bot, CancellationToken cancellationToken = default)
=> bot.ContextCollection.Business.WtExchangeLogic.LoginByEasy(false, cancellationToken);

/// <summary>
/// Submit the captcha of the url given by the <see cref="EventInvoker.OnBotCaptchaEvent"/>
/// </summary>
/// <returns>Whether the captcha is submitted successfully</returns>
public static bool SubmitCaptcha(this BotContext bot, string ticket, string randStr)
=> bot.ContextCollection.Business.WtExchangeLogic.SubmitCaptcha(ticket, randStr);
public static Task<bool> SetNeedToConfirmSwitch(this BotContext bot, bool needToConfirm)

public static Task<bool> SetNeedToConfirmSwitch(this BotContext bot, bool needToConfirm)
=> bot.ContextCollection.Business.OperationLogic.SetNeedToConfirmSwitch(needToConfirm);

/// <summary>
/// Use this method to update keystore, so EasyLogin may be preformed next time by using this keystore
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion Lagrange.Core/Common/Interface/Api/OperationExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public static Task<bool> DeleteFriend(this BotContext bot, uint friendUin, bool
public static Task<bool> RequestFriend(this BotContext bot, uint targetUin, string question = "", string message = "")
=> bot.ContextCollection.Business.OperationLogic.RequestFriend(targetUin, question, message);

public static Task<bool> Like(this BotContext bot, uint targetUin, uint count = 1)
public static Task<string?> Like(this BotContext bot, uint targetUin, uint count = 1)
=> bot.ContextCollection.Business.OperationLogic.Like(targetUin, count);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,14 +491,15 @@ public async Task<bool> RequestFriend(uint targetUin, string question, string me
return events.Count != 0 && ((RequestFriendEvent)events[0]).ResultCode == 0;
}

public async Task<bool> Like(uint targetUin, uint count)
public async Task<string?> Like(uint targetUin, uint count)
{
var uid = await Collection.Business.CachingLogic.ResolveUid(null, targetUin);
if (uid == null) return false;
// var uid = await Collection.Business.CachingLogic.ResolveUid(null, targetUin);
// if (uid == null) return false;

var friendLikeEvent = FriendLikeEvent.Create(uid, count);
var friendLikeEvent = FriendLikeEvent.Create(targetUin, count);
var results = await Collection.Business.SendEvent(friendLikeEvent);
return results.Count != 0 && results[0].ResultCode == 0;
var res = (FriendLikeEvent)results[0];
return results.Count == 0 ? null : res.Error;
}

public async Task<bool> InviteGroup(uint targetGroupUin, Dictionary<uint, uint?> invitedUins)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class WtExchangeLogic : LogicBase
private const string Tag = nameof(WtExchangeLogic);

private readonly Timer _reLoginTimer;

private TaskCompletionSource<bool> _transEmpTask = new();
private TaskCompletionSource<(string, string)> _captchaTask = new();

Expand All @@ -50,7 +50,7 @@ public override async Task Incoming(ProtocolEvent e)
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(kick.Tag, kick.Message)); // TODO: Fill in the reason of offline
Collection.Scheduler.Dispose();
break;
Expand Down Expand Up @@ -80,13 +80,13 @@ private void OnCancellation()
Collection.Log.LogInfo(Tag, "Connecting Servers...");
if (!await Collection.Socket.Connect()) return null;
Collection.Scheduler.Interval(HeartbeatEvent, 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create()));

if (Collection.Keystore.Session.D2.Length != 0)
{
Collection.Log.LogWarning(Tag, "Invalid Session found, try to clean D2Key, D2 and TGT Token");
Collection.Keystore.ClearSession();
}

var transEmp = TransEmpEvent.Create(TransEmpEvent.State.FetchQrCode);
var result = await Collection.Business.SendEvent(transEmp);

Expand All @@ -96,7 +96,7 @@ private void OnCancellation()
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.Url, @event.QrCode);
}
Expand All @@ -107,7 +107,7 @@ public Task LoginByQrCode(CancellationToken cancellationToken)
{
Reset();
cancellationToken.Register(OnCancellation);

Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryTransEmpState(async @event =>
{
if (@event.TgtgtKey != null)
Expand All @@ -116,25 +116,27 @@ public Task LoginByQrCode(CancellationToken cancellationToken)
Collection.Keystore.Session.TempPassword = @event.TempPassword;
Collection.Keystore.Session.NoPicSig = @event.NoPicSig;
}

return await DoWtLogin();
}));

return _transEmpTask.Task;
}

public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
public Task<bool> LoginByPassword(CancellationToken token) => LoginByEasy(true, token);

public async Task<bool> LoginByEasy(bool easyFallbackToPassword, CancellationToken cancellationToken)
{
Reset();
cancellationToken.Register(OnCancellation);

if (!Collection.Socket.Connected) // if socket not connected, try to connect
{
{
if (!await Collection.Socket.Connect()) return false;
Collection.Scheduler.Interval(HeartbeatEvent, 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create()));
}

if (Collection.Keystore.Session.D2.Length > 0 && Collection.Keystore.Session.Tgt.Length > 0 &&
if (Collection.Keystore.Session.D2.Length > 0 && Collection.Keystore.Session.Tgt.Length > 0 &&
DateTime.Now - Collection.Keystore.Session.SessionDate < TimeSpan.FromDays(15))
{
Collection.Log.LogInfo(Tag, "Session has not expired, using session to login and register status");
Expand Down Expand Up @@ -185,7 +187,7 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
Collection.Log.LogInfo(Tag, "Fetch unusual state failed");
return false;
}

Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryTransEmpState(async e =>
{
if (e.TempPassword != null)
Expand All @@ -202,14 +204,14 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
default:
{
Collection.Log.LogWarning(Tag, $"Fast Login Failed with code {easyLoginResult[0].ResultCode}, trying to Login by Password...");

Collection.Keystore.Session.TempPassword = null; // clear temp password
return await LoginByPassword(cancellationToken); // try password login
return easyFallbackToPassword && await LoginByPassword(cancellationToken); // fallback to password
}
}
}
}
else
else if (easyFallbackToPassword)
{
Collection.Log.LogInfo(Tag, "Trying to Login by Password...");
var passwordLoginEvent = PasswordLoginEvent.Create();
Expand All @@ -235,19 +237,19 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
case LoginCommon.Error.CaptchaVerify:
{
Collection.Log.LogInfo(Tag, "Login 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];
var (ticket, randStr) = await _captchaTask.Task;
Collection.Keystore.Session.Captcha = new ValueTuple<string, string, string>(ticket, randStr, aid);

return await LoginByPassword(cancellationToken);
}

Collection.Log.LogInfo(Tag, "Captcha Url is null, please try again later");
return false;
}
Expand All @@ -257,7 +259,7 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
string? parameters = Collection.Keystore.Session.NewDeviceVerifyUrl;
if (parameters == null) return false;
var parsed = HttpUtility.ParseQueryString(parameters);

uint uin = Collection.Keystore.Uin;
string url = $"https://oidb.tim.qq.com/v3/oidbinterface/oidb_0xc9e_8?uid={uin}&getqrcode=1&sdkappid=39998&actype=2";
var request = new NTNewDeviceQrCodeRequest
Expand All @@ -274,15 +276,15 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
var response = await client.PostAsJsonAsync(url, request, cancellationToken);
var json = await response.Content.ReadFromJsonAsync<NTNewDeviceQrCodeResponse>(cancellationToken: cancellationToken);
if (json == null) return false;

var newDeviceEvent = new BotNewDeviceVerifyEvent(json.StrUrl, Array.Empty<byte>());
Collection.Invoker.PostEvent(newDeviceEvent);
Collection.Log.LogInfo(Tag, $"NewDeviceLogin Url: {json.StrUrl}");

string? original = HttpUtility.ParseQueryString(json.StrUrl.Split("?")[1])["str_url"];
if (original == null) return false;
Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () =>

Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () =>
{
var query = new NTNewDeviceQrCodeQuery
{
Expand All @@ -294,7 +296,7 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
if (!string.IsNullOrEmpty(responseJson?.StrNtSuccToken))
{
Collection.Scheduler.Cancel(QueryEvent); // cancel the event

Collection.Keystore.Session.TempPassword = Encoding.UTF8.GetBytes(responseJson.StrNtSuccToken);
_transEmpTask.SetResult(true);
client.Dispose();
Expand All @@ -304,23 +306,23 @@ public async Task<bool> LoginByPassword(CancellationToken cancellationToken)
Collection.Log.LogInfo(Tag, "NewDeviceLogin is waiting for scanning");
}
});

if (await _transEmpTask.Task)
{
Collection.Log.LogInfo(Tag, "Trying to Login by NewDeviceLogin...");
var newDeviceLogin = NewDeviceLoginEvent.Create();
_ = await Collection.Business.SendEvent(newDeviceLogin);
return await BotOnline();
}

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}");

return false;
}
}
Expand Down Expand Up @@ -351,7 +353,7 @@ private async Task<bool> DoWtLogin()
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];
Expand All @@ -366,7 +368,7 @@ private async Task<bool> DoWtLogin()
Collection.Log.LogFatal(Tag, $"Login failed: {@event.ResultCode}");
Collection.Log.LogFatal(Tag, $"Tag: {@event.Tag}\nState: {@event.Message}");
}

return false;
}

Expand All @@ -380,7 +382,7 @@ private async Task QueryTransEmpState(Func<TransEmpEvent, Task<bool>> callback)
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<NTLoginHttpResponse>(response);
Expand Down Expand Up @@ -421,7 +423,7 @@ private async Task QueryTransEmpState(Func<TransEmpEvent, Task<bool>> callback)
_transEmpTask.SetResult(false);
return;
}
case TransEmp12.State.WaitingForConfirm:
case TransEmp12.State.WaitingForConfirm:
case TransEmp12.State.WaitingForScan:
default:
break;
Expand All @@ -448,14 +450,14 @@ public async Task<bool> BotOnline(BotOnlineEvent.OnlineReason reason = BotOnline

var onlineEvent = new BotOnlineEvent(reason);
Collection.Invoker.PostEvent(onlineEvent);

_reLoginTimer.Change(TimeSpan.FromDays(15), TimeSpan.FromDays(15));
Collection.Log.LogInfo(Tag, "AutoReLogin Enabled, session would be refreshed in 15 days period");
}

return result;
}

return false;
}

Expand Down Expand Up @@ -489,11 +491,11 @@ private async Task ReLogin()
Collection.Log.LogInfo(Tag, "A2 is null, abort");
return;
}

var d2 = Collection.Keystore.Session.D2;
var d2Key = Collection.Keystore.Session.D2Key;
var tgt = Collection.Keystore.Session.Tgt; // save the original state

Collection.Socket.Disconnect();
Collection.Keystore.ClearSession();
await Collection.Socket.Connect();
Expand All @@ -508,8 +510,8 @@ private async Task ReLogin()
if ((LoginCommon.Error)result.ResultCode == LoginCommon.Error.Success)
{
Collection.Log.LogInfo(Tag, "Login Success, try to register services");
if (await BotOnline(BotOnlineEvent.OnlineReason.Reconnect)) return;
if (await BotOnline(BotOnlineEvent.OnlineReason.Reconnect)) return;

Collection.Log.LogInfo(Tag, "Re-login failed, please refresh manually");
}
}
Expand All @@ -518,13 +520,13 @@ private async Task ReLogin()
{
Collection.Log.LogInfo(Tag, "Key Exchange Failed, trying to online, please refresh manually");
}

Collection.Keystore.Session.D2 = d2;
Collection.Keystore.Session.D2Key = d2Key;
Collection.Keystore.Session.Tgt = tgt;

await BotOnline(BotOnlineEvent.OnlineReason.Reconnect);
}

public bool SubmitCaptcha(string ticket, string randStr) => _captchaTask.TrySetResult((ticket, randStr));
}
Loading

0 comments on commit 1e2a675

Please sign in to comment.