Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MarketFace #134

Merged
merged 6 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions client/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -1522,3 +1522,29 @@ func (c *QQClient) SendGroupAiRecord(groupUin uint32, chatType entity.ChatType,
}
return oidb2.ParseGroupAiRecordService(rsp)
}

// FetchMarketFaceKey 获取魔法表情key
func (c *QQClient) FetchMarketFaceKey(faceIDs ...string) ([]string, error) {
for i, v := range faceIDs {
faceIDs[i] = strings.ToLower(v)
}
pkt, err := proto.Marshal(&message.MarketFaceKeyReq{
Field1: 3,
Info: &message.MarketFaceKeyReqInfo{FaceIds: faceIDs},
})
if err != nil {
return nil, err
}
rsp, err := c.sendUniPacketAndWait("BQMallSvc.TabOpReq", pkt)
if err != nil {
return nil, err
}
var info message.MarketFaceKeyRsp
if err = proto.Unmarshal(rsp, &info); err != nil {
return nil, err
}
if info.Info == nil {
return nil, errors.New("valid ids")
}
return info.Info.Keys, nil
}
19 changes: 19 additions & 0 deletions client/packets/pb/message/action.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions client/packets/pb/message/action.proto
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,22 @@ message SsoGetC2cMsgResponse {
string FriendUid = 4;
repeated PushMsgBody Messages = 7;
}

// 魔法表情 key

message MarketFaceKeyReq {
uint32 Field1 = 1;
MarketFaceKeyReqInfo Info = 5;
}

message MarketFaceKeyReqInfo {
repeated string FaceIds = 3;
}

message MarketFaceKeyRsp {
MarketFaceKeyRspInfo Info = 5;
}

message MarketFaceKeyRspInfo {
repeated string Keys = 1;
}
31 changes: 18 additions & 13 deletions client/packets/pb/message/element.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 17 additions & 13 deletions client/packets/pb/message/element.proto
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,23 @@ message LightAppElem {
}

message MarketFace {
bytes FaceName = 1;
int32 ItemType = 2;
int32 FaceInfo = 3;
bytes FaceId = 4;
int32 TabId = 5;
int32 SubType = 6;
bytes Key = 7;
bytes Param = 8;
int32 MediaType = 9;
int32 ImageWidth = 10;
int32 ImageHeight = 11;
bytes Mobileparam = 12;
bytes PbReserve = 13;
optional string FaceName = 1; // 表情名称,UTF-8
optional uint32 ItemType = 2; // 后台二进制编码是主机字节序,不是网络,默认值 6
optional uint32 FaceInfo = 3; // 默认为1
optional bytes FaceId = 4; // 16字节,表情ID
optional uint32 TabId = 5; // 表情的分组ID
optional uint32 SubType = 6; // 表情类型: 0->None,1->魔法表情,2->gif,3->png
optional bytes Key = 7; // 16字节,表情的加密KEY
optional bytes Param = 8; // 魔法表情的播放参数
optional uint32 MediaType = 9; // 媒体类型:1->有声表情,2->动态表情秀
optional uint32 ImageWidth = 10; // 表情图片的宽度
optional uint32 ImageHeight = 11; // 表情图片的高度
optional bytes MobileParam = 12; // 手Q的播放参数,避免与PC冲突
optional bytes PbReserve = 13; // 商城表情业务控制的扩展结构,参见hummer_resv_12.proto
}

message MarketFacePbReserve {
int32 field8 = 8;
}

message NotOnlineImage {
Expand Down
23 changes: 23 additions & 0 deletions message/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,26 @@ func (e *FileElement) BuildContent() []byte {
})
return content
}

func (e *MarketFaceElement) BuildContent() []*message.Elem {
mFace := &message.MarketFace{
FaceName: proto.String(e.Summary),
ItemType: proto.Uint32(e.ItemType),
FaceInfo: proto.Uint32(1),
FaceId: e.FaceID,
TabId: proto.Uint32(e.TabID),
SubType: proto.Uint32(e.SubType),
Key: e.EncryptKey,
MediaType: proto.Uint32(e.MediaType),
ImageWidth: proto.Uint32(300),
ImageHeight: proto.Uint32(300),
MobileParam: utils.S2B(e.MagicValue),
}
mFace.PbReserve, _ = proto.Marshal(&message.MarketFacePbReserve{Field8: 1})

return []*message.Elem{{
MarketFace: mFace,
}, {
Text: &message.Text{Str: proto.Some(e.Summary)},
}}
}
42 changes: 39 additions & 3 deletions message/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,22 @@ type (
Nodes []*ForwardNode
}

