1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-05-06 12:03:50 +08:00

coolq: unify converting IMessage to string&array message

This commit is contained in:
wdvxdr 2022-05-25 15:24:48 +08:00
parent 810c781c25
commit 296668441f
No known key found for this signature in database
GPG Key ID: 703F8C071DE7A1B6
6 changed files with 195 additions and 216 deletions

View File

@ -1577,7 +1577,7 @@ func (bot *CQBot) CQGetMessage(messageID int32) global.MSG {
// @route(get_guild_msg)
func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
source, seq := decodeGuildMessageID(messageID)
if source == nil {
if source.SourceType == 0 {
log.Warnf("获取消息时出现错误: 无效消息ID")
return Failed(100, "INVALID_MESSAGE_ID", "无效消息ID")
}
@ -1613,7 +1613,7 @@ func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
"tiny_id": fU64(pull[0].Sender.TinyId),
"nickname": pull[0].Sender.Nickname,
}
m["message"] = ToFormattedMessage(pull[0].Elements, *source, false)
m["message"] = ToFormattedMessage(pull[0].Elements, source, false)
m["reactions"] = convertReactions(pull[0].Reactions)
bot.InsertGuildChannelMessage(pull[0])
} else {
@ -1628,7 +1628,7 @@ func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
"tiny_id": fU64(channelMsgByDB.Attribute.SenderTinyID),
"nickname": channelMsgByDB.Attribute.SenderName,
}
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel), *source)
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel), source, false)
}
case message.SourceGuildDirect:
// todo(mrs4s): 支持 direct 消息

View File

