From ce15e6d4dd34416a5bdb757004185683da7d3852 Mon Sep 17 00:00:00 2001 From: dogdie233 Date: Wed, 8 Jan 2025 11:51:28 +0800 Subject: [PATCH] [Core] Add bounce face (Recvive not available) --- .../Logic/Implementation/MessagingLogic.cs | 18 +++ .../Implementation/Extra/QBounceFaceExtra.cs | 23 ++++ .../Message/Entity/BounceFaceEntity.cs | 112 ++++++++++++++++++ Lagrange.Core/Message/MessageBuilder.cs | 29 +++++ 4 files changed, 182 insertions(+) create mode 100644 Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBounceFaceExtra.cs create mode 100644 Lagrange.Core/Message/Entity/BounceFaceEntity.cs diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs index 71c7df0e7..b3e7679ae 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs @@ -425,6 +425,24 @@ private async Task ResolveOutgoingChain(MessageChain chain) face.SysFaceEntry ??= await cache.GetCachedFaceEntity(face.FaceId); break; } + case BounceFaceEntity bounceFace: + { + var cache = Collection.Business.CachingLogic; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (bounceFace.Name != null) + break; + + string name = (await cache.GetCachedFaceEntity(bounceFace.FaceId))?.QDes ?? string.Empty; + + // Because the name is used as a preview text, it should not start with '/' + // But the QDes of the face may start with '/', so remove it + if (name.StartsWith('/')) + name = name[1..]; + + bounceFace.Name = name; + break; + } case ForwardEntity forward when forward.TargetUin != 0: { var cache = Collection.Business.CachingLogic; diff --git a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBounceFaceExtra.cs b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBounceFaceExtra.cs new file mode 100644 index 000000000..4c2523f6e --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBounceFaceExtra.cs @@ -0,0 +1,23 @@ +#pragma warning disable CS8618 + +using ProtoBuf; + +namespace Lagrange.Core.Internal.Packets.Message.Element.Implementation.Extra; + +[ProtoContract] +internal class QBounceFaceExtra +{ + [ProtoMember(1)] public int Field1 { get; set; } = 13; + + [ProtoMember(2)] public uint Count { get; set; } = 1; + + [ProtoMember(3)] public string Name { get; set; } = string.Empty; + + [ProtoMember(6)] public QSmallFaceExtra Face { get; set; } + + [ProtoContract] + public class FallbackPreviewTextPb + { + [ProtoMember(1)] public string Text { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Message/Entity/BounceFaceEntity.cs b/Lagrange.Core/Message/Entity/BounceFaceEntity.cs new file mode 100644 index 000000000..b8cd02f38 --- /dev/null +++ b/Lagrange.Core/Message/Entity/BounceFaceEntity.cs @@ -0,0 +1,112 @@ +using System.Text; +using Lagrange.Core.Common.Entity; +using Lagrange.Core.Internal.Packets.Message.Element; +using Lagrange.Core.Internal.Packets.Message.Element.Implementation; +using Lagrange.Core.Internal.Packets.Message.Element.Implementation.Extra; +using Lagrange.Core.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Message.Entity; + +[MessageElement(typeof(CommonElem))] +public class BounceFaceEntity : IMessageEntity +{ + public uint FaceId { get; set; } + + public uint Count { get; set; } + + public string Name { get; set; } + + public bool ShouldAddPreviewText { get; set; } + + public BounceFaceEntity() : this(0, 0, string.Empty) {} + + public BounceFaceEntity(SysFaceEntry face, uint count, bool shouldAddPreviewText = true) + { + if (!face.AniStickerId.HasValue) + throw new ArgumentException("Face does not have a sticker ID", nameof(face)); + + FaceId = (uint)face.AniStickerId.Value; + Count = count; + Name = face.QDes ?? string.Empty; + ShouldAddPreviewText = shouldAddPreviewText; + + // Because the name is used as a preview text, it should not start with '/' + // But the QDes of the face may start with '/', so remove it + if (Name.StartsWith('/')) + Name = Name[1..]; + } + + public BounceFaceEntity(uint faceId, uint count, string? name = null, bool shouldAddPreviewText = true) + { + FaceId = faceId; + Count = count; + + // If the name is null, it will be assigned in MessagingLogic.ResolveOutgoingChain + Name = name!; + ShouldAddPreviewText = shouldAddPreviewText; + } + + IEnumerable IMessageEntity.PackElement() + { + byte[] pbElem; + using (var ms = new MemoryStream()) + { + Serializer.Serialize(ms, new QBounceFaceExtra + { + Field1 = 13, + Face = new QSmallFaceExtra + { + FaceId = FaceId, + Text = Name, + CompatText = Name + }, + Count = Count, + Name = Name + }); + pbElem = ms.ToArray(); + } + + var common = new CommonElem + { + ServiceType = 23, + PbElem = pbElem, + BusinessType = 13 + }; + + if (!ShouldAddPreviewText) + return new Elem[] { new() { CommonElem = common } }; + + byte[] textFallbackPb; + using (var ms = new MemoryStream()) + { + Serializer.Serialize(ms, new QBounceFaceExtra.FallbackPreviewTextPb { Text = $"[{Name}]请使用最新版手机QQ体验新功能。" }); + textFallbackPb = ms.ToArray(); + } + return new Elem[] + { + new() { CommonElem = common }, + new() + { + Text = new Text + { + Str = ToPreviewText(), + PbReserve = textFallbackPb + } + } + }; + } + + IMessageEntity? IMessageEntity.UnpackElement(Elem elem) + { + if (elem.CommonElem is not { ServiceType: 23 } common) + return null; + + var extra = Serializer.Deserialize(common.PbElem.AsSpan()); + return new BounceFaceEntity(extra.Face.FaceId, extra.Count, extra.Name); + } + + public string ToPreviewString() => "$[BounceFace | Name: {Name}({FaceId}) | Count: {Count}]"; + + public string ToPreviewText() => $"[{Name}]x{Count}"; +} \ No newline at end of file diff --git a/Lagrange.Core/Message/MessageBuilder.cs b/Lagrange.Core/Message/MessageBuilder.cs index 3c83cb618..f1acefd29 100644 --- a/Lagrange.Core/Message/MessageBuilder.cs +++ b/Lagrange.Core/Message/MessageBuilder.cs @@ -298,6 +298,35 @@ public MessageBuilder SpecialPoke(SpecialPokeFaceType type, uint count = 1) return this; } + /// + /// Add a bounce face entity to message chain + /// + /// The face entry of the face to be added to the message chain + /// The number of face entities to generate + /// Should the entity add a preview text + public MessageBuilder BounceFace(SysFaceEntry face, uint count, bool shouldAddPreviewText = true) + { + var bounceFaceEntity = new BounceFaceEntity(face, count, shouldAddPreviewText); + _chain.Add(bounceFaceEntity); + + return this; + } + + /// + /// Add a bounce face entity to message chain + /// + /// The face ID of the face to be added to the message chain + /// The number of face entities to generate, default is 1 + /// The name of the face + /// Should the entity add a preview text + public MessageBuilder BounceFace(uint faceId, uint count, string? name = null, bool shouldAddPreviewText = true) + { + var bounceFaceEntity = new BounceFaceEntity(faceId, count, name, shouldAddPreviewText); + _chain.Add(bounceFaceEntity); + + return this; + } + /// /// Add a dedicated LightApp entity to message chain ///