diff --git a/Lagrange.Core/Common/Interface/OperationExt.cs b/Lagrange.Core/Common/Interface/OperationExt.cs index 3777b86a..bc8c3374 100644 --- a/Lagrange.Core/Common/Interface/OperationExt.cs +++ b/Lagrange.Core/Common/Interface/OperationExt.cs @@ -38,4 +38,7 @@ public static Task FetchStranger(this BotContext context, long uin) public static Task SetGroupNotification(this BotContext context, long groupUin, ulong sequence, BotGroupNotificationType type, bool isFiltered, GroupNotificationOperate operate, string message = "") => context.EventContext.GetLogic().SetGroupNotification(groupUin, sequence, type, isFiltered, operate, message); + + public static Task SetGroupReaction(this BotContext context, long groupUin, ulong sequence, string code, bool isAdd) => + context.EventContext.GetLogic().SetGroupReaction(groupUin, sequence, code, isAdd); } \ No newline at end of file diff --git a/Lagrange.Core/Events/EventArgs/BotGroupReactionEvent.cs b/Lagrange.Core/Events/EventArgs/BotGroupReactionEvent.cs new file mode 100644 index 00000000..371e3b79 --- /dev/null +++ b/Lagrange.Core/Events/EventArgs/BotGroupReactionEvent.cs @@ -0,0 +1,21 @@ +namespace Lagrange.Core.Events.EventArgs; + +public class BotGroupReactionEvent(long targetGroupUin, ulong targetSequence, long operatorUin, bool isAdd, string code, ulong currentCount) : EventBase +{ + public long TargetGroupUin { get; } = targetGroupUin; + + public ulong TargetSequence { get; } = targetSequence; + + public long OperatorUin { get; } = operatorUin; + + public bool IsAdd { get; } = isAdd; + + public string Code { get; } = code; + + public ulong CurrentCount { get; } = currentCount; + + public override string ToEventMessage() + { + return $"{nameof(BotGroupReactionEvent)}: Target {TargetGroupUin} > {TargetSequence} Operator {OperatorUin} {(IsAdd ? "Add" : "Reduce")} {Code} Current {CurrentCount}"; + } +} diff --git a/Lagrange.Core/Internal/Context/EventContext.cs b/Lagrange.Core/Internal/Context/EventContext.cs index ffd69395..67c38c1a 100644 --- a/Lagrange.Core/Internal/Context/EventContext.cs +++ b/Lagrange.Core/Internal/Context/EventContext.cs @@ -13,9 +13,9 @@ namespace Lagrange.Core.Internal.Context; internal class EventContext : IDisposable { private const string Tag = nameof(EventContext); - + private readonly BotContext _context; - + private readonly FrozenDictionary> _events; private readonly FrozenDictionary _logics; @@ -27,7 +27,7 @@ public EventContext(BotContext context) { var events = new Dictionary>(); var logics = new Dictionary(); - + foreach (var type in typeof(ILogic).Assembly.GetTypes()) { if (type.HasImplemented() && Activator.CreateInstance(type, context) is ILogic instance) @@ -39,30 +39,30 @@ public EventContext(BotContext context) list = []; events.Add(@event.EventType, list); } - + list.Add(instance); } - + logics[type] = instance; } } - + _context = context; _events = events.ToFrozenDictionary(); _logics = logics.ToFrozenDictionary(); } - + public async ValueTask SendEvent(ProtocolEvent @event) where T : ProtocolEvent { try { await HandleOutgoingEvent(@event); - var (frame, attribute) = await _context.ServiceContext.Resolve(@event); + var (frame, attribute) = await _context.ServiceContext.Resolve(@event); if (frame.Sequence == 0) throw new LagrangeException("The sequence number is 0 for the SSOFrame"); - + var @return = await _context.PacketContext.SendPacket(frame, attribute); var resolved = await _context.ServiceContext.Resolve(@return); - + if (resolved is T result) { await HandleIncomingEvent(result); @@ -76,7 +76,7 @@ public async ValueTask SendEvent(ProtocolEvent @event) where T : ProtocolE throw new LagrangeException("An error occurred while sending the event", e); } } - + private async ValueTask HandleIncomingEvent(ProtocolEvent @event) { if (_events.TryGetValue(@event.GetType(), out var logics)) @@ -112,7 +112,7 @@ private async ValueTask HandleOutgoingEvent(ProtocolEvent @event) } } } - + public async ValueTask HandleOutgoingEvent(EventBase @event) { if (_events.TryGetValue(@event.GetType(), out var logics)) @@ -142,8 +142,12 @@ public async Task HandleServerPacket(SsoPacket packet) { _context.LogWarning(Tag, "Service not found for command: {0}", e, e.Command); } + catch (Exception e) + { + _context.LogError(Tag, "Handle {0} server packet failed", e, packet.Command); + } } - + public T GetLogic() where T : ILogic => (T)_logics[typeof(T)]; public void Dispose() diff --git a/Lagrange.Core/Internal/Context/PacketContext.cs b/Lagrange.Core/Internal/Context/PacketContext.cs index 0a7d58ed..72a6fab9 100644 --- a/Lagrange.Core/Internal/Context/PacketContext.cs +++ b/Lagrange.Core/Internal/Context/PacketContext.cs @@ -42,7 +42,7 @@ public ValueTask SendPacket(SsoPacket packet, ServiceAttribute option Task.Run(async () => // Schedule the task to the ThreadPool { ReadOnlyMemory frame; - + switch (options.RequestType) { case RequestType.D2Auth: @@ -72,10 +72,10 @@ public ValueTask SendPacket(SsoPacket packet, ServiceAttribute option throw new InvalidOperationException($"Unknown RequestType: {options.RequestType}"); } } - + await _context.SocketContext.Send(frame); }); - + return new ValueTask(tcs, 0); } @@ -83,7 +83,7 @@ public void DispatchPacket(ReadOnlySpan buffer) { var service = _servicePacker.Parse(buffer); var sso = _ssoPacker.Parse(service); - + if (_pendingTasks.TryRemove(sso.Sequence, out var tcs)) { if (sso is { RetCode: not 0, Extra: var extra }) @@ -98,7 +98,7 @@ public void DispatchPacket(ReadOnlySpan buffer) } else { - _ = _context.EventContext.HandleServerPacket(sso); + Task.Run(() => _context.EventContext.HandleServerPacket(sso)); } } -} \ No newline at end of file +} diff --git a/Lagrange.Core/Internal/Events/System/AddGroupReactionEvent.cs b/Lagrange.Core/Internal/Events/System/AddGroupReactionEvent.cs new file mode 100644 index 00000000..a9b1e547 --- /dev/null +++ b/Lagrange.Core/Internal/Events/System/AddGroupReactionEvent.cs @@ -0,0 +1,15 @@ +namespace Lagrange.Core.Internal.Events.System; + +internal class AddGroupReactionEventReq(long groupUin, ulong sequence, string code) : ProtocolEvent +{ + public long GroupUin { get; } = groupUin; + + public ulong Sequence { get; } = sequence; + + public string Code { get; } = code; +} + +internal class AddGroupReactionEventResp : ProtocolEvent +{ + public static readonly AddGroupReactionEventResp Default = new(); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Events/System/ReduceGroupReactionEventReq.cs b/Lagrange.Core/Internal/Events/System/ReduceGroupReactionEventReq.cs new file mode 100644 index 00000000..5f76a4e7 --- /dev/null +++ b/Lagrange.Core/Internal/Events/System/ReduceGroupReactionEventReq.cs @@ -0,0 +1,15 @@ +namespace Lagrange.Core.Internal.Events.System; + +internal class ReduceGroupReactionEventReq(long groupUin, ulong sequence, string code) : ProtocolEvent +{ + public long GroupUin { get; } = groupUin; + + public ulong Sequence { get; } = sequence; + + public string Code { get; } = code; +} + +internal class ReduceGroupReactionEventResp : ProtocolEvent +{ + public static readonly ReduceGroupReactionEventResp Default = new(); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Logic/OperationLogic.cs b/Lagrange.Core/Internal/Logic/OperationLogic.cs index a8fb268c..3e513f02 100644 --- a/Lagrange.Core/Internal/Logic/OperationLogic.cs +++ b/Lagrange.Core/Internal/Logic/OperationLogic.cs @@ -274,4 +274,14 @@ await context.EventContext.SendEvent( ); } } + + public async Task SetGroupReaction(long groupUin, ulong sequence, string code, bool isAdd) + { + if (isAdd) await context.EventContext.SendEvent( + new AddGroupReactionEventReq(groupUin, sequence, code) + ); + else await context.EventContext.SendEvent( + new ReduceGroupReactionEventReq(groupUin, sequence, code) + ); + } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Logic/PushLogic.cs b/Lagrange.Core/Internal/Logic/PushLogic.cs index 8d8aa0d0..706e5c8f 100644 --- a/Lagrange.Core/Internal/Logic/PushLogic.cs +++ b/Lagrange.Core/Internal/Logic/PushLogic.cs @@ -27,7 +27,7 @@ public async ValueTask Incoming(ProtocolEvent e) case Type.TempMessage: { var message = await context.EventContext.GetLogic().Parse(messageEvent.MsgPush.CommonMessage); - if (message.Entities[0] is LightAppEntity {AppName: "com.tencent.qun.invite"} || message.Entities[0] is LightAppEntity {AppName: "com.tencent.tuwen.lua"}) + if (message.Entities[0] is LightAppEntity { AppName: "com.tencent.qun.invite" } || message.Entities[0] is LightAppEntity { AppName: "com.tencent.tuwen.lua" }) { var app = (LightAppEntity)message.Entities[0]; using var document = JsonDocument.Parse(app.Payload); @@ -152,8 +152,8 @@ public async ValueTask Incoming(ProtocolEvent e) var pkgType210 = (Event0x210SubType)messageEvent.MsgPush.CommonMessage.ContentHead.SubType; switch (pkgType210) { - case Event0x210SubType.FriendRequestNotice - when messageEvent.MsgPush.CommonMessage.MessageBody.MsgContent is { } content: + case Event0x210SubType.FriendRequestNotice when messageEvent.MsgPush.CommonMessage.MessageBody.MsgContent is { } content: + { var friendRequest = ProtoHelper.Deserialize(content.Span); context.EventInvoker.PostEvent(new BotFriendRequestEvent( friendRequest.Info!.SourceUid, @@ -162,6 +162,12 @@ public async ValueTask Incoming(ProtocolEvent e) friendRequest.Info.Source ?? string.Empty )); break; + } + default: + { + context.LogWarning(nameof(PushLogic), "Unknown 0x210 sub type: {0}", null, pkgType210); + break; + } } break; @@ -195,6 +201,40 @@ public async ValueTask Incoming(ProtocolEvent e) } break; } + case Event0x2DCSubType.SubType16 when messageEvent.MsgPush.CommonMessage.MessageBody.MsgContent is { } content: + { + var reader = new BinaryPacket(content); + // group uin and 1 byte + reader.Skip(4 + 1); + var proto = reader.ReadBytes(Prefix.Int16 | Prefix.LengthOnly); + var body = ProtoHelper.Deserialize(proto); + + switch ((Event0x2DCSubType16SubType)body.SubType) + { + case Event0x2DCSubType16SubType.GroupReactionNotice: + { + var reaction = body.Reaction.Data.Data; + + long @operator = context.CacheContext.ResolveUin(reaction.Data.OperatorUid); + + context.EventInvoker.PostEvent(new BotGroupReactionEvent( + body.GroupUin, + reaction.Target.Sequence, + @operator, + reaction.Data.Type == 1, + reaction.Data.Code, + reaction.Data.CurrentCount + )); + break; + } + default: + { + context.LogWarning(nameof(PushLogic), "Unknown 0x2DCSub16 sub type: {0}", null, body.SubType); + break; + } + } + break; + } default: { context.LogWarning(nameof(PushLogic), "Unknown 0x2DC sub type: {0}", null, pkgType); @@ -244,14 +284,11 @@ private enum Event0x2DCSubType GroupGreyTipNotice20 = 20, } - private enum Event0x2DCSubType16Field13 + private enum Event0x2DCSubType16SubType { - GroupMemberSpecialTitleNotice = 6, - GroupNameChangeNotice = 12, - GroupTodoNotice = 23, GroupReactionNotice = 35, } - + private enum Event0x210SubType { FriendRequestNotice = 35, diff --git a/Lagrange.Core/Internal/Packets/Notify/GroupReaction.cs b/Lagrange.Core/Internal/Packets/Notify/GroupReaction.cs index fe43ba79..b5fbd661 100644 --- a/Lagrange.Core/Internal/Packets/Notify/GroupReaction.cs +++ b/Lagrange.Core/Internal/Packets/Notify/GroupReaction.cs @@ -35,7 +35,7 @@ internal partial class GroupReactionData3 { [ProtoMember(1)] public string Code { get; set; } - [ProtoMember(3)] public uint Count { get; set; } + [ProtoMember(3)] public uint CurrentCount { get; set; } [ProtoMember(4)] public string OperatorUid { get; set; } diff --git a/Lagrange.Core/Internal/Packets/Notify/NotifyMessageBody.cs b/Lagrange.Core/Internal/Packets/Notify/NotifyMessageBody.cs index b1c60636..e781064b 100644 --- a/Lagrange.Core/Internal/Packets/Notify/NotifyMessageBody.cs +++ b/Lagrange.Core/Internal/Packets/Notify/NotifyMessageBody.cs @@ -7,7 +7,7 @@ namespace Lagrange.Core.Internal.Packets.Notify; [ProtoPackable] internal partial class NotifyMessageBody { - [ProtoMember(1)] public uint Type { get; set; } + [ProtoMember(1)] public uint NotifyType { get; set; } [ProtoMember(4)] public long GroupUin { get; set; } @@ -15,7 +15,7 @@ internal partial class NotifyMessageBody [ProtoMember(11)] public GroupRecall Recall { get; set; } - [ProtoMember(13)] public uint? Field13 { get; set; } + [ProtoMember(13)] public uint SubType { get; set; } [ProtoMember(21)] public string OperatorUid { get; set; } diff --git a/Lagrange.Core/Internal/Packets/Service/SetGroupReaction.cs b/Lagrange.Core/Internal/Packets/Service/SetGroupReaction.cs new file mode 100644 index 00000000..93ec9e47 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/SetGroupReaction.cs @@ -0,0 +1,20 @@ +using Lagrange.Proto; + +namespace Lagrange.Core.Internal.Packets.Service; + +#pragma warning disable CS8618 + +[ProtoPackable] +public partial class SetGroupReactionRequest +{ + [ProtoMember(2)] public long GroupUin { get; set; } + + [ProtoMember(3)] public ulong Sequence { get; set; } + + [ProtoMember(4)] public string Code { get; set; } + + [ProtoMember(5)] public ulong Type { get; set; } +} + +[ProtoPackable] +public partial class SetGroupReactionResponse; diff --git a/Lagrange.Core/Internal/Services/System/AddGroupReactionService.cs b/Lagrange.Core/Internal/Services/System/AddGroupReactionService.cs new file mode 100644 index 00000000..2a58e4e0 --- /dev/null +++ b/Lagrange.Core/Internal/Services/System/AddGroupReactionService.cs @@ -0,0 +1,31 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Events; +using Lagrange.Core.Internal.Events.System; +using Lagrange.Core.Internal.Packets.Service; + +namespace Lagrange.Core.Internal.Services.System; + +[EventSubscribe(Protocols.All)] +[Service("OidbSvcTrpcTcp.0x9082_1")] +internal class AddGroupReactionService : OidbService +{ + private protected override uint Command => 0x9082; + + private protected override uint Service => 1; + + private protected override Task ProcessRequest(AddGroupReactionEventReq request, BotContext context) + { + return Task.FromResult(new SetGroupReactionRequest + { + GroupUin = request.GroupUin, + Sequence = request.Sequence, + Code = request.Code, + Type = request.Code.Length <= 3 ? 1ul : 2ul + }); + } + + private protected override Task ProcessResponse(SetGroupReactionResponse response, BotContext context) + { + return Task.FromResult(AddGroupReactionEventResp.Default); + } +} diff --git a/Lagrange.Core/Internal/Services/System/ReduceGroupReactionService.cs b/Lagrange.Core/Internal/Services/System/ReduceGroupReactionService.cs new file mode 100644 index 00000000..ae531a8b --- /dev/null +++ b/Lagrange.Core/Internal/Services/System/ReduceGroupReactionService.cs @@ -0,0 +1,31 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Events; +using Lagrange.Core.Internal.Events.System; +using Lagrange.Core.Internal.Packets.Service; + +namespace Lagrange.Core.Internal.Services.System; + +[EventSubscribe(Protocols.All)] +[Service("OidbSvcTrpcTcp.0x9082_2")] +internal class ReduceGroupReactionService : OidbService +{ + private protected override uint Command => 0x9082; + + private protected override uint Service => 2; + + private protected override Task ProcessRequest(ReduceGroupReactionEventReq request, BotContext context) + { + return Task.FromResult(new SetGroupReactionRequest + { + GroupUin = request.GroupUin, + Sequence = request.Sequence, + Code = request.Code, + Type = request.Code.Length <= 3 ? 1ul : 2ul + }); + } + + private protected override Task ProcessResponse(SetGroupReactionResponse response, BotContext context) + { + return Task.FromResult(ReduceGroupReactionEventResp.Default); + } +}