diff --git a/Lagrange.Core/Common/Entity/SysFaceEntry.cs b/Lagrange.Core/Common/Entity/SysFaceEntry.cs new file mode 100644 index 000000000..7bbf0468e --- /dev/null +++ b/Lagrange.Core/Common/Entity/SysFaceEntry.cs @@ -0,0 +1,67 @@ +namespace Lagrange.Core.Common.Entity; + +[Serializable] +public class SysFaceEntry +{ + public string QSid { get; set; } + + public string? QDes { get; set; } + + public string? EMCode { get; set; } + + public int? QCid { get; set; } + + public int? AniStickerType { get; set; } + + public int? AniStickerPackId { get; set; } + + public int? AniStickerId { get; set; } + + public string? Url { get; set; } + + public string[]? EmojiNameAlias { get; set; } + + public int? AniStickerWidth { get; set; } + + public int? AniStickerHeight { get; set; } + + public SysFaceEntry(string qSid, string? qDes, string? emCode, int? qCid, int? aniStickerType, + int? aniStickerPackId, int? aniStickerId, string? url, string[]? emojiNameAlias, int? aniStickerWidth, + int? aniStickerHeight) + { + QSid = qSid; + QDes = qDes; + EMCode = emCode; + QCid = qCid; + AniStickerType = aniStickerType; + AniStickerPackId = aniStickerPackId; + AniStickerId = aniStickerId; + Url = url; + EmojiNameAlias = emojiNameAlias; + AniStickerWidth = aniStickerWidth; + AniStickerHeight = aniStickerHeight; + } +} + +[Serializable] +public class SysFacePackEntry +{ + public string EmojiPackName { get; set; } + + public SysFaceEntry[] Emojis { get; set; } + + public SysFacePackEntry(string emojiPackName, SysFaceEntry[] emojis) + { + EmojiPackName = emojiPackName; + Emojis = emojis; + } + + public uint[] GetUniqueSuperQSids((int AniStickerType, int AniStickerPackId)[] excludeAniStickerTypesAndPackIds) + => Emojis + .Where(e => e.AniStickerType is not null + && e.AniStickerPackId is not null + && !excludeAniStickerTypesAndPackIds.Contains((e.AniStickerType.Value, e.AniStickerPackId.Value))) + .Select(e => uint.Parse(e.QSid)) + .Distinct() + .ToArray(); +} \ 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 3900a4416..fee6b08a2 100644 --- a/Lagrange.Core/Common/Interface/Api/OperationExt.cs +++ b/Lagrange.Core/Common/Interface/Api/OperationExt.cs @@ -249,4 +249,16 @@ public static Task FriendShake(this BotContext bot, uint friendUi /// The avatar object, public static Task SetAvatar(this BotContext bot, ImageEntity avatar) => bot.ContextCollection.Business.OperationLogic.SetAvatar(avatar); + + public static Task FetchSuperFaceId(this BotContext bot, uint id) + => bot.ContextCollection.Business.OperationLogic.FetchSuperFaceId(id); + + public static Task FetchFaceEntity(this BotContext bot, uint id) + => bot.ContextCollection.Business.OperationLogic.FetchFaceEntity(id); + + public static Task GroupJoinEmojiChain(this BotContext bot, uint groupUin, uint emojiId, uint targetMessageSeq) + => bot.ContextCollection.Business.OperationLogic.GroupJoinEmojiChain(groupUin, emojiId, targetMessageSeq); + + public static Task FriendJoinEmojiChain(this BotContext bot, uint friendUin, uint emojiId, uint targetMessageSeq) + => bot.ContextCollection.Business.OperationLogic.FriendJoinEmojiChain(friendUin, emojiId, targetMessageSeq); } diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs index 645946768..3fbafcdb7 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/CachingLogic.cs @@ -25,6 +25,9 @@ internal class CachingLogic : LogicBase private readonly ConcurrentDictionary _cacheUsers; + private readonly Dictionary _cacheFaceEntities; + private readonly List _cacheSuperFaceId; + internal CachingLogic(ContextCollection collection) : base(collection) { _uinToUid = new Dictionary(); @@ -35,6 +38,9 @@ internal CachingLogic(ContextCollection collection) : base(collection) _cachedGroupMembers = new Dictionary>(); _cacheUsers = new ConcurrentDictionary(); + + _cacheFaceEntities = new Dictionary(); + _cacheSuperFaceId = new List(); } public override Task Incoming(ProtocolEvent e) @@ -92,6 +98,7 @@ public async Task> GetCachedMembers(uint groupUin, bool ref await ResolveMembersUid(groupUin); return _cachedGroupMembers.TryGetValue(groupUin, out members) ? members : new List(); } + return members; } @@ -142,6 +149,7 @@ private async Task ResolveFriendsUidAndFriendGroups() { friend.Group = new(friend.Group.GroupId, friendGroups[friend.Group.GroupId]); } + friends.AddRange(result.Friends); next = result.NextUin; @@ -190,4 +198,32 @@ private async Task ResolveUser(uint uin) _cacheUsers.AddOrUpdate(uin, @event.UserInfo, (_key, _value) => @event.UserInfo); } } + + private async Task ResolveEmojiCache() + { + var fetchSysEmojisEvent = FetchFullSysFacesEvent.Create(); + var events = await Collection.Business.SendEvent(fetchSysEmojisEvent); + var emojiPacks = ((FetchFullSysFacesEvent)events[0]).FacePacks; + + emojiPacks + .SelectMany(pack => pack.Emojis) + .Where(emoji => uint.TryParse(emoji.QSid, out _)) + .ToList() + .ForEach(emoji => _cacheFaceEntities[uint.Parse(emoji.QSid)] = emoji); + + _cacheSuperFaceId.AddRange(emojiPacks + .SelectMany(emojiPack => emojiPack.GetUniqueSuperQSids(new[] { (1, 1) }))); + } + + public async Task GetCachedIsSuperFaceId(uint id) + { + if (!_cacheSuperFaceId.Any()) await ResolveEmojiCache(); + return _cacheSuperFaceId.Contains(id); + } + + public async Task GetCachedFaceEntity(uint faceId) + { + if (!_cacheFaceEntities.ContainsKey(faceId)) await ResolveEmojiCache(); + return _cacheFaceEntities.GetValueOrDefault(faceId); + } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs index 9da1c8f2c..4581d2ce5 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/MessagingLogic.cs @@ -10,6 +10,7 @@ using Lagrange.Core.Message; using Lagrange.Core.Message.Entity; using Lagrange.Core.Message.Filter; +using Lagrange.Core.Utility.Extension; using FriendPokeEvent = Lagrange.Core.Event.EventArg.FriendPokeEvent; using GroupPokeEvent = Lagrange.Core.Event.EventArg.GroupPokeEvent; @@ -382,6 +383,13 @@ private async Task ResolveOutgoingChain(MessageChain chain) { switch (entity) { + case FaceEntity face: + { + var cache = Collection.Business.CachingLogic; + face.SysFaceEntry ??= await cache.GetCachedFaceEntity(face.FaceId); + break; + } + case ForwardEntity forward when forward.TargetUin != 0: { var cache = Collection.Business.CachingLogic; diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs index fdf82a6aa..1d1ce7796 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/OperationLogic.cs @@ -697,4 +697,24 @@ public async Task GroupSetAvatar(uint groupUin, ImageEntity avatar) var ret = (FetchGroupAtAllRemainEvent)results[0]; return (ret.RemainAtAllCountForUin, ret.RemainAtAllCountForGroup); } + + public async Task FetchSuperFaceId(uint id) => await Collection.Business.CachingLogic.GetCachedIsSuperFaceId(id); + + public async Task FetchFaceEntity(uint id) => await Collection.Business.CachingLogic.GetCachedFaceEntity(id); + + public async Task GroupJoinEmojiChain(uint groupUin, uint emojiId, uint targetMessageSeq) + { + var groupJoinEmojiChainEvent = GroupJoinEmojiChainEvent.Create(targetMessageSeq, emojiId, groupUin); + var results = await Collection.Business.SendEvent(groupJoinEmojiChainEvent); + return results.Count != 0 && results[0].ResultCode == 0; + } + + public async Task FriendJoinEmojiChain(uint friendUin, uint emojiId, uint targetMessageSeq) + { + string? friendUid = await Collection.Business.CachingLogic.ResolveUid(null, friendUin); + if (friendUid == null) return false; + var friendJoinEmojiChainEvent = FriendJoinEmojiChainEvent.Create(targetMessageSeq, emojiId, friendUid); + var results = await Collection.Business.SendEvent(friendJoinEmojiChainEvent); + return results.Count != 0 && results[0].ResultCode == 0; + } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Action/FriendJoinEmojiChainEvent.cs b/Lagrange.Core/Internal/Event/Action/FriendJoinEmojiChainEvent.cs new file mode 100644 index 000000000..1846f5048 --- /dev/null +++ b/Lagrange.Core/Internal/Event/Action/FriendJoinEmojiChainEvent.cs @@ -0,0 +1,14 @@ +namespace Lagrange.Core.Internal.Event.Action; + +internal class FriendJoinEmojiChainEvent : JoinEmojiChainEvent +{ + private FriendJoinEmojiChainEvent(uint targetMessageSeq, uint targetFaceId, string friendUid) : base(targetMessageSeq, targetFaceId) + { + FriendUid = friendUid; + } + + private FriendJoinEmojiChainEvent(int resultCode) : base(resultCode) { } + + public static FriendJoinEmojiChainEvent Create(uint targetMessageSeq, uint targetFaceId, string friendUid) + => new(targetMessageSeq, targetFaceId, friendUid); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Action/GroupJoinEmojiChainEvent.cs b/Lagrange.Core/Internal/Event/Action/GroupJoinEmojiChainEvent.cs new file mode 100644 index 000000000..8f58544ec --- /dev/null +++ b/Lagrange.Core/Internal/Event/Action/GroupJoinEmojiChainEvent.cs @@ -0,0 +1,14 @@ +namespace Lagrange.Core.Internal.Event.Action; + +internal class GroupJoinEmojiChainEvent : JoinEmojiChainEvent +{ + private GroupJoinEmojiChainEvent(uint targetMessageSeq, uint targetFaceId, uint groupUin) : base(targetMessageSeq, targetFaceId) + { + GroupUin = groupUin; + } + + private GroupJoinEmojiChainEvent(int resultCode) : base(resultCode) { } + + public static GroupJoinEmojiChainEvent Create(uint targetMessageSeq, uint targetFaceId, uint groupUin) + => new(targetMessageSeq, targetFaceId, groupUin); +} diff --git a/Lagrange.Core/Internal/Event/Action/JoinEmojiChainEvent.cs b/Lagrange.Core/Internal/Event/Action/JoinEmojiChainEvent.cs new file mode 100644 index 000000000..7f0c0d74b --- /dev/null +++ b/Lagrange.Core/Internal/Event/Action/JoinEmojiChainEvent.cs @@ -0,0 +1,22 @@ +namespace Lagrange.Core.Internal.Event.Action; + +internal class JoinEmojiChainEvent : ProtocolEvent +{ + public uint TargetMessageSeq { get; set; } + + public uint TargetFaceId { get; set; } + + public uint? GroupUin { get; set; } + + public string? FriendUid { get; set; } + + protected JoinEmojiChainEvent(uint targetMessageSeq, uint targetFaceId) : base(true) + { + TargetMessageSeq = targetMessageSeq; + TargetFaceId = targetFaceId; + } + + protected JoinEmojiChainEvent(int resultCode) : base(resultCode) { } + + public static JoinEmojiChainEvent Result(int resultCode) => new(resultCode); +} diff --git a/Lagrange.Core/Internal/Event/System/FetchFullSysFacesEvent.cs b/Lagrange.Core/Internal/Event/System/FetchFullSysFacesEvent.cs new file mode 100644 index 000000000..71c64950d --- /dev/null +++ b/Lagrange.Core/Internal/Event/System/FetchFullSysFacesEvent.cs @@ -0,0 +1,22 @@ +using Lagrange.Core.Common.Entity; + +namespace Lagrange.Core.Internal.Event.System; + +internal class FetchFullSysFacesEvent : ProtocolEvent +{ + public List FacePacks { get; set; } + + private FetchFullSysFacesEvent(List facePacks) : base(true) + { + FacePacks = facePacks; + } + + private FetchFullSysFacesEvent(int resultCode, List facePacks) : base(resultCode) + { + FacePacks = facePacks; + } + + public static FetchFullSysFacesEvent Create() => new(new List()); + + public static FetchFullSysFacesEvent Result(int resultCode, List emojiPacks) => new(resultCode, emojiPacks); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QFaceExtra.cs b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBigFaceExtra.cs similarity index 70% rename from Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QFaceExtra.cs rename to Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBigFaceExtra.cs index 49c4d4a12..db7a9defa 100644 --- a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QFaceExtra.cs +++ b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QBigFaceExtra.cs @@ -6,17 +6,17 @@ namespace Lagrange.Core.Internal.Packets.Message.Element.Implementation.Extra; /// Constructed at , Service Type 33, Big face /// [ProtoContract] -internal class QFaceExtra +internal class QBigFaceExtra { - [ProtoMember(1)] public string? Field1 { get; set; } + [ProtoMember(1)] public string? AniStickerPackId { get; set; } - [ProtoMember(2)] public string? Field2 { get; set; } + [ProtoMember(2)] public string? AniStickerId { get; set; } [ProtoMember(3)] public int? FaceId { get; set; } // 318 [ProtoMember(4)] public int? Field4 { get; set; } - [ProtoMember(5)] public int? Field5 { get; set; } + [ProtoMember(5)] public int? AniStickerType { get; set; } [ProtoMember(6)] public string? Field6 { get; set; } diff --git a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QSmallFaceExtra.cs b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QSmallFaceExtra.cs index 4c53d714a..749dcd9c1 100644 --- a/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QSmallFaceExtra.cs +++ b/Lagrange.Core/Internal/Packets/Message/Element/Implementation/Extra/QSmallFaceExtra.cs @@ -1,15 +1,16 @@ using ProtoBuf; - -#pragma warning disable CS8618 namespace Lagrange.Core.Internal.Packets.Message.Element.Implementation.Extra; +/// +/// Constructed at , Service Type 33, Small face (FaceId >= 260) +/// [ProtoContract] internal class QSmallFaceExtra { [ProtoMember(1)] public uint FaceId { get; set; } - - [ProtoMember(2)] public string Preview { get; set; } - - [ProtoMember(3)] public string Preview2 { get; set; } + + [ProtoMember(2)] public string? Text { get; set; } + + [ProtoMember(3)] public string? CompatText { get; set; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp.0x90EE_1.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp.0x90EE_1.cs new file mode 100644 index 000000000..0ca6e733d --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp.0x90EE_1.cs @@ -0,0 +1,24 @@ +using ProtoBuf; + +#pragma warning disable CS8618 +// ReSharper disable InconsistentNaming + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; + +[ProtoContract] +[OidbSvcTrpcTcp(0x90EE, 1)] +internal class OidbSvcTrpcTcp0x90EE_1 +{ + [ProtoMember(1)] public uint FaceId { get; set; } + + [ProtoMember(2)] public uint TargetMsgSeq { get; set; } + + [ProtoMember(3)] public uint TargetMsgSeq_2 { get; set; } + + [ProtoMember(4)] public int Field4 { get; set; } // group 2 friend 1 ? + + [ProtoMember(5)] public uint? TargetGroupId { get; set; } + + [ProtoMember(6)] public string? TargetUid { get; set; } + +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x9154_1.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x9154_1.cs new file mode 100644 index 000000000..09c246d12 --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Request/OidbSvcTrpcTcp0x9154_1.cs @@ -0,0 +1,17 @@ +using ProtoBuf; + +#pragma warning disable CS8618 +// ReSharper disable InconsistentNaming + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Request; + +[ProtoContract] +[OidbSvcTrpcTcp(0x9154, 1)] +internal class OidbSvcTrpcTcp0x9154_1 +{ + [ProtoMember(1)] public int Field1 { get; set; } // 0 + + [ProtoMember(2)] public int Field2 { get; set; } // 7 + + [ProtoMember(3)] public string Field3 { get; set; } // 0 +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x9154_1Response.cs b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x9154_1Response.cs new file mode 100644 index 000000000..a29ce92dd --- /dev/null +++ b/Lagrange.Core/Internal/Packets/Service/Oidb/Response/OidbSvcTrpcTcp0x9154_1Response.cs @@ -0,0 +1,84 @@ +using ProtoBuf; + +#pragma warning disable CS8618 +// Resharper disable InconsistentNaming + +namespace Lagrange.Core.Internal.Packets.Service.Oidb.Response; + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1Response +{ + [ProtoMember(1)] public int Field1 { get; set; } + + [ProtoMember(2)] public OidbSvcTrpcTcp0x9154_1ResponseContent CommonFace { get; set; } + + [ProtoMember(3)] public OidbSvcTrpcTcp0x9154_1ResponseContent SpecialBigFace { get; set; } + + [ProtoMember(4)] public OidbSvcTrpcTcp0x9154_1ResponsesMagicEmojiContent SpecialMagicFace { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1ResponseContent +{ + [ProtoMember(1)] public OidbSvcTrpcTcp0x9154_1ResponseContentEmojiList[] EmojiList { get; set; } + + [ProtoMember(2)] public OidbSvcTrpcTcp0x9154_1ResponseContentResourceUrl? ResourceUrl { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1ResponsesMagicEmojiContent +{ + [ProtoMember(1)] public OidbSvcTrpcTcp0x9154_1ResponsesMagicEmojiContentList Field1 { get; set; } + + [ProtoMember(2)] public OidbSvcTrpcTcp0x9154_1ResponseContentResourceUrl? ResourceUrl { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1ResponsesMagicEmojiContentList +{ + [ProtoMember(2)] public OidbSvcTrpcTcp0x9154_1ResponseContentEmoji[] EmojiList { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1ResponseContentEmojiList +{ + [ProtoMember(1)] public string EmojiPackName { get; set; } + + [ProtoMember(2)] public OidbSvcTrpcTcp0x9154_1ResponseContentEmoji[] EmojiDetail { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1ResponseContentEmoji +{ + [ProtoMember(1)] public string QSid { get; set; } + + [ProtoMember(2)] public string? QDes { get; set; } + + [ProtoMember(3)] public string? EMCode { get; set; } + + [ProtoMember(4)] public int? QCid { get; set; } + + [ProtoMember(5)] public int? AniStickerType { get; set; } + + [ProtoMember(6)] public int? AniStickerPackId { get; set; } + + [ProtoMember(7)] public int? AniStickerId { get; set; } + + [ProtoMember(8)] public OidbSvcTrpcTcp0x9154_1ResponseContentResourceUrl? Url { get; set; } + + [ProtoMember(9)] public string[]? EmojiNameAlias { get; set; } + + [ProtoMember(10)] public int? Unknown10 { get; set; } + + [ProtoMember(13)] public int? AniStickerWidth { get; set; } + + [ProtoMember(14)] public int? AniStickerHeight { get; set; } +} + +[ProtoContract] +internal class OidbSvcTrpcTcp0x9154_1ResponseContentResourceUrl +{ + [ProtoMember(1)] public string? BaseUrl { get; set; } + + [ProtoMember(2)] public string? AdvUrl { get; set; } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Action/JoinEmojiChainService.cs b/Lagrange.Core/Internal/Service/Action/JoinEmojiChainService.cs new file mode 100644 index 000000000..4364dcd4d --- /dev/null +++ b/Lagrange.Core/Internal/Service/Action/JoinEmojiChainService.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.Utility.Extension; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.Action; + +[EventSubscribe(typeof(GroupJoinEmojiChainEvent))] +[EventSubscribe(typeof(FriendJoinEmojiChainEvent))] +[Service("OidbSvcTrpcTcp.0x90ee_1")] +internal class JoinEmojiChainService : BaseService +{ + protected override bool Build(JoinEmojiChainEvent input, BotKeystore keystore, BotAppInfo appInfo, + BotDeviceInfo device, out Span output, out List>? extraPackets) + { + var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0x90EE_1 + { + FaceId = input.TargetFaceId, + TargetMsgSeq = input.TargetMessageSeq, + TargetMsgSeq_2 = input.TargetMessageSeq, + Field4 = input.GroupUin == null ? 1 : 2, + TargetGroupId = input.GroupUin, + TargetUid = input.FriendUid, + }); + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out JoinEmojiChainEvent output, out List? extraEvents) + { + var payload = Serializer.Deserialize>(input); + output = JoinEmojiChainEvent.Result((int)payload.ErrorCode); + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/System/FetchFullSysFacesService.cs b/Lagrange.Core/Internal/Service/System/FetchFullSysFacesService.cs new file mode 100644 index 000000000..681c12cc5 --- /dev/null +++ b/Lagrange.Core/Internal/Service/System/FetchFullSysFacesService.cs @@ -0,0 +1,68 @@ +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; + +// TODO: onebot11 API (? +[EventSubscribe(typeof(FetchFullSysFacesEvent))] +[Service("OidbSvcTrpcTcp.0x9154_1")] +internal class FetchFullSysFacesService : BaseService +{ + protected override bool Build(FetchFullSysFacesEvent input, BotKeystore keystore, BotAppInfo appInfo, + BotDeviceInfo device, out Span output, out List>? extraPackets) + { + var packet = new OidbSvcTrpcTcpBase(new OidbSvcTrpcTcp0x9154_1 + { + Field1 = 0, + Field2 = 7, + Field3 = "0", + }); + + output = packet.Serialize(); + extraPackets = null; + return true; + } + + protected override bool Parse(Span input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out FetchFullSysFacesEvent output, out List? extraEvents) + { + var packet = Serializer.Deserialize>(input); + var response = packet.Body; + + var emojiPackList = new[] { response.CommonFace, response.SpecialBigFace } + .SelectMany(content => content.EmojiList) + .Select( + emojiList => new SysFacePackEntry( + emojiList.EmojiPackName, emojiList.EmojiDetail.Select( + emoji => new SysFaceEntry( + emoji.QSid, emoji.QDes, emoji.EMCode, emoji.QCid, emoji.AniStickerType, + emoji.AniStickerPackId, emoji.AniStickerId, emoji.Url?.BaseUrl, + emoji.EmojiNameAlias, emoji.AniStickerWidth, emoji.AniStickerHeight + ) + ).ToArray() + ) + ).ToList(); + + var magicFaceList = response.SpecialMagicFace.Field1.EmojiList.Select( + emoji => new SysFaceEntry( + emoji.QSid, emoji.QDes, emoji.EMCode, emoji.QCid, emoji.AniStickerType, + emoji.AniStickerPackId, emoji.AniStickerId, emoji.Url?.BaseUrl, + emoji.EmojiNameAlias, emoji.AniStickerWidth, emoji.AniStickerHeight + ) + ).ToList(); + + var magicFacePackEntry = new SysFacePackEntry("MagicFace", magicFaceList.ToArray()); + emojiPackList.Add(magicFacePackEntry); + + output = FetchFullSysFacesEvent.Result((int)packet.ErrorCode, emojiPackList); + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Message/Entity/FaceEntity.cs b/Lagrange.Core/Message/Entity/FaceEntity.cs index 0e56d6279..941f1b6d8 100644 --- a/Lagrange.Core/Message/Entity/FaceEntity.cs +++ b/Lagrange.Core/Message/Entity/FaceEntity.cs @@ -1,3 +1,4 @@ +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; @@ -10,11 +11,13 @@ public class FaceEntity : IMessageEntity { public ushort FaceId { get; set; } - public bool IsLargeFace { get; set; } + public bool? IsLargeFace { get; set; } + + public SysFaceEntry? SysFaceEntry { get; set; } public FaceEntity() { } - public FaceEntity(ushort faceId, bool isLargeFace) + public FaceEntity(ushort faceId, bool? isLargeFace) { FaceId = faceId; IsLargeFace = isLargeFace; @@ -22,22 +25,21 @@ public FaceEntity(ushort faceId, bool isLargeFace) IEnumerable IMessageEntity.PackElement() { - if (IsLargeFace) + if (IsLargeFace ?? false) { - var qFace = new QFaceExtra + var qBigFace = new QBigFaceExtra { - Field1 = "1", - Field2 = "8", + AniStickerPackId = SysFaceEntry?.AniStickerPackId.ToString() ?? "1", + AniStickerId = SysFaceEntry?.AniStickerId.ToString() ?? "8", FaceId = FaceId, Field4 = 1, - Field5 = 1, + AniStickerType = SysFaceEntry?.AniStickerType ?? 1, Field6 = "", - Preview = "", + Preview = SysFaceEntry?.QDes ?? "", Field9 = 1 }; using var stream = new MemoryStream(); - Serializer.Serialize(stream, qFace); - + Serializer.Serialize(stream, qBigFace); return new Elem[] { new() @@ -46,12 +48,36 @@ IEnumerable IMessageEntity.PackElement() { ServiceType = 37, PbElem = stream.ToArray(), - BusinessType = 1 + BusinessType = (uint)(SysFaceEntry?.AniStickerType ?? 1) } } }; } - + + if (FaceId >= 260) + { + var qSmallFace = new QSmallFaceExtra + { + FaceId = FaceId, + Text = SysFaceEntry?.QDes ?? "", + CompatText = SysFaceEntry?.QDes ?? "" + }; + using var stream = new MemoryStream(); + Serializer.Serialize(stream, qSmallFace); + return new Elem[] + { + new() + { + CommonElem = new CommonElem + { + ServiceType = 33, + PbElem = stream.ToArray(), + BusinessType = (uint)(SysFaceEntry?.AniStickerType ?? 1) + } + } + }; + } + return new Elem[] { new() { Face = new Face { Index = FaceId } } }; } @@ -63,10 +89,10 @@ IEnumerable IMessageEntity.PackElement() if (faceId != null) return new FaceEntity((ushort)faceId, false); } - if (elems.CommonElem is { ServiceType: 37, PbElem: not null } common) + if (elems.CommonElem is { ServiceType:37, PbElem: not null } common) { - var qFace = Serializer.Deserialize(common.PbElem.AsSpan()); - + var qFace = Serializer.Deserialize(common.PbElem.AsSpan()); + ushort? faceId = (ushort?)qFace.FaceId; if (faceId != null) return new FaceEntity((ushort)faceId, true); } @@ -76,9 +102,9 @@ IEnumerable IMessageEntity.PackElement() var qSmallFace = Serializer.Deserialize(append.PbElem.AsSpan()); return new FaceEntity((ushort)qSmallFace.FaceId, false); } - + return null; } - public string ToPreviewString() => $"[Face][{(IsLargeFace ? "Large" : "Small")}]: {FaceId}"; + public string ToPreviewString() => $"[Face][{(IsLargeFace ?? false ? "Large" : "Small")}]: {FaceId}"; } \ No newline at end of file diff --git a/Lagrange.Core/Message/MessageBuilder.cs b/Lagrange.Core/Message/MessageBuilder.cs index 5bf5fc34d..a20a5cd3b 100644 --- a/Lagrange.Core/Message/MessageBuilder.cs +++ b/Lagrange.Core/Message/MessageBuilder.cs @@ -102,7 +102,7 @@ public MessageBuilder Mention(uint target, string? display = null) /// /// The id of emoji /// Is the emoji large - public MessageBuilder Face(ushort id, bool isLarge = false) + public MessageBuilder Face(ushort id, bool? isLarge = false) { var faceEntity = new FaceEntity(id, isLarge); _chain.Add(faceEntity); diff --git a/Lagrange.OneBot/Core/Entity/Action/OneBotGroupJoinEmojiChain.cs b/Lagrange.OneBot/Core/Entity/Action/OneBotGroupJoinEmojiChain.cs new file mode 100644 index 000000000..9adf71032 --- /dev/null +++ b/Lagrange.OneBot/Core/Entity/Action/OneBotGroupJoinEmojiChain.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +namespace Lagrange.OneBot.Core.Entity.Action; + +public class OneBotGroupJoinEmojiChain +{ + [JsonPropertyName("group_id")] public uint GroupId { get; set; } + + [JsonPropertyName("message_id")] public int MessageId { get; set; } + + [JsonPropertyName("emoji_id")] public uint EmojiId { get; set; } +} \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Entity/Action/OneBotPrivateJoinEmojiChain.cs b/Lagrange.OneBot/Core/Entity/Action/OneBotPrivateJoinEmojiChain.cs new file mode 100644 index 000000000..47b44fd05 --- /dev/null +++ b/Lagrange.OneBot/Core/Entity/Action/OneBotPrivateJoinEmojiChain.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +namespace Lagrange.OneBot.Core.Entity.Action; + +public class OneBotPrivateJoinEmojiChain +{ + [JsonPropertyName("user_id")] public uint UserId { get; set; } + + [JsonPropertyName("message_id")] public int MessageId { get; set; } + + [JsonPropertyName("emoji_id")] public uint EmojiId { get; set; } +} \ No newline at end of file diff --git a/Lagrange.OneBot/Core/Operation/Generic/FriendJoinEmojiChainOperation.cs b/Lagrange.OneBot/Core/Operation/Generic/FriendJoinEmojiChainOperation.cs new file mode 100644 index 000000000..e6b17db93 --- /dev/null +++ b/Lagrange.OneBot/Core/Operation/Generic/FriendJoinEmojiChainOperation.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Lagrange.Core; +using Lagrange.Core.Message; +using Lagrange.Core.Common.Interface.Api; +using Lagrange.OneBot.Core.Entity.Action; +using Lagrange.OneBot.Core.Operation.Converters; +using Lagrange.OneBot.Database; +using LiteDB; + +namespace Lagrange.OneBot.Core.Operation.Generic; + + +[Operation(".join_friend_emoji_chain")] +public class FriendJoinEmojiChainOperation(LiteDatabase database) : IOperation +{ + public async Task HandleOperation(BotContext context, JsonNode? payload) + { + if (payload.Deserialize(SerializerOptions.DefaultOptions) is { } data) + { + var message = (MessageChain)database.GetCollection().FindById(data.MessageId); + bool res = await context.FriendJoinEmojiChain(data.UserId, data.EmojiId, message.Sequence); + return new OneBotResult(null, res ? 0 : -1, res ? "ok" : "failed"); + } + throw new Exception(); + } +} diff --git a/Lagrange.OneBot/Core/Operation/Generic/GroupJoinEmojiChainOperation.cs b/Lagrange.OneBot/Core/Operation/Generic/GroupJoinEmojiChainOperation.cs new file mode 100644 index 000000000..71638e322 --- /dev/null +++ b/Lagrange.OneBot/Core/Operation/Generic/GroupJoinEmojiChainOperation.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using Lagrange.Core; +using Lagrange.Core.Message; +using Lagrange.Core.Common.Interface.Api; +using Lagrange.OneBot.Core.Entity.Action; +using Lagrange.OneBot.Core.Operation.Converters; +using Lagrange.OneBot.Database; +using LiteDB; + +namespace Lagrange.OneBot.Core.Operation.Generic; + + +[Operation(".join_group_emoji_chain")] +public class GroupJoinEmojiChainOperation(LiteDatabase database) : IOperation +{ + public async Task HandleOperation(BotContext context, JsonNode? payload) + { + if (payload.Deserialize(SerializerOptions.DefaultOptions) is { } data) + { + var message = (MessageChain)database.GetCollection().FindById(data.MessageId); + bool res = await context.GroupJoinEmojiChain(data.GroupId, data.EmojiId, message.Sequence); + return new OneBotResult(null, res ? 0 : -1, res ? "ok" : "failed"); + } + throw new Exception(); + } +} \ No newline at end of file diff --git a/Lagrange.OneBot/Message/Entity/FaceSegment.cs b/Lagrange.OneBot/Message/Entity/FaceSegment.cs index 3e2f5d2cc..4fa52beea 100644 --- a/Lagrange.OneBot/Message/Entity/FaceSegment.cs +++ b/Lagrange.OneBot/Message/Entity/FaceSegment.cs @@ -10,6 +10,9 @@ public partial class FaceSegment(int id) public FaceSegment() : this(0) { } [JsonPropertyName("id")] [CQProperty] public string Id { get; set; } = id.ToString(); + + [JsonPropertyName("large")] [CQProperty] public bool? IsLarge { get; set; } + } [SegmentSubscriber(typeof(FaceEntity), "face")] @@ -19,7 +22,7 @@ public partial class FaceSegment : SegmentBase public override void Build(MessageBuilder builder, SegmentBase segment) { - if (segment is FaceSegment faceSegment) builder.Face(ushort.Parse(faceSegment.Id)); + if (segment is FaceSegment faceSegment) builder.Face(ushort.Parse(faceSegment.Id), faceSegment.IsLarge); } public override SegmentBase? FromEntity(MessageChain chain, IMessageEntity entity)