1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 11:07:40 +08:00

feat: GetTopicChannelFeeds

This commit is contained in:
Mrs4s 2021-11-27 01:21:04 +08:00
parent e8e50b5062
commit 8b4b8269f3
No known key found for this signature in database
GPG Key ID: 3186E98FA19CE3A7
10 changed files with 450 additions and 60 deletions

View File

@ -2,6 +2,7 @@ package client
import ( import (
"fmt" "fmt"
"github.com/Mrs4s/MiraiGo/topic"
"math/rand" "math/rand"
"sort" "sort"
"time" "time"
@ -525,17 +526,17 @@ func (s *GuildService) FetchChannelInfo(guildId, channelId uint64) (*ChannelInfo
return convertChannelInfo(body.Info), nil return convertChannelInfo(body.Info), nil
} }
func (s *GuildService) GetChannelTopics(guildId, channelId uint64) error { func (s *GuildService) GetTopicChannelFeeds(guildId, channelId uint64) ([]*topic.Feed, error) {
guild := s.FindGuild(guildId) guild := s.FindGuild(guildId)
if guild == nil { if guild == nil {
return errors.New("guild not found") return nil, errors.New("guild not found")
} }
channelInfo := guild.FindChannel(channelId) channelInfo := guild.FindChannel(channelId)
if channelInfo == nil { if channelInfo == nil {
return errors.New("channel not found") return nil, errors.New("channel not found")
} }
if channelInfo.ChannelType != ChannelTypeTopic { if channelInfo.ChannelType != ChannelTypeTopic {
return errors.New("channel type error") return nil, errors.New("channel type error")
} }
req, _ := proto.Marshal(&channel.StGetChannelFeedsReq{ req, _ := proto.Marshal(&channel.StGetChannelFeedsReq{
Count: proto.Uint32(12), Count: proto.Uint32(12),
@ -571,17 +572,21 @@ func (s *GuildService) GetChannelTopics(guildId, channelId uint64) error {
packet := packets.BuildUniPacket(s.c.Uin, seq, "QChannelSvr.trpc.qchannel.commreader.ComReader.GetChannelTimelineFeeds", 1, s.c.OutGoingPacketSessionId, []byte{}, s.c.sigInfo.d2Key, payload) packet := packets.BuildUniPacket(s.c.Uin, seq, "QChannelSvr.trpc.qchannel.commreader.ComReader.GetChannelTimelineFeeds", 1, s.c.OutGoingPacketSessionId, []byte{}, s.c.sigInfo.d2Key, payload)
rsp, err := s.c.sendAndWaitDynamic(seq, packet) rsp, err := s.c.sendAndWaitDynamic(seq, packet)
if err != nil { if err != nil {
return errors.New("send packet error") return nil, errors.New("send packet error")
} }
pkg := new(qweb.QWebRsp) pkg := new(qweb.QWebRsp)
body := new(channel.StGetChannelFeedsRsp) body := new(channel.StGetChannelFeedsRsp)
if err = proto.Unmarshal(rsp, pkg); err != nil { if err = proto.Unmarshal(rsp, pkg); err != nil {
return errors.Wrap(err, "failed to unmarshal protobuf message") return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
} }
if err = proto.Unmarshal(pkg.BusiBuff, body); err != nil { if err = proto.Unmarshal(pkg.BusiBuff, body); err != nil {
return errors.Wrap(err, "failed to unmarshal protobuf message") return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
} }
return nil feeds := make([]*topic.Feed, 0, len(body.VecFeed))
for _, f := range body.VecFeed {
feeds = append(feeds, topic.DecodeFeed(f))
}
return feeds, nil
} }
/* need analysis /* need analysis

View File

@ -176,6 +176,25 @@ func (x *StChannelSign) GetChannelId() uint64 {
return 0 return 0
} }
type StEmotionReactionInfo struct {
Id *string `protobuf:"bytes,1,opt"`
EmojiReactionList []*EmojiReaction `protobuf:"bytes,2,rep"`
}
func (x *StEmotionReactionInfo) GetId() string {
if x != nil && x.Id != nil {
return *x.Id
}
return ""
}
func (x *StEmotionReactionInfo) GetEmojiReactionList() []*EmojiReaction {
if x != nil {
return x.EmojiReactionList
}
return nil
}
type StCommonExt struct { type StCommonExt struct {
MapInfo []*CommonEntry `protobuf:"bytes,1,rep"` MapInfo []*CommonEntry `protobuf:"bytes,1,rep"`
AttachInfo *string `protobuf:"bytes,2,opt"` AttachInfo *string `protobuf:"bytes,2,opt"`

View File

@ -4,6 +4,8 @@ package channel;
option go_package = "pb/channel;channel"; option go_package = "pb/channel;channel";
import "pb/channel/MsgResponsesSvr.proto";
message ChannelUserInfo { message ChannelUserInfo {
optional ClientIdentity clientIdentity = 1; optional ClientIdentity clientIdentity = 1;
optional uint32 memberType = 2; optional uint32 memberType = 2;
@ -51,12 +53,13 @@ message StEmojiReaction {
optional bool isClicked = 4; optional bool isClicked = 4;
optional bool isDefaultEmoji = 10001; optional bool isDefaultEmoji = 10001;
} }
*/
message StEmotionReactionInfo { message StEmotionReactionInfo {
optional string id = 1; optional string id = 1;
repeated StEmojiReaction emojiReactionList = 2; repeated EmojiReaction emojiReactionList = 2;
} }
*/
message StCommonExt { message StCommonExt {
repeated CommonEntry mapInfo = 1; repeated CommonEntry mapInfo = 1;

View File

@ -580,31 +580,31 @@ func (x *StExternalMedalWallInfo) GetNeedShowEntrance() bool {
} }
type StFeed struct { type StFeed struct {
Id *string `protobuf:"bytes,1,opt"` Id *string `protobuf:"bytes,1,opt"`
Title *StRichText `protobuf:"bytes,2,opt"` Title *StRichText `protobuf:"bytes,2,opt"`
Subtitle *StRichText `protobuf:"bytes,3,opt"` Subtitle *StRichText `protobuf:"bytes,3,opt"`
Poster *StUser `protobuf:"bytes,4,opt"` Poster *StUser `protobuf:"bytes,4,opt"`
Videos []*StVideo `protobuf:"bytes,5,rep"` Videos []*StVideo `protobuf:"bytes,5,rep"`
Contents *StRichText `protobuf:"bytes,6,opt"` Contents *StRichText `protobuf:"bytes,6,opt"`
CreateTime *uint64 `protobuf:"varint,7,opt"` CreateTime *uint64 `protobuf:"varint,7,opt"`
EmotionReaction *EmojiReaction `protobuf:"bytes,8,opt"` EmotionReaction *StEmotionReactionInfo `protobuf:"bytes,8,opt"`
CommentCount *uint32 `protobuf:"varint,9,opt"` CommentCount *uint32 `protobuf:"varint,9,opt"`
VecComment []*StComment `protobuf:"bytes,10,rep"` VecComment []*StComment `protobuf:"bytes,10,rep"`
Share *StShare `protobuf:"bytes,11,opt"` Share *StShare `protobuf:"bytes,11,opt"`
VisitorInfo *StVisitor `protobuf:"bytes,12,opt"` VisitorInfo *StVisitor `protobuf:"bytes,12,opt"`
Images []*StImage `protobuf:"bytes,13,rep"` Images []*StImage `protobuf:"bytes,13,rep"`
PoiInfo *StPoiInfoV2 `protobuf:"bytes,14,opt"` PoiInfo *StPoiInfoV2 `protobuf:"bytes,14,opt"`
TagInfos []*StTagInfo `protobuf:"bytes,15,rep"` TagInfos []*StTagInfo `protobuf:"bytes,15,rep"`
BusiReport []byte `protobuf:"bytes,16,opt"` BusiReport []byte `protobuf:"bytes,16,opt"`
OpMask []uint32 `protobuf:"varint,17,rep"` OpMask []uint32 `protobuf:"varint,17,rep"`
Opinfo *StOpinfo `protobuf:"bytes,18,opt"` Opinfo *StOpinfo `protobuf:"bytes,18,opt"`
ExtInfo []*CommonEntry `protobuf:"bytes,19,rep"` ExtInfo []*CommonEntry `protobuf:"bytes,19,rep"`
PatternInfo *string `protobuf:"bytes,20,opt"` PatternInfo *string `protobuf:"bytes,20,opt"`
ChannelInfo *StChannelInfo `protobuf:"bytes,21,opt"` ChannelInfo *StChannelInfo `protobuf:"bytes,21,opt"`
CreateTimeNs *uint64 `protobuf:"varint,22,opt"` CreateTimeNs *uint64 `protobuf:"varint,22,opt"`
Summary *StFeedSummary `protobuf:"bytes,23,opt"` Summary *StFeedSummary `protobuf:"bytes,23,opt"`
RecomInfo *StRecomInfo `protobuf:"bytes,24,opt"` RecomInfo *StRecomInfo `protobuf:"bytes,24,opt"`
Meta *FeedMetaData `protobuf:"bytes,25,opt"` Meta *FeedMetaData `protobuf:"bytes,25,opt"`
} }
func (x *StFeed) GetId() string { func (x *StFeed) GetId() string {
@ -656,7 +656,7 @@ func (x *StFeed) GetCreateTime() uint64 {
return 0 return 0
} }
func (x *StFeed) GetEmotionReaction() *EmojiReaction { func (x *StFeed) GetEmotionReaction() *StEmotionReactionInfo {
if x != nil { if x != nil {
return x.EmotionReaction return x.EmotionReaction
} }
@ -1734,9 +1734,10 @@ func (x *StPoiInfoV2) GetDisplayName() string {
} }
type StPrePullCacheFeed struct { type StPrePullCacheFeed struct {
Id *string `protobuf:"bytes,1,opt"` Id *string `protobuf:"bytes,1,opt"`
Poster *StUser `protobuf:"bytes,2,opt"` Poster *StUser `protobuf:"bytes,2,opt"`
CreateTime *uint64 `protobuf:"varint,3,opt"` //repeated GuildCommon.BytesEntry busiTranparent = 4; CreateTime *uint64 `protobuf:"varint,3,opt"`
BusiTranparent []*BytesEntry `protobuf:"bytes,4,rep"`
} }
func (x *StPrePullCacheFeed) GetId() string { func (x *StPrePullCacheFeed) GetId() string {
@ -1760,6 +1761,13 @@ func (x *StPrePullCacheFeed) GetCreateTime() uint64 {
return 0 return 0
} }
func (x *StPrePullCacheFeed) GetBusiTranparent() []*BytesEntry {
if x != nil {
return x.BusiTranparent
}
return nil
}
type StProxyInfo struct { type StProxyInfo struct {
CmdId *int32 `protobuf:"varint,1,opt"` CmdId *int32 `protobuf:"varint,1,opt"`
SubCmdId *int32 `protobuf:"varint,2,opt"` SubCmdId *int32 `protobuf:"varint,2,opt"`

View File

@ -4,7 +4,6 @@ package channel;
option go_package = "pb/channel;channel"; option go_package = "pb/channel;channel";
import "pb/channel/MsgResponsesSvr.proto";
import "pb/channel/GuildChannelBase.proto"; import "pb/channel/GuildChannelBase.proto";
message ContentMetaData { message ContentMetaData {
@ -130,7 +129,7 @@ message StFeed {
repeated StVideo videos = 5; repeated StVideo videos = 5;
optional StRichText contents = 6; optional StRichText contents = 6;
optional uint64 createTime = 7; optional uint64 createTime = 7;
optional EmojiReaction emotionReaction = 8; optional StEmotionReactionInfo emotionReaction = 8;
optional uint32 commentCount = 9; optional uint32 commentCount = 9;
repeated StComment vecComment = 10; repeated StComment vecComment = 10;
optional StShare share = 11; optional StShare share = 11;
@ -328,7 +327,7 @@ message StPrePullCacheFeed {
optional string id = 1; optional string id = 1;
optional StUser poster = 2; optional StUser poster = 2;
optional uint64 createTime = 3; optional uint64 createTime = 3;
//repeated GuildCommon.BytesEntry busiTranparent = 4; repeated BytesEntry busiTranparent = 4;
} }
message StProxyInfo { message StProxyInfo {

View File

@ -1534,9 +1534,10 @@ func (x *SwitchDetail) GetPlatform() uint32 {
type SwitchLiveRoom struct { type SwitchLiveRoom struct {
GuildId *uint64 `protobuf:"varint,1,opt"` GuildId *uint64 `protobuf:"varint,1,opt"`
ChannelId *uint64 `protobuf:"varint,2,opt"` ChannelId *uint64 `protobuf:"varint,2,opt"`
RoomId *uint64 `protobuf:"varint,3,opt"` // optional uint64 roomId = 3;
Tinyid *uint64 `protobuf:"varint,4,opt"` // optional uint64 tinyid = 4;
Action *uint32 `protobuf:"varint,5,opt"` UserInfo *SwitchLiveRoomUserInfo `protobuf:"bytes,3,opt"`
Action *uint32 `protobuf:"varint,4,opt"` // JOIN = 1 QUIT = 2
} }
func (x *SwitchLiveRoom) GetGuildId() uint64 { func (x *SwitchLiveRoom) GetGuildId() uint64 {
@ -1553,18 +1554,11 @@ func (x *SwitchLiveRoom) GetChannelId() uint64 {
return 0 return 0
} }
func (x *SwitchLiveRoom) GetRoomId() uint64 { func (x *SwitchLiveRoom) GetUserInfo() *SwitchLiveRoomUserInfo {
if x != nil && x.RoomId != nil { if x != nil {
return *x.RoomId return x.UserInfo
} }
return 0 return nil
}
func (x *SwitchLiveRoom) GetTinyid() uint64 {
if x != nil && x.Tinyid != nil {
return *x.Tinyid
}
return 0
} }
func (x *SwitchLiveRoom) GetAction() uint32 { func (x *SwitchLiveRoom) GetAction() uint32 {
@ -1574,6 +1568,25 @@ func (x *SwitchLiveRoom) GetAction() uint32 {
return 0 return 0
} }
type SwitchLiveRoomUserInfo struct {
TinyId *uint64 `protobuf:"varint,1,opt"`
Nickname *string `protobuf:"bytes,2,opt"`
}
func (x *SwitchLiveRoomUserInfo) GetTinyId() uint64 {
if x != nil && x.TinyId != nil {
return *x.TinyId
}
return 0
}
func (x *SwitchLiveRoomUserInfo) GetNickname() string {
if x != nil && x.Nickname != nil {
return *x.Nickname
}
return ""
}
type SwitchVoiceChannel struct { type SwitchVoiceChannel struct {
MemberId *uint64 `protobuf:"varint,1,opt"` MemberId *uint64 `protobuf:"varint,1,opt"`
EnterDetail *SwitchDetail `protobuf:"bytes,2,opt"` EnterDetail *SwitchDetail `protobuf:"bytes,2,opt"`

View File

@ -286,9 +286,15 @@ message SwitchDetail {
message SwitchLiveRoom { message SwitchLiveRoom {
optional uint64 guildId = 1; optional uint64 guildId = 1;
optional uint64 channelId = 2; optional uint64 channelId = 2;
optional uint64 roomId = 3; // optional uint64 roomId = 3;
optional uint64 tinyid = 4; // optional uint64 tinyid = 4;
optional uint32 action = 5; optional SwitchLiveRoomUserInfo userInfo = 3;
optional uint32 action = 4; // JOIN = 1 QUIT = 2
}
message SwitchLiveRoomUserInfo {
optional uint64 tinyId = 1;
optional string nickname = 2;
} }
message SwitchVoiceChannel { message SwitchVoiceChannel {

View File

@ -607,5 +607,8 @@ func ToReadableString(m []IMessageElement) string {
} }
func FaceNameById(id int) string { func FaceNameById(id int) string {
return faceMap[id] if name, ok := faceMap[id]; ok {
return name
}
return "未知表情"
} }

141
topic/elements.go Normal file
View File

@ -0,0 +1,141 @@
package topic
import (
"strconv"
)
type (
TextElement struct {
Content string
}
EmojiElement struct {
Index int32
Id string
Name string
}
AtElement struct {
Id string
TinyId uint64
Nickname string
}
ChannelQuoteElement struct {
GuildId uint64
ChannelId uint64
DisplayText string
}
UrlQuoteElement struct {
Url string
DisplayText string
}
)
func selectContent(b bool, c1, c2 content) content {
if b {
return c1
}
return c2
}
func (e *TextElement) pack(patternId string, isPatternData bool) content {
return selectContent(isPatternData,
content{
"type": 1,
"style": "n",
"text": e.Content,
"children": make([]int, 0),
},
content{
"type": 1,
"text_content": content{
"text": e.Content,
},
})
}
func (e *EmojiElement) pack(patternId string, isPatternData bool) content {
return selectContent(isPatternData,
content{
"type": 2,
"id": patternId,
"emojiType": "1",
"emojiId": e.Id,
},
content{
"type": 4,
"pattern_id": patternId,
"emoji_content": content{
"type": "1",
"id": e.Id,
},
})
}
func (e *AtElement) pack(patternId string, isPatternData bool) content {
return selectContent(isPatternData,
content{
"type": 3,
"id": patternId,
"user": content{
"id": strconv.FormatUint(e.TinyId, 10),
"nick": e.Nickname,
},
},
content{
"type": 2,
"pattern_id": patternId,
"at_content": content{
"type": 1,
"user": content{
"id": e.Id,
"nick": e.Nickname,
},
},
})
}
func (e *ChannelQuoteElement) pack(patternId string, isPatternData bool) content {
return selectContent(isPatternData,
content{
"type": 4,
"id": patternId,
"guild_info": content{
"channel_id": strconv.FormatUint(e.ChannelId, 10),
"name": e.DisplayText,
},
},
content{
"type": 5,
"pattern_id": patternId,
"channel_content": content{
"channel_info": content{
"name": e.DisplayText,
"sign": content{
"guild_id": strconv.FormatUint(e.GuildId, 10),
"channel_id": strconv.FormatUint(e.ChannelId, 10),
},
},
},
})
}
func (e *UrlQuoteElement) pack(patternId string, isPatternData bool) content {
return selectContent(isPatternData,
content{
"type": 5,
"desc": e.DisplayText,
"href": e.Url,
"id": patternId,
},
content{
"type": 3,
"pattern_id": patternId,
"url_content": content{
"url": e.Url,
"displayText": e.DisplayText,
},
})
}

193
topic/feed.go Normal file
View File

@ -0,0 +1,193 @@
package topic
import (
"encoding/json"
"fmt"
"github.com/Mrs4s/MiraiGo/client/pb/channel"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
"strconv"
"strings"
"sync/atomic"
"time"
)
type (
Feed struct {
Id string
Title string
SubTitle string
CreateTime int64
Poster *FeedPoster
GuildId uint64
ChannelId uint64
Images []*FeedImageInfo
Videos []*FeedVideoInfo
Contents []IFeedRichContentElement
}
FeedPoster struct {
TinyId uint64
TinyIdStr string
Nickname string
IconUrl string
}
FeedImageInfo struct {
FileId string
PatternId string
Url string
Width uint32
Height uint32
}
FeedVideoInfo struct {
FileId string
PatternId string
Url string
Width uint32
Height uint32
// CoverImage FeedImageInfo
}
IFeedRichContentElement interface {
pack(patternId string, isPatternData bool) content
}
content map[string]interface{}
)
var (
globalBlockId int64 = 0
)
func genBlockId() string {
id := atomic.AddInt64(&globalBlockId, 1)
return fmt.Sprintf("%v_%v_%v", time.Now().UnixMilli(), utils.RandomStringRange(4, "0123456789"), id)
}
func (f *Feed) ToSendingPayload(selfUin int64) string {
c := content{ // todo: support media
"images": make([]int, 0),
"videos": make([]int, 0),
"poster": content{
"id": f.Poster.TinyIdStr,
"nick": f.Poster.Nickname,
},
"channelInfo": content{
"sign": content{
"guild_id": strconv.FormatUint(f.GuildId, 10),
"channel_id": strconv.FormatUint(f.ChannelId, 10),
},
},
"title": content{
"contents": []content{
(&TextElement{Content: f.Title}).pack("", false),
},
},
}
patternInfo := []content{
{
"id": genBlockId(),
"type": "blockParagraph",
"data": []content{
(&TextElement{Content: f.Title}).pack("", true),
},
},
}
patternData := make([]content, len(f.Contents))
contents := make([]content, len(f.Contents))
for i, c := range f.Contents {
patternId := fmt.Sprintf("o%v_%v_%v", selfUin, time.Now().Format("2006_01_02_15_04_05"), strings.ToLower(utils.RandomStringRange(16, "0123456789abcdef"))) // readCookie("uin")_yyyy_MM_dd_hh_mm_ss_randomHex(16)
contents[i] = c.pack(patternId, false)
patternData[i] = c.pack(patternId, true)
}
c["contents"] = content{"contents": contents}
patternInfo = append(patternInfo, content{
"id": genBlockId(),
"type": "blockParagraph",
"data": patternData,
})
packedPattern, _ := json.Marshal(patternInfo)
c["patternInfo"] = utils.B2S(packedPattern)
packedContent, _ := json.Marshal(c)
return utils.B2S(packedContent)
}
func DecodeFeed(p *channel.StFeed) *Feed {
f := &Feed{
Id: p.GetId(),
Title: p.Title.Contents[0].TextContent.GetText(),
SubTitle: "",
CreateTime: int64(p.GetCreateTime()),
GuildId: p.ChannelInfo.Sign.GetGuildId(),
ChannelId: p.ChannelInfo.Sign.GetChannelId(),
}
if p.Subtitle != nil && len(p.Subtitle.Contents) > 0 {
f.SubTitle = p.Subtitle.Contents[0].TextContent.GetText()
}
if p.Poster != nil {
tinyId, _ := strconv.ParseUint(p.Poster.GetId(), 10, 64)
f.Poster = &FeedPoster{
TinyId: tinyId,
TinyIdStr: p.Poster.GetId(),
Nickname: p.Poster.GetNick(),
}
if p.Poster.Icon != nil {
f.Poster.IconUrl = p.Poster.Icon.GetIconUrl()
}
}
for _, video := range p.Videos {
f.Videos = append(f.Videos, &FeedVideoInfo{
FileId: video.GetFileId(),
PatternId: video.GetPatternId(),
Url: video.GetPlayUrl(),
Width: video.GetWidth(),
Height: video.GetHeight(),
})
}
for _, image := range p.Images {
f.Images = append(f.Images, &FeedImageInfo{
FileId: image.GetPicId(),
PatternId: image.GetPatternId(),
Url: image.GetPicUrl(),
Width: image.GetWidth(),
Height: image.GetHeight(),
})
}
for _, c := range p.Contents.Contents {
if c.TextContent != nil {
f.Contents = append(f.Contents, &TextElement{Content: c.TextContent.GetText()})
}
if c.EmojiContent != nil {
id, _ := strconv.ParseInt(c.EmojiContent.GetId(), 10, 64)
f.Contents = append(f.Contents, &EmojiElement{
Index: int32(id),
Id: c.EmojiContent.GetId(),
Name: message.FaceNameById(int(id)),
})
}
if c.ChannelContent != nil && c.ChannelContent.ChannelInfo != nil {
f.Contents = append(f.Contents, &ChannelQuoteElement{
GuildId: c.ChannelContent.ChannelInfo.Sign.GetGuildId(),
ChannelId: c.ChannelContent.ChannelInfo.Sign.GetChannelId(),
DisplayText: c.ChannelContent.ChannelInfo.GetName(),
})
}
if c.AtContent != nil && c.AtContent.User != nil {
tinyId, _ := strconv.ParseUint(c.AtContent.User.GetId(), 10, 64)
f.Contents = append(f.Contents, &AtElement{
Id: c.AtContent.User.GetId(),
TinyId: tinyId,
Nickname: c.AtContent.User.GetNick(),
})
}
if c.UrlContent != nil {
f.Contents = append(f.Contents, &UrlQuoteElement{
Url: c.UrlContent.GetUrl(),
DisplayText: c.UrlContent.GetDisplayText(),
})
}
}
return f
}