From 462fdbbbaed6e2afcad80c36f4e14cde9674852a Mon Sep 17 00:00:00 2001 From: Linwenxuan04 Date: Mon, 7 Oct 2024 09:48:57 -0400 Subject: [PATCH 1/4] [All] Add CancellationToken Support to Login --- Lagrange.Core/Common/Interface/Api/BotExt.cs | 8 +-- .../Logic/Implementation/WtExchangeLogic.cs | 55 ++++++++++++------- Lagrange.OneBot/LagrangeApp.cs | 4 +- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/Lagrange.Core/Common/Interface/Api/BotExt.cs b/Lagrange.Core/Common/Interface/Api/BotExt.cs index 2fdea50cf..e709267ad 100644 --- a/Lagrange.Core/Common/Interface/Api/BotExt.cs +++ b/Lagrange.Core/Common/Interface/Api/BotExt.cs @@ -14,14 +14,14 @@ public static class BotExt /// /// Use this method to login by QrCode, you should call first /// - public static Task LoginByQrCode(this BotContext bot) - => bot.ContextCollection.Business.WtExchangeLogic.LoginByQrCode(); + public static Task LoginByQrCode(this BotContext bot, CancellationToken cancellationToken = default) + => bot.ContextCollection.Business.WtExchangeLogic.LoginByQrCode(cancellationToken); /// /// Use this method to login by password, EasyLogin may be preformed if there is sig in /// - public static Task LoginByPassword(this BotContext bot) - => bot.ContextCollection.Business.WtExchangeLogic.LoginByPassword(); + public static Task LoginByPassword(this BotContext bot, CancellationToken cancellationToken = default) + => bot.ContextCollection.Business.WtExchangeLogic.LoginByPassword(cancellationToken); /// /// Submit the captcha of the url given by the diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs index 79364be56..f2e7a7421 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs @@ -28,16 +28,17 @@ internal class WtExchangeLogic : LogicBase private readonly Timer _reLoginTimer; - private readonly TaskCompletionSource _transEmpTask; - private TaskCompletionSource<(string, string)>? _captchaTask; + private TaskCompletionSource _transEmpTask = new(); + private TaskCompletionSource<(string, string)> _captchaTask = new(); private const string Interface = "https://ntlogin.qq.com/qr/getFace"; private const string QueryEvent = "wtlogin.trans_emp CMD0x12"; + private const string HeartbeatEvent = "Heartbeat.Alive"; + private const string SsoHeartbeatEvent = "SsoHeartBeat"; internal WtExchangeLogic(ContextCollection collection) : base(collection) { - _transEmpTask = new TaskCompletionSource(); _reLoginTimer = new Timer(async _ => await ReLogin(), null, Timeout.Infinite, Timeout.Infinite); } @@ -56,6 +57,20 @@ public override async Task Incoming(ProtocolEvent e) } } + private void Reset() + { + _transEmpTask = new TaskCompletionSource(); + _captchaTask = new TaskCompletionSource<(string, string)>(); + } + + private void OnCancellation() + { + Collection.Scheduler.Cancel(QueryEvent); + Collection.Scheduler.Cancel(HeartbeatEvent); + _transEmpTask.TrySetException(new TaskCanceledException()); + _captchaTask.TrySetException(new TaskCanceledException()); + } + /// /// 1. resolve wtlogin.trans_emp CMD0x31 packet /// 2. Schedule wtlogin.trans_emp CMD0x12 Task @@ -64,7 +79,7 @@ public override async Task Incoming(ProtocolEvent e) { 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())); + Collection.Scheduler.Interval(HeartbeatEvent, 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create())); if (Collection.Keystore.Session.D2.Length != 0) { @@ -88,8 +103,11 @@ public override async Task Incoming(ProtocolEvent e) return null; } - public Task LoginByQrCode() + public Task LoginByQrCode(CancellationToken cancellationToken) { + Reset(); + cancellationToken.Register(OnCancellation); + Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryTransEmpState(async @event => { if (@event.TgtgtKey != null) @@ -105,12 +123,15 @@ public Task LoginByQrCode() return _transEmpTask.Task; } - public async Task LoginByPassword() + public async Task LoginByPassword(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("Heartbeat.Alive", 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create())); + 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 && @@ -183,7 +204,7 @@ public async Task LoginByPassword() 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(); // try password login + return await LoginByPassword(cancellationToken); // try password login } } } @@ -221,11 +242,10 @@ public async Task LoginByPassword() 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(); + return await LoginByPassword(cancellationToken); } Collection.Log.LogInfo(Tag, "Captcha Url is null, please try again later"); @@ -251,8 +271,8 @@ public async Task LoginByPassword() }; var client = new HttpClient(); - var response = await client.PostAsJsonAsync(url, request); - var json = await response.Content.ReadFromJsonAsync(); + var response = await client.PostAsJsonAsync(url, request, cancellationToken); + var json = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); if (json == null) return false; var newDeviceEvent = new BotNewDeviceVerifyEvent(json.StrUrl, Array.Empty()); @@ -269,8 +289,8 @@ public async Task LoginByPassword() Uint32Flag = 0, Token = Convert.ToBase64String(Encoding.UTF8.GetBytes(original)) }; - var resp = await client.PostAsJsonAsync(url, query); - var responseJson = await resp.Content.ReadFromJsonAsync(); + var resp = await client.PostAsJsonAsync(url, query, cancellationToken); + var responseJson = await resp.Content.ReadFromJsonAsync(cancellationToken: cancellationToken); if (!string.IsNullOrEmpty(responseJson?.StrNtSuccToken)) { Collection.Scheduler.Cancel(QueryEvent); // cancel the event @@ -301,7 +321,6 @@ public async Task LoginByPassword() ? $"Login Failed: {(LoginCommon.Error)@event.ResultCode} | {@event.Tag}: {@event.Message}" : $"Login Failed: {(LoginCommon.Error)@event.ResultCode}"); - Collection.Invoker.Dispose(); return false; } } @@ -390,7 +409,6 @@ private async Task QueryTransEmpState(Func> callback) { Collection.Log.LogWarning(Tag, "QrCode Expired, Please Fetch QrCode Again"); Collection.Scheduler.Cancel(QueryEvent); - Collection.Scheduler.Dispose(); _transEmpTask.SetResult(false); return; @@ -399,7 +417,6 @@ private async Task QueryTransEmpState(Func> callback) { Collection.Log.LogWarning(Tag, "QrCode Canceled, Please Fetch QrCode Again"); Collection.Scheduler.Cancel(QueryEvent); - Collection.Scheduler.Dispose(); _transEmpTask.SetResult(false); return; @@ -427,7 +444,7 @@ public async Task BotOnline(BotOnlineEvent.OnlineReason reason = BotOnline bool result = resp.Message.Contains("register success"); if (result) { - Collection.Scheduler.Interval("SsoHeartBeat", (int)(4.5 * 60 * 1000), heartbeatDelegate); + Collection.Scheduler.Interval(SsoHeartbeatEvent, (int)(4.5 * 60 * 1000), heartbeatDelegate); var onlineEvent = new BotOnlineEvent(reason); Collection.Invoker.PostEvent(onlineEvent); @@ -511,5 +528,5 @@ private async Task ReLogin() await BotOnline(BotOnlineEvent.OnlineReason.Reconnect); } - public bool SubmitCaptcha(string ticket, string randStr) => _captchaTask?.TrySetResult((ticket, randStr)) ?? false; + public bool SubmitCaptcha(string ticket, string randStr) => _captchaTask.TrySetResult((ticket, randStr)); } \ No newline at end of file diff --git a/Lagrange.OneBot/LagrangeApp.cs b/Lagrange.OneBot/LagrangeApp.cs index 4c8bb7b30..d67daf2ba 100644 --- a/Lagrange.OneBot/LagrangeApp.cs +++ b/Lagrange.OneBot/LagrangeApp.cs @@ -100,7 +100,7 @@ internal LagrangeApp(IHost host) Logger.LogInformation($"Please scan the QR code above, Url: {qrCode.Url}"); await File.WriteAllBytesAsync($"qr-{Instance.BotUin}.png", qrCode.QrCode ?? Array.Empty(), cancellationToken); - _ = Task.Run(Instance.LoginByQrCode, cancellationToken); + await Instance.LoginByQrCode(cancellationToken); } } else @@ -119,7 +119,7 @@ await Task.Run(() => }, cancellationToken); }; - _ = Task.Run(Instance.LoginByPassword, cancellationToken); + await Instance.LoginByPassword(cancellationToken); } } From 65ba880e62830670877134d358395fee0c9761b5 Mon Sep 17 00:00:00 2001 From: Linwenxuan04 Date: Mon, 7 Oct 2024 10:22:33 -0400 Subject: [PATCH 2/4] [Core] Implemented FetchFriendsRequests --- .../Common/Entity/BotFriendRequest.cs | 39 ++++++++++++++++ .../Common/Interface/Api/OperationExt.cs | 2 +- .../Logic/Implementation/OperationLogic.cs | 21 ++++++++- .../Event/System/FetchFriendsRequestsEvent.cs | 9 +++- .../OidbSvcTrpcTcp0x5CF_11Response.cs | 46 +++++++++++++++++++ .../System/FetchFriendsRequestsService.cs | 10 +++- 6 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 Lagrange.Core/Common/Entity/BotFriendRequest.cs create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x5CF_11Response.cs diff --git a/Lagrange.Core/Common/Entity/BotFriendRequest.cs b/Lagrange.Core/Common/Entity/BotFriendRequest.cs new file mode 100644 index 000000000..78dc00987 --- /dev/null +++ b/Lagrange.Core/Common/Entity/BotFriendRequest.cs @@ -0,0 +1,39 @@ +namespace Lagrange.Core.Common.Entity; + +[Serializable] +public class BotFriendRequest +{ + public BotFriendRequest(string targetUid, string sourceUid, uint eventState, string comment, string source, uint time) + { + TargetUid = targetUid; + SourceUid = sourceUid; + EventState = (State)eventState; + Comment = comment; + Source = source; + Time = DateTime.UnixEpoch.AddSeconds(time); + } + + public string TargetUid { get; set; } + + public uint TargetUin { get; set; } + + public string SourceUid { get; set; } + + public uint SourceUin { get; set; } + + public State EventState { get; set; } + + + public string Comment { get; set; } + + public string Source { get; set; } + + public DateTime Time { get; set; } + + public enum State + { + Pending = 1, + Disapproved = 2, + Approved = 3, + } +} \ No newline at end of file diff --git a/Lagrange.Core/Common/Interface/Api/OperationExt.cs b/Lagrange.Core/Common/Interface/Api/OperationExt.cs index 9daff4f5d..3900a4416 100644 --- a/Lagrange.Core/Common/Interface/Api/OperationExt.cs +++ b/Lagrange.Core/Common/Interface/Api/OperationExt.cs @@ -112,7 +112,7 @@ public static Task RecallFriendMessage(this BotContext bot, MessageChain c /// /// /// - public static Task?> FetchFriendRequests(this BotContext bot) + public static Task?> FetchFriendRequests(this BotContext bot) => bot.ContextCollection.Business.OperationLogic.FetchFriendRequests(); /// diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs index 47e04f765..5b018b05e 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs @@ -329,13 +329,30 @@ async Task ResolveUid(string? uid) } } - public async Task?> FetchFriendRequests() + public async Task?> FetchFriendRequests() { var fetchRequestsEvent = FetchFriendsRequestsEvent.Create(); var events = await Collection.Business.SendEvent(fetchRequestsEvent); if (events.Count == 0) return null; - return null; + var resolved = ((FetchFriendsRequestsEvent)events[0]).Requests; + foreach (var result in resolved) + { + var uins = await Task.WhenAll(ResolveUid(result.TargetUid), ResolveUid(result.SourceUid)); + result.TargetUin = uins[0]; + result.SourceUin = uins[1]; + } + + return resolved; + + async Task ResolveUid(string? uid) + { + if (uid == null) return 0; + + var fetchUidEvent = FetchUserInfoEvent.Create(uid); + var e = await Collection.Business.SendEvent(fetchUidEvent); + return e.Count == 0 ? 0 : ((FetchUserInfoEvent)e[0]).UserInfo.Uin; + } } public async Task GroupTransfer(uint groupUin, uint targetUin) diff --git a/Lagrange.Core/Internal/Event/System/FetchFriendsRequestsEvent.cs b/Lagrange.Core/Internal/Event/System/FetchFriendsRequestsEvent.cs index 4845fc4e5..ca83d1799 100644 --- a/Lagrange.Core/Internal/Event/System/FetchFriendsRequestsEvent.cs +++ b/Lagrange.Core/Internal/Event/System/FetchFriendsRequestsEvent.cs @@ -1,14 +1,21 @@ +using Lagrange.Core.Common.Entity; + namespace Lagrange.Core.Internal.Event.System; internal class FetchFriendsRequestsEvent : ProtocolEvent { + public List Requests { get; } = new(); + private FetchFriendsRequestsEvent() : base(true) { } - private FetchFriendsRequestsEvent(int resultCode) : base(resultCode) + private FetchFriendsRequestsEvent(int resultCode, List requests) : base(resultCode) { + Requests = requests; } public static FetchFriendsRequestsEvent Create() => new(); + + public static FetchFriendsRequestsEvent Result(int resultCode, List requests) => new(resultCode, requests); } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x5CF_11Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x5CF_11Response.cs new file mode 100644 index 000000000..96868d9e5 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x5CF_11Response.cs @@ -0,0 +1,46 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response; +#pragma warning disable CS8618 + +[ProtoContract] +internal class OidbSvcTrpcTcp0x5CF_11Response +{ + [ProtoMember(1)] public uint Field1 { get; set; } + + [ProtoMember(2)] public uint Field2 { get; set; } + + [ProtoMember(3)] public OidbSvcTrpcTcp0x5CF_11ResponseInfo Info { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x5CF_11ResponseInfo +{ + [ProtoMember(2)] public uint Field2 { get; set; } + + [ProtoMember(3)] public uint Count { get; set; } + + [ProtoMember(7)] public List Requests { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x5CF_11ResponseRequests +{ + [ProtoMember(1)] public string TargetUid { get; set; } + + [ProtoMember(2)] public string SourceUid { get; set; } + + [ProtoMember(3)] public uint State { get; set; } + + [ProtoMember(4)] public uint Timestamp { get; set; } + + [ProtoMember(5)] public string Comment { get; set; } + + [ProtoMember(6)] public string Source { get; set; } + + [ProtoMember(7)] public uint SourceId { get; set; } + + [ProtoMember(8)] public uint SubSourceId { get; set; } + + // 剩下的我也猜不出来了 谁猜出来了谁PR吧 +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/System/FetchFriendsRequestsService.cs b/Lagrange.Core/Internal/Service/System/FetchFriendsRequestsService.cs index 0ec7ce127..09ae4a0ab 100644 --- a/Lagrange.Core/Internal/Service/System/FetchFriendsRequestsService.cs +++ b/Lagrange.Core/Internal/Service/System/FetchFriendsRequestsService.cs @@ -1,9 +1,12 @@ using Lagrange.Core.Common; +using Lagrange.Core.Common.Entity; using Lagrange.Core.Internal.Event; using Lagrange.Core.Internal.Event.System; using Lagrange.Core.Internal.Packets.Service.Oidb; using Lagrange.Core.Internal.Packets.Service.Oidb.Request; +using Lagrange.Core.Internal.Packets.Service.Oidb.Response; using Lagrange.Core.Utility.Extension; +using ProtoBuf; namespace Lagrange.Core.Internal.Service.System; @@ -35,6 +38,11 @@ protected override bool Build(FetchFriendsRequestsEvent input, BotKeystore keyst protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, out FetchFriendsRequestsEvent output, out List? extraEvents) { - return base.Parse(input, keystore, appInfo, device, out output, out extraEvents); + var payload = Serializer.Deserialize>(input); + var requests = payload.Body.Info.Requests.Select(r => new BotFriendRequest(r.TargetUid, r.SourceUid, r.State, r.Comment, r.Source, r.Timestamp)).ToList(); + + extraEvents = null; + output = FetchFriendsRequestsEvent.Result((int)payload.ErrorCode, requests); + return true; } } \ No newline at end of file From ef74c5fe484269bc26c522abca838ea79e6f01b9 Mon Sep 17 00:00:00 2001 From: Linwenxuan04 Date: Mon, 7 Oct 2024 10:27:26 -0400 Subject: [PATCH 3/4] [Core] Add Build-only Grey --- .../Message/Component/Extra/GreyTipExtra.cs | 23 +++++++ Lagrange.Core/Message/Entity/GreyTipEntity.cs | 68 +++++++++++++++++++ Lagrange.Core/Message/MessageBuilder.cs | 8 +++ 3 files changed, 99 insertions(+) create mode 100644 Lagrange.Core/Internal/Packets/Message/Component/Extra/GreyTipExtra.cs create mode 100644 Lagrange.Core/Message/Entity/GreyTipEntity.cs diff --git a/Lagrange.Core/Internal/Packets/Message/Component/Extra/GreyTipExtra.cs b/Lagrange.Core/Internal/Packets/Message/Component/Extra/GreyTipExtra.cs new file mode 100644 index 000000000..8985e4a28 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Message/Component/Extra/GreyTipExtra.cs @@ -0,0 +1,23 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Message.Component.Extra; + +[ProtoContract] +internal class GreyTipExtra +{ + [ProtoMember(101)] public GreyTipExtraLayer1? Layer1 { get; set; } +} + +[ProtoContract] +internal class GreyTipExtraLayer1 +{ + [ProtoMember(1)] public GreyTipExtraInfo? Info { get; set; } +} + +[ProtoContract] +internal class GreyTipExtraInfo +{ + [ProtoMember(1)] public uint Type { get; set; } // 1 + + [ProtoMember(2)] public string Content { get; set; } = string.Empty; // "{\"gray_tip\":\"谨防兼职刷单、游戏交易、投资荐股、色情招嫖、仿冒公检法及客服人员的网络骗局,如有资金往来请谨慎操作。 \",\"object_type\":3,\"sub_type\":2,\"type\":4}" +} \ No newline at end of file diff --git a/Lagrange.Core/Message/Entity/GreyTipEntity.cs b/Lagrange.Core/Message/Entity/GreyTipEntity.cs new file mode 100644 index 000000000..ee9c4c7fe --- /dev/null +++ b/Lagrange.Core/Message/Entity/GreyTipEntity.cs @@ -0,0 +1,68 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Lagrange.Core.Internal.Packets.Message.Component.Extra; +using Lagrange.Core.Internal.Packets.Message.Element; +using Lagrange.Core.Internal.Packets.Message.Element.Implementation; +using Lagrange.Core.Utility.Extension; + +namespace Lagrange.Core.Message.Entity; + +public class GreyTipEntity : IMessageEntity +{ + public string GreyTip { get; } + + public GreyTipEntity(string greyTip) => GreyTip = greyTip; + + public GreyTipEntity() => GreyTip = string.Empty; + + IEnumerable IMessageEntity.PackElement() + { + var content = new GreyTipContent + { + GrayTip = GreyTip, + ObjectType = 3, + SubType = 2, + Type = 4 + }; + + var extra = new GreyTipExtra + { + Layer1 = new GreyTipExtraLayer1 + { + Info = new GreyTipExtraInfo { Content = JsonSerializer.Serialize(content), Type = 1 } + } + }; + + return new Elem[] + { + new() + { + GeneralFlags = new GeneralFlags + { + PbReserve = extra.Serialize().ToArray(), + } + } + }; + } + + IMessageEntity? IMessageEntity.UnpackElement(Elem elem) + { + throw new NotImplementedException(); + } + + public string ToPreviewString() + { + throw new NotImplementedException(); + } + + private class GreyTipContent + { + [JsonPropertyName("gray_tip")] public string GrayTip { get; set; } = string.Empty; + + [JsonPropertyName("object_type")] public uint ObjectType { get; set; } + + [JsonPropertyName("sub_type")] public uint SubType { get; set; } + + [JsonPropertyName("type")] public uint Type { get; set; } + } +} \ No newline at end of file diff --git a/Lagrange.Core/Message/MessageBuilder.cs b/Lagrange.Core/Message/MessageBuilder.cs index 4120ce0d8..971af07ba 100644 --- a/Lagrange.Core/Message/MessageBuilder.cs +++ b/Lagrange.Core/Message/MessageBuilder.cs @@ -353,6 +353,14 @@ public MessageBuilder MarketFace(string faceId, int tabId, string key, string su return this; } + public MessageBuilder GeryTip(string greyTip) + { + var greyTipEntity = new GreyTipEntity(greyTip); + _chain.Add(greyTipEntity); + + return this; + } + public MessageBuilder Add(IMessageEntity entity) { _chain.Add(entity); From efa35d050ee4bf0fdb7c0f026cf95b01bfc5f3e6 Mon Sep 17 00:00:00 2001 From: Linwenxuan04 Date: Mon, 7 Oct 2024 11:25:12 -0400 Subject: [PATCH 4/4] [Core] Implemented GroupAtAllRemain --- .../Common/Interface/Api/GroupExt.cs | 3 ++ .../Logic/Implementation/OperationLogic.cs | 10 +++++ .../Action/FetchGroupAtAllRemainEvent.cs | 26 ++++++++++++ .../Oidb/Request/OidbSvcTrpcTcp0x8A7_0.cs | 18 ++++++++ .../Response/OidbSvcTrpcTcp0x8A7_0Response.cs | 17 ++++++++ .../Action/FetchGroupAtAllRemainService.cs | 41 +++++++++++++++++++ 6 files changed, 115 insertions(+) create mode 100644 Lagrange.Core/Internal/Event/Action/FetchGroupAtAllRemainEvent.cs create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x8A7_0.cs create mode 100644 Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x8A7_0Response.cs create mode 100644 Lagrange.Core/Internal/Service/Action/FetchGroupAtAllRemainService.cs diff --git a/Lagrange.Core/Common/Interface/Api/GroupExt.cs b/Lagrange.Core/Common/Interface/Api/GroupExt.cs index 4546ee7f5..35dbc2ddf 100644 --- a/Lagrange.Core/Common/Interface/Api/GroupExt.cs +++ b/Lagrange.Core/Common/Interface/Api/GroupExt.cs @@ -98,6 +98,9 @@ public static Task GroupSetMessageReaction(this BotContext bot, uint group public static Task GroupSetAvatar(this BotContext bot, uint groupUin, ImageEntity imageEntity) => bot.ContextCollection.Business.OperationLogic.GroupSetAvatar(groupUin, imageEntity); + + public static Task<(uint, uint)> GroupRemainAtAll(this BotContext bot, uint groupUin) + => bot.ContextCollection.Business.OperationLogic.GroupRemainAtAll(groupUin); #region Group File System diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs index 5b018b05e..d4bb7e817 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs @@ -633,4 +633,14 @@ public async Task GroupSetAvatar(uint groupUin, ImageEntity avatar) }.Serialize().ToArray(); return await Collection.Highway.UploadSrcByStreamAsync(3000, avatar.ImageStream.Value, ticket, md5, extra); } + + public async Task<(uint, uint)> GroupRemainAtAll(uint groupUin) + { + var groupRemainAtAllEvent = FetchGroupAtAllRemainEvent.Create(groupUin); + var results = await Collection.Business.SendEvent(groupRemainAtAllEvent); + if (results.Count == 0) return (0, 0); + + var ret = (FetchGroupAtAllRemainEvent)results[0]; + return (ret.RemainAtAllCountForUin, ret.RemainAtAllCountForGroup); + } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Action/FetchGroupAtAllRemainEvent.cs b/Lagrange.Core/Internal/Event/Action/FetchGroupAtAllRemainEvent.cs new file mode 100644 index 000000000..c9580c453 --- /dev/null +++ b/Lagrange.Core/Internal/Event/Action/FetchGroupAtAllRemainEvent.cs @@ -0,0 +1,26 @@ +namespace Lagrange.Core.Internal.Event.Action; + +internal class FetchGroupAtAllRemainEvent : ProtocolEvent +{ + public uint GroupUin { get; set; } + + public uint RemainAtAllCountForUin { get; set; } + + public uint RemainAtAllCountForGroup { get; set; } + + private FetchGroupAtAllRemainEvent(uint groupUin) : base(true) + { + GroupUin = groupUin; + } + + private FetchGroupAtAllRemainEvent(int resultCode, uint remainAtAllCountForUin, uint remainAtAllCountForGroup) : base(resultCode) + { + RemainAtAllCountForUin = remainAtAllCountForUin; + RemainAtAllCountForGroup = remainAtAllCountForGroup; + } + + public static FetchGroupAtAllRemainEvent Create(uint groupUin) => new(groupUin); + + public static FetchGroupAtAllRemainEvent Result(int resultCode, uint remainAtAllCountForUin, uint remainAtAllCountForGroup) => + new(resultCode, remainAtAllCountForUin, remainAtAllCountForGroup); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x8A7_0.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x8A7_0.cs new file mode 100644 index 000000000..a3787f98b --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x8A7_0.cs @@ -0,0 +1,18 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; + +[ProtoContract] +[OidbSvcTrpcTcp(0x8A7, 0)] +internal class OidbSvcTrpcTcp0x8A7_0 +{ + [ProtoMember(1)] public uint SubCmd { get; set; } + + [ProtoMember(2)] public uint LimitIntervalTypeForUin { get; set; } + + [ProtoMember(3)] public uint LimitIntervalTypeForGroup { get; set; } + + [ProtoMember(4)] public uint Uin { get; set; } + + [ProtoMember(5)] public uint GroupCode { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x8A7_0Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x8A7_0Response.cs new file mode 100644 index 000000000..a4fc459bb --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x8A7_0Response.cs @@ -0,0 +1,17 @@ +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response; + +[ProtoContract] +internal class OidbSvcTrpcTcp0x8A7_0Response +{ + [ProtoMember(1)] public bool CanAtAll { get; set; } + + [ProtoMember(2)] public uint RemainAtAllCountForUin { get; set; } + + [ProtoMember(3)] public uint RemainAtAllCountForGroup { get; set; } + + [ProtoMember(4)] public string? PromptMsg1 { get; set; } + + [ProtoMember(5)] public string? PromptMsg2 { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Action/FetchGroupAtAllRemainService.cs b/Lagrange.Core/Internal/Service/Action/FetchGroupAtAllRemainService.cs new file mode 100644 index 000000000..6ddfdfbfd --- /dev/null +++ b/Lagrange.Core/Internal/Service/Action/FetchGroupAtAllRemainService.cs @@ -0,0 +1,41 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Event; +using Lagrange.Core.Internal.Event.Action; +using Lagrange.Core.Internal.Packets.Service.Oidb; +using Lagrange.Core.Internal.Packets.Service.Oidb.Request; +using Lagrange.Core.Internal.Packets.Service.Oidb.Response; +using Lagrange.Core.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.Action; + +[EventSubscribe(typeof(FetchGroupAtAllRemainEvent))] +[Service("OidbSvcTrpcTcp.0x8a7_0")] +internal class FetchGroupAtAllRemainService : BaseService +{ + protected override bool Build(FetchGroupAtAllRemainEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out Span output, out List>? extraPackets) + { + var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0x8A7_0 + { + SubCmd = 1, + LimitIntervalTypeForUin = 2, + LimitIntervalTypeForGroup = 1, + Uin = keystore.Uin, + GroupCode = input.GroupUin + }); + + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out FetchGroupAtAllRemainEvent output, out List? extraEvents) + { + var packet = Serializer.Deserialize>(input); + output = FetchGroupAtAllRemainEvent.Result((int)packet.ErrorCode, packet.Body.RemainAtAllCountForUin, packet.Body.RemainAtAllCountForGroup); + extraEvents = null; + return true; + } +} \ No newline at end of file