MarketFaceElement struct {
Summary string
ItemType uint32
FaceInfo uint32
FaceID []byte // decoded = mediaType == 2 ? string(FaceId) : hex.EncodeToString(FaceId).toLower().trimSpace(); download url param?
TabID uint32
SubType uint32 // image type, 0 -> None 1 -> Magic Face 2 -> GIF 3 -> PNG
EncryptKey []byte // tea + xor, see EMosmUtils.class::a maybe useful?
MediaType uint32 // 1 -> Voice Face 2 -> dynamic face
MagicValue string
}

AtType int
)

const (
AtTypeGroupMember = 0 // At群成员
)
const AtTypeGroupMember = 0 // At群成员

func NewText(s string) *TextElement {
return &TextElement{Content: s}
Expand Down Expand Up @@ -375,6 +385,30 @@ func NewFace(id uint32) *FaceElement {
return &FaceElement{FaceID: id}
}

// NewMarketFace
//
// key: FetchMarketFaceKey(emojiID) 获取的值
func NewMarketFace(emojiPackID uint32, emojiID []byte, key, summary, value string) *MarketFaceElement {
return &MarketFaceElement{
Summary: summary,
ItemType: 6,
FaceInfo: 1,
FaceID: emojiID,
TabID: emojiPackID,
SubType: 3,
EncryptKey: utils.S2B(key),
MediaType: 0,
MagicValue: value,
}
}

func (e *MarketFaceElement) FaceIDString() string {
if e.MediaType == 2 {
return utils.B2S(e.FaceID)
}
return fmt.Sprintf("%x", e.FaceID)
}

func NewDice(value uint32) *FaceElement {
if value > 6 {
value = crypto.RandU32()%3 + 1
Expand Down Expand Up @@ -455,3 +489,5 @@ func (e *XMLElement) Type() ElementType {
func (e *ForwardMessage) Type() ElementType {
return Forward
}

func (e *MarketFaceElement) Type() ElementType { return MarketFace }
41 changes: 29 additions & 12 deletions message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@ type IMessage interface {
}

const (
Text ElementType = iota // 文本
Image // 图片
Face // 表情
At // 艾特
Reply // 回复
Service // 服务
Forward // 转发
File // 文件
Voice // 语音
Video // 视频
LightApp // 轻应用
RedBag // 红包
Text ElementType = iota // 文本
Image // 图片
Face // 表情
At // 艾特
Reply // 回复
Service // 服务
Forward // 转发
File // 文件
Voice // 语音
Video // 视频
LightApp // 轻应用
RedBag // 红包
MarketFace // 魔法表情
)

type (
Expand Down Expand Up @@ -399,6 +400,20 @@ func ParseMessageElements(msg []*message.Elem) []IMessageElement {
res = append(res, NewLightApp(utils.B2S(content)))
}
}

if elem.MarketFace != nil {
res = append(res, &MarketFaceElement{
Summary: elem.MarketFace.FaceName.Unwrap(),
ItemType: elem.MarketFace.ItemType.Unwrap(),
FaceInfo: elem.MarketFace.FaceInfo.Unwrap(),
FaceID: elem.MarketFace.FaceId,
TabID: elem.MarketFace.TabId.Unwrap(),
SubType: elem.MarketFace.SubType.Unwrap(),
EncryptKey: elem.MarketFace.Key,
MediaType: elem.MarketFace.MediaType.Unwrap(),
MagicValue: utils.B2S(elem.MarketFace.MobileParam),
})
}
}

return res
Expand Down Expand Up @@ -566,6 +581,8 @@ func ToReadableStringEle(elem IMessageElement) string {
return "[卡片消息]"
case *ForwardMessage:
return "[转发消息]"
case *MarketFaceElement:
return "[魔法表情]"
default:
return "[暂不支持该消息类型]"
}
Expand Down