@ -630,13 +630,13 @@ func encodeGuildMessageID(primaryID, subID, seq uint64, source message.SourceTyp
}))
}
func decodeGuildMessageID(id string) (source *message.Source, seq uint64) {
func decodeGuildMessageID(id string) (source message.Source, seq uint64) {
b, _ := base64.StdEncoding.DecodeString(id)
if len(b) < 25 {
return
}
r := binary.NewReader(b)
source = &message.Source{
source = message.Source{
SourceType: message.SourceType(r.ReadByte()),
PrimaryID: r.ReadInt64(),
SecondaryID: r.ReadInt64(),

View File

@ -2,6 +2,7 @@ package coolq
import (
"strconv"
"strings"
"github.com/Mrs4s/MiraiGo/topic"
@ -64,7 +65,7 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) global.MSG {
SourceType: message.SourceGroup,
PrimaryID: m.GroupCode,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source, true)
postType := "message"
if m.Sender.Uin == bot.Client.Uin {
postType = "message_sent"
@ -211,6 +212,15 @@ func convertReactions(reactions []*message.GuildMessageEmojiReaction) (r []globa
return
}
func toStringMessage(m []message.IMessageElement, source message.Source, raw bool) string {
elems := toElements(m, source, raw)
var sb strings.Builder
for _, elem := range elems {
sb.WriteString(elem.CQCode())
}
return sb.String()
}
func fU64(v uint64) string {
return strconv.FormatUint(v, 10)
}

View File

@ -92,9 +92,12 @@ func replyID(r *message.ReplyElement, source message.Source) int32 {
return db.ToGlobalID(id, seq)
}
// ToArrayMessage 将消息元素数组转为MSG数组以用于消息上报
func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []global.MSG) {
r = make([]global.MSG, 0, len(e))
// toElements 将消息元素数组转为MSG数组以用于消息上报
func toElements(e []message.IMessageElement, source message.Source, raw bool) (r []cqcode.Element) {
type pair = cqcode.Pair // simplify code
type pairs = []pair
r = make([]cqcode.Element, 0, len(e))
m := &message.SendingMessage{Elements: e}
reply := m.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
@ -103,26 +106,24 @@ func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []glo
if reply != nil && source.SourceType&(message.SourceGroup|message.SourcePrivate) != 0 {
replyElem := reply.(*message.ReplyElement)
id := replyID(replyElem, source)
if base.ExtraReplyData {
r = append(r, global.MSG{
"type": "reply",
"data": map[string]string{
"id": strconv.FormatInt(int64(id), 10),
"seq": strconv.FormatInt(int64(replyElem.ReplySeq), 10),
"qq": strconv.FormatInt(replyElem.Sender, 10),
"time": strconv.FormatInt(int64(replyElem.Time), 10),
"text": ToStringMessage(replyElem.Elements, source),
},
})
} else {
r = append(r, global.MSG{
"type": "reply",
"data": map[string]string{"id": strconv.FormatInt(int64(id), 10)},
})
elem := cqcode.Element{
Type: "reply",
Data: pairs{
{"id", strconv.FormatInt(int64(id), 10)},
},
}
if base.ExtraReplyData {
elem.Data = append(elem.Data,
pair{K: "seq", V: strconv.FormatInt(int64(replyElem.ReplySeq), 10)},
pair{K: "qq", V: strconv.FormatInt(replyElem.Sender, 10)},
pair{K: "time", V: strconv.FormatInt(int64(replyElem.Time), 10)},
pair{K: "text", V: toStringMessage(replyElem.Elements, source, true)},
)
}
r = append(r, elem)
}
for i, elem := range e {
var m global.MSG
var m cqcode.Element
switch o := elem.(type) {
case *message.ReplyElement:
if base.RemoveReplyAt && i+1 < len(e) {
@ -133,103 +134,143 @@ func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []glo
}
continue
case *message.TextElement:
m = global.MSG{
"type": "text",
"data": map[string]string{"text": o.Content},
m = cqcode.Element{
Type: "text",
Data: pairs{
{"text", o.Content},
},
}
case *message.LightAppElement:
m = global.MSG{
"type": "json",
"data": map[string]string{"data": o.Content},
m = cqcode.Element{
Type: "json",
Data: pairs{
{"data", o.Content},
},
}
case *message.AtElement:
target := "all"
if o.Target != 0 {
target = strconv.FormatUint(uint64(o.Target), 10)
}
m = global.MSG{
"type": "at",
"data": map[string]string{"qq": target},
m = cqcode.Element{
Type: "at",
Data: pairs{
{"qq", target},
},
}
case *message.RedBagElement:
m = global.MSG{
"type": "redbag",
"data": map[string]string{"title": o.Title},
m = cqcode.Element{
Type: "redbag",
Data: pairs{
{"title", o.Title},
},
}
case *message.ForwardElement:
m = global.MSG{
"type": "forward",
"data": map[string]string{"id": o.ResId},
m = cqcode.Element{
Type: "forward",
Data: pairs{
{"id", o.ResId},
},
}
case *message.FaceElement:
m = global.MSG{
"type": "face",
"data": map[string]string{"id": strconv.FormatInt(int64(o.Index), 10)},
m = cqcode.Element{
Type: "face",
Data: pairs{
{"id", strconv.FormatInt(int64(o.Index), 10)},
},
}
case *message.VoiceElement:
m = global.MSG{
"type": "record",
"data": map[string]string{"file": o.Name, "url": o.Url},
m = cqcode.Element{
Type: "record",
Data: pairs{
{"file", o.Name},
{"url", o.Url},
},
}
case *message.ShortVideoElement:
m = global.MSG{
"type": "video",
"data": map[string]string{"file": o.Name, "url": o.Url},
m = cqcode.Element{
Type: "video",
Data: pairs{
{"file", o.Name},
{"url", o.Url},
},
}
case *message.GroupImageElement:
data := map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url, "subType": strconv.FormatInt(int64(o.ImageBizType), 10)}
data := pairs{
{"file", hex.EncodeToString(o.Md5) + ".image"},
{"subType", strconv.FormatInt(int64(o.ImageBizType), 10)},
}
if raw {
data = append(data, pair{K: "url", V: o.Url})
}
switch {
case o.Flash:
data["type"] = "flash"
data = append(data, pair{K: "type", V: "flash"})
case o.EffectID != 0:
data["type"] = "show"
data["id"] = strconv.FormatInt(int64(o.EffectID), 10)
data = append(data, pair{K: "type", V: "show"})
data = append(data, pair{K: "id", V: strconv.FormatInt(int64(o.EffectID), 10)})
}
m = global.MSG{
"type": "image",
"data": data,
m = cqcode.Element{
Type: "image",
Data: data,
}
case *message.GuildImageElement:
data := map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url}
m = global.MSG{
"type": "image",
"data": data,
data := pairs{
{"file", hex.EncodeToString(o.Md5) + ".image"},
}
if raw {
data = append(data, pair{K: "url", V: o.Url})
}
m = cqcode.Element{
Type: "image",
Data: data,
}
case *message.FriendImageElement:
data := map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url}
if o.Flash {
data["type"] = "flash"
data := pairs{
{"file", hex.EncodeToString(o.Md5) + ".image"},
}
m = global.MSG{
"type": "image",
"data": data,
if raw {
data = append(data, pair{K: "url", V: o.Url})
}
if o.Flash {
data = append(data, pair{K: "type", V: "flash"})
}
m = cqcode.Element{
Type: "image",
Data: data,
}
case *message.DiceElement:
m = global.MSG{
"type": "dice",
"data": map[string]string{"value": strconv.FormatInt(int64(o.Value), 10)},
m = cqcode.Element{
Type: "dice",
Data: pairs{
{"value", strconv.FormatInt(int64(o.Value), 10)},
},
}
case *message.MarketFaceElement:
m = global.MSG{
"type": "text",
"data": map[string]string{"text": o.Name},
m = cqcode.Element{
Type: "text",
Data: pairs{
{"text", o.Name},
},
}
case *message.ServiceElement:
if isOk := strings.Contains(o.Content, "<?xml"); isOk {
m = global.MSG{
"type": "xml",
"data": map[string]string{"data": o.Content, "resid": strconv.FormatInt(int64(o.Id), 10)},
}
} else {
m = global.MSG{
"type": "json",
"data": map[string]string{"data": o.Content, "resid": strconv.FormatInt(int64(o.Id), 10)},
}
m = cqcode.Element{
Type: "xml",
Data: pairs{
{"data", o.Content},
{"resid", o.ResId},
},
}
if !strings.Contains(o.Content, "<?xml") {
m.Type = "json"
}
case *message.AnimatedSticker:
m = global.MSG{
"type": "face",
"data": map[string]string{"id": strconv.FormatInt(int64(o.ID), 10), "type": "sticker"},
m = cqcode.Element{
Type: "face",
Data: pairs{
{"id", strconv.FormatInt(int64(o.ID), 10)},
{"type", "sticker"},
},
}
default:
continue
@ -239,129 +280,6 @@ func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []glo
return
}
// ToStringMessage 将消息元素数组转为字符串以用于消息上报
func ToStringMessage(e []message.IMessageElement, source message.Source, isRaw ...bool) (r string) {
sb := global.NewBuffer()
sb.Reset()
write := func(format string, a ...interface{}) {
_, _ = fmt.Fprintf(sb, format, a...)
}
ur := false
if len(isRaw) != 0 {
ur = isRaw[0]
}
// 方便
m := &message.SendingMessage{Elements: e}
reply := m.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
return ok
})
if reply != nil && source.SourceType&(message.SourceGroup|message.SourcePrivate) != 0 {
replyElem := reply.(*message.ReplyElement)
id := replyID(replyElem, source)
if base.ExtraReplyData {
write("[CQ:reply,id=%d,seq=%d,qq=%d,time=%d,text=%s]",
id, replyElem.ReplySeq, replyElem.Sender, replyElem.Time,
cqcode.EscapeValue(ToStringMessage(replyElem.Elements, source)))
} else {
write("[CQ:reply,id=%d]", id)
}
}
for i, elem := range e {
switch o := elem.(type) {
case *message.ReplyElement:
if base.RemoveReplyAt && len(e) > i+1 {
elem, ok := e[i+1].(*message.AtElement)
if ok && elem.Target == o.Sender {
e[i+1] = nil
}
}
case *message.TextElement:
sb.WriteString(cqcode.EscapeText(o.Content))
case *message.AtElement:
if o.Target == 0 {
write("[CQ:at,qq=all]")
continue
}
write("[CQ:at,qq=%d]", uint64(o.Target))
case *message.RedBagElement:
write("[CQ:redbag,title=%s]", o.Title)
case *message.ForwardElement:
write("[CQ:forward,id=%s]", o.ResId)
case *message.FaceElement:
write(`[CQ:face,id=%d]`, o.Index)
case *message.VoiceElement:
if ur {
write(`[CQ:record,file=%s]`, o.Name)
} else {
write(`[CQ:record,file=%s,url=%s]`, o.Name, cqcode.EscapeValue(o.Url))
}
case *message.ShortVideoElement:
if ur {
write(`[CQ:video,file=%s]`, o.Name)
} else {
write(`[CQ:video,file=%s,url=%s]`, o.Name, cqcode.EscapeValue(o.Url))
}
case *message.GroupImageElement:
var arg string
if o.Flash {
arg = ",type=flash"
} else if o.EffectID != 0 {
arg = ",type=show,id=" + strconv.FormatInt(int64(o.EffectID), 10)
}
arg += ",subType=" + strconv.FormatInt(int64(o.ImageBizType), 10)
if ur {
write("[CQ:image,file=%x.image%s]", o.Md5, arg)
} else {
write("[CQ:image,file=%x.image,url=%s%s]", o.Md5, cqcode.EscapeValue(o.Url), arg)
}
case *message.FriendImageElement:
var arg string
if o.Flash {
arg = ",type=flash"
}
if ur {
write("[CQ:image,file=%x.image%s]", o.Md5, arg)
} else {
write("[CQ:image,file=%x.image,url=%s%s]", cqcode.EscapeValue(o.Url), arg)
}
case *LocalImageElement:
var arg string
if o.Flash {
arg = ",type=flash"
}
data, err := os.ReadFile(o.File)
if err == nil {
m := md5.Sum(data)
if ur {
write("[CQ:image,file=%x.image%s]", m[:], arg)
} else {
write("[CQ:image,file=%x.image,url=%s%s]", m[:], cqcode.EscapeValue(o.URL), arg)
}
}
case *message.GuildImageElement:
write("[CQ:image,file=%x.image,url=%s]", o.Md5, cqcode.EscapeValue(o.Url))
case *message.DiceElement:
write("[CQ:dice,value=%v]", o.Value)
case *message.MarketFaceElement:
sb.WriteString(o.Name)
case *message.ServiceElement:
if isOk := strings.Contains(o.Content, "<?xml"); isOk {
write(`[CQ:xml,data=%s,resid=%d]`, cqcode.EscapeValue(o.Content), o.Id)
} else {
write(`[CQ:json,data=%s,resid=%d]`, cqcode.EscapeValue(o.Content), o.Id)
}
case *message.LightAppElement:
write(`[CQ:json,data=%s]`, cqcode.EscapeValue(o.Content))
case *message.AnimatedSticker:
write(`[CQ:face,id=%d,type=sticker]`, o.ID)
}
}
r = sb.String() // 内部已拷贝
global.PutBuffer(sb)
return
}
// ToMessageContent 将消息转换成 Content. 忽略 Reply
// 不同于 onebot 的 Array Message, 此函数转换出来的 Content 的 data 段为实际类型
// 方便数据库查询

51
coolq/cqcode/element.go Normal file
View File

@ -0,0 +1,51 @@
package cqcode
import (
"fmt"
"strings"
"github.com/Mrs4s/go-cqhttp/global"
)
type Element struct {
Type string
Data []Pair
}
type Pair struct {
K string
V string
}
func (e *Element) CQCode() string {
if e.Type == "text" {
return EscapeText(e.Data[0].V) // must be {"text": value}
}
var sb strings.Builder
sb.WriteString("[CQ:")
sb.WriteString(e.Type)
for _, data := range e.Data {
sb.WriteByte(',')
sb.WriteString(data.K)
sb.WriteByte('=')
sb.WriteString(EscapeValue(data.V))
}
sb.WriteByte(']')
return sb.String()
}
func (e *Element) MarshalJSON() ([]byte, error) {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
fmt.Fprintf(buf, `{"type":"%s","data":{`, e.Type)
for i, data := range e.Data {
if i != 0 {
buf.WriteByte(',')
}
fmt.Fprintf(buf, `"%s":%q`, data.K, data.V)
}
buf.WriteString(`}}`)
return append([]byte(nil), buf.Bytes()...), nil
}

View File

@ -21,11 +21,11 @@ import (
)
// ToFormattedMessage 将给定[]message.IMessageElement转换为通过coolq.SetMessageFormat所定义的消息上报格式
func ToFormattedMessage(e []message.IMessageElement, source message.Source, isRaw ...bool) (r interface{}) {
func ToFormattedMessage(e []message.IMessageElement, source message.Source, raw bool) (r interface{}) {
if base.PostFormat == "string" {
r = ToStringMessage(e, source, isRaw...)
r = toStringMessage(e, source, raw)
} else if base.PostFormat == "array" {
r = ToArrayMessage(e, source)
r = toElements(e, source, raw)
}
return
}
@ -36,7 +36,7 @@ func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMess
SourceType: message.SourcePrivate,
PrimaryID: m.Sender.Uin,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source, true)
id := bot.InsertPrivateMessage(m)
log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
fm := global.MSG{
@ -93,7 +93,7 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
SourceType: message.SourceGroup,
PrimaryID: m.GroupCode,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source, true)
id := bot.InsertGroupMessage(m)
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
gm := bot.formatGroupMessage(m)
@ -111,7 +111,7 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
SourceType: message.SourcePrivate,
PrimaryID: e.Session.Sender,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source, true)
bot.tempSessionCache.Store(m.Sender.Uin, e.Session)
id := m.Id
// todo(Mrs4s)
@ -154,7 +154,7 @@ func (bot *CQBot) guildChannelMessageEvent(c *client.QQClient, m *message.GuildC
PrimaryID: int64(m.GuildId),
SecondaryID: int64(m.ChannelId),
}
log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, ToStringMessage(m.Elements, source, true))
log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, toStringMessage(m.Elements, source, true))
id := bot.InsertGuildChannelMessage(m)
bot.dispatchEventMessage(global.MSG{
"post_type": "message",