diff --git a/coolq/api.go b/coolq/api.go index b7459b7..50a90a8 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -754,7 +754,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b var elem []message.IMessageElement if m.Type == gjson.JSON { - elem = bot.ConvertObjectMessage(m, message.SourceGroup) + elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGroup) } else { str := m.String() if str == "" { @@ -764,7 +764,7 @@ func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape b if autoEscape { elem = []message.IMessageElement{message.NewText(str)} } else { - elem = bot.ConvertStringMessage(str, message.SourceGroup) + elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGroup) } } fixAt(elem) @@ -808,7 +808,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R var elem []message.IMessageElement if m.Type == gjson.JSON { - elem = bot.ConvertObjectMessage(m, message.SourceGuildChannel) + elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGuildChannel) } else { str := m.String() if str == "" { @@ -818,7 +818,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R if autoEscape { elem = []message.IMessageElement{message.NewText(str)} } else { - elem = bot.ConvertStringMessage(str, message.SourceGuildChannel) + elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGuildChannel) } } fixAt(elem) @@ -928,7 +928,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType } } } - content := bot.ConvertObjectMessage(c, sourceType) + content := bot.ConvertObjectMessage(onebot.V11, c, sourceType) if uin != 0 && name != "" && len(content) > 0 { return &message.ForwardNode{ SenderId: uin, @@ -1021,7 +1021,7 @@ func (bot *CQBot) CQSendPrivateForwardMessage(userID int64, m gjson.Result) glob func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Result, autoEscape bool) global.MSG { var elem []message.IMessageElement if m.Type == gjson.JSON { - elem = bot.ConvertObjectMessage(m, message.SourcePrivate) + elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourcePrivate) } else { str := m.String() if str == "" { @@ -1030,7 +1030,7 @@ func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Resu if autoEscape { elem = []message.IMessageElement{message.NewText(str)} } else { - elem = bot.ConvertStringMessage(str, message.SourcePrivate) + elem = bot.ConvertStringMessage(onebot.V11, str, message.SourcePrivate) } } mid := bot.SendPrivateMessage(userID, groupID, &message.SendingMessage{Elements: elem}) diff --git a/coolq/api_v12.go b/coolq/api_v12.go index 95a432d..21c8835 100644 --- a/coolq/api_v12.go +++ b/coolq/api_v12.go @@ -29,5 +29,6 @@ func (bot *CQBot) CQGetVersion() global.MSG { // @route12(send_message) // @rename(m->message) func (bot *CQBot) CQSendMessageV12(groupID, userID, detailType string, m gjson.Result) global.MSG { + // TODO: implement return OK(nil) } diff --git a/coolq/cqcode.go b/coolq/cqcode.go index 9a0f1f3..7d500d0 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -31,6 +31,7 @@ import ( "github.com/Mrs4s/go-cqhttp/internal/download" "github.com/Mrs4s/go-cqhttp/internal/mime" "github.com/Mrs4s/go-cqhttp/internal/msg" + "github.com/Mrs4s/go-cqhttp/internal/onebot" "github.com/Mrs4s/go-cqhttp/internal/param" ) @@ -62,6 +63,7 @@ func replyID(r *message.ReplyElement, source message.Source) int32 { // // nolint:govet func toElements(e []message.IMessageElement, source message.Source) (r []msg.Element) { + // TODO: support OneBot V12 type pair = msg.Pair // simplify code type pairs = []pair @@ -380,18 +382,18 @@ func ToMessageContent(e []message.IMessageElement) (r []global.MSG) { } // ConvertStringMessage 将消息字符串转为消息元素数组 -func (bot *CQBot) ConvertStringMessage(raw string, sourceType message.SourceType) (r []message.IMessageElement) { +func (bot *CQBot) ConvertStringMessage(spec *onebot.Spec, raw string, sourceType message.SourceType) (r []message.IMessageElement) { elems := msg.ParseString(raw) - return bot.ConvertElements(elems, sourceType) + return bot.ConvertElements(spec, elems, sourceType) } // ConvertObjectMessage 将消息JSON对象转为消息元素数组 -func (bot *CQBot) ConvertObjectMessage(m gjson.Result, sourceType message.SourceType) (r []message.IMessageElement) { - if m.Type == gjson.String { - return bot.ConvertStringMessage(m.Str, sourceType) +func (bot *CQBot) ConvertObjectMessage(spec *onebot.Spec, m gjson.Result, sourceType message.SourceType) (r []message.IMessageElement) { + if spec.Version == 11 && m.Type == gjson.String { + return bot.ConvertStringMessage(spec, m.Str, sourceType) } elems := msg.ParseObject(m) - return bot.ConvertElements(elems, sourceType) + return bot.ConvertElements(spec, elems, sourceType) } // ConvertContentMessage 将数据库用的 content 转换为消息元素数组 @@ -405,14 +407,14 @@ func (bot *CQBot) ConvertContentMessage(content []global.MSG, sourceType message } elems[i] = elem } - return bot.ConvertElements(elems, sourceType) + return bot.ConvertElements(onebot.V11, elems, sourceType) } // ConvertElements 将解码后的消息数组转换为MiraiGo表示 -func (bot *CQBot) ConvertElements(elems []msg.Element, sourceType message.SourceType) (r []message.IMessageElement) { +func (bot *CQBot) ConvertElements(spec *onebot.Spec, elems []msg.Element, sourceType message.SourceType) (r []message.IMessageElement) { var replyCount int for _, elem := range elems { - me, err := bot.ConvertElement(elem, sourceType) + me, err := bot.ConvertElement(spec, elem, sourceType) if err != nil { // TODO: don't use cqcode format if !base.IgnoreInvalidCQCode { @@ -439,7 +441,7 @@ func (bot *CQBot) ConvertElements(elems []msg.Element, sourceType message.Source return } -func (bot *CQBot) reply(elem msg.Element, sourceType message.SourceType) (any, error) { +func (bot *CQBot) reply(spec *onebot.Spec, elem msg.Element, sourceType message.SourceType) (any, error) { mid, err := strconv.Atoi(elem.Get("id")) customText := elem.Get("text") var re *message.ReplyElement @@ -466,7 +468,7 @@ func (bot *CQBot) reply(elem msg.Element, sourceType message.SourceType) (any, e ReplySeq: org.GetAttribute().MessageSeq, Sender: org.GetAttribute().SenderUin, Time: int32(org.GetAttribute().Timestamp), - Elements: bot.ConvertStringMessage(customText, sourceType), + Elements: bot.ConvertStringMessage(spec, customText, sourceType), } if senderErr != nil { re.Sender = sender @@ -483,7 +485,7 @@ func (bot *CQBot) reply(elem msg.Element, sourceType message.SourceType) (any, e ReplySeq: int32(messageSeq), Sender: sender, Time: int32(msgTime), - Elements: bot.ConvertStringMessage(customText, sourceType), + Elements: bot.ConvertStringMessage(spec, customText, sourceType), } case err == nil: @@ -504,12 +506,89 @@ func (bot *CQBot) reply(elem msg.Element, sourceType message.SourceType) (any, e return re, nil } +func (bot *CQBot) voice(elem msg.Element) (m any, err error) { + f := elem.Get("file") + data, err := global.FindFile(f, elem.Get("cache"), global.VoicePath) + if err != nil { + return nil, err + } + if !global.IsAMRorSILK(data) { + mt, ok := mime.CheckAudio(bytes.NewReader(data)) + if !ok { + return nil, errors.New("voice type error: " + mt) + } + data, err = global.EncoderSilk(data) + if err != nil { + return nil, err + } + } + return &message.VoiceElement{Data: data}, nil +} + +func (bot *CQBot) at(id, name string) (m any, err error) { + t, err := strconv.ParseInt(id, 10, 64) + if err != nil { + return nil, err + } + name = strings.TrimSpace(name) + if len(name) > 0 { + name = "@" + name + } + return message.NewAt(t, name), nil +} + +// convertV11 ConvertElement11 +func (bot *CQBot) convertV11(elem msg.Element) (m any, err error, failed bool) { + switch elem.Type { + case "at": + qq := elem.Get("qq") + if qq == "all" { + m = message.AtAll() + break + } + m, err = bot.at(qq, elem.Get("name")) + case "record": + m, err = bot.voice(elem) + default: + failed = true + } + return +} + +// convertV11 ConvertElement11 +func (bot *CQBot) convertV12(elem msg.Element) (m any, err error, failed bool) { + switch elem.Type { + case "mention": + m, err = bot.at(elem.Get("user_id"), elem.Get("name")) + case "mention_all": + m = message.AtAll() + case "voice": + m, err = bot.voice(elem) + default: + failed = true + } + return +} + // ConvertElement 将解码后的消息转换为MiraiGoElement. // // 返回 interface{} 存在三种类型 // // message.IMessageElement []message.IMessageElement nil -func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType) (m any, err error) { +func (bot *CQBot) ConvertElement(spec *onebot.Spec, elem msg.Element, sourceType message.SourceType) (m any, err error) { + var failed bool + switch spec.Version { + case 11: + m, err, failed = bot.convertV11(elem) + case 12: + m, err, failed = bot.convertV12(elem) + default: + panic("invalid onebot version:" + strconv.Itoa(spec.Version)) + } + if !failed { + return m, err + } + switch elem.Type { case "text": text := elem.Get("text") @@ -553,7 +632,7 @@ func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType } return img, nil case "reply": - return bot.reply(elem, sourceType) + return bot.reply(spec, elem, sourceType) case "forward": id := elem.Get("id") if id != "" { @@ -574,23 +653,6 @@ func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType return nil, err } return &message.VoiceElement{Data: base.ResampleSilk(data)}, nil - case "record", "audio": - f := elem.Get("file") - data, err := global.FindFile(f, elem.Get("cache"), global.VoicePath) - if err != nil { - return nil, err - } - if !global.IsAMRorSILK(data) { - mt, ok := mime.CheckAudio(bytes.NewReader(data)) - if !ok { - return nil, errors.New("audio type error: " + mt) - } - data, err = global.EncoderSilk(data) - if err != nil { - return nil, err - } - } - return &message.VoiceElement{Data: data}, nil case "face": id, err := strconv.Atoi(elem.Get("id")) if err != nil { @@ -600,27 +662,12 @@ func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType return &message.AnimatedSticker{ID: int32(id)}, nil } return message.NewFace(int32(id)), nil - case "mention_all": - return message.AtAll(), nil - case "at", "mention": - qq := elem.Get("qq") - if qq == "all" { - return message.AtAll(), nil - } - t, err := strconv.ParseInt(qq, 10, 64) - if err != nil { - return nil, err - } - name := strings.TrimSpace(elem.Get("name")) - if len(name) > 0 { - name = "@" + name - } - return message.NewAt(t, name), nil case "share": return message.NewUrlShare(elem.Get("url"), elem.Get("title"), elem.Get("content"), elem.Get("image")), nil case "music": id := elem.Get("id") - if elem.Get("type") == "qq" { + switch elem.Get("type") { + case "qq": info, err := global.QQMusicSongInfo(id) if err != nil { return nil, err @@ -628,27 +675,22 @@ func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType if !info.Get("track_info").Exists() { return nil, errors.New("song not found") } - name := info.Get("track_info.name").Str - mid := info.Get("track_info.mid").Str - albumMid := info.Get("track_info.album.mid").Str - pinfo, _ := download.Request{URL: "https://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=2034008533&uin=0&format=json&data={\"comm\":{\"ct\":23,\"cv\":0},\"url_mid\":{\"module\":\"vkey.GetVkeyServer\",\"method\":\"CgiGetVkey\",\"param\":{\"guid\":\"4311206557\",\"songmid\":[\"" + mid + "\"],\"songtype\":[0],\"uin\":\"0\",\"loginflag\":1,\"platform\":\"23\"}}}&_=1599039471576"}.JSON() - jumpURL := "https://i.y.qq.com/v8/playsong.html?platform=11&appshare=android_qq&appversion=10030010&hosteuin=oKnlNenz7i-s7c**&songmid=" + mid + "&type=0&appsongtype=1&_wv=1&source=qq&ADTAG=qfshare" - purl := pinfo.Get("url_mid.data.midurlinfo.0.purl").Str - preview := "https://y.gtimg.cn/music/photo_new/T002R180x180M000" + albumMid + ".jpg" - content := info.Get("track_info.singer.0.name").Str + albumMid := info.Get("track_info.album.mid").String() + pinfo, _ := download.Request{URL: "https://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=2034008533&uin=0&format=json&data={\"comm\":{\"ct\":23,\"cv\":0},\"url_mid\":{\"module\":\"vkey.GetVkeyServer\",\"method\":\"CgiGetVkey\",\"param\":{\"guid\":\"4311206557\",\"songmid\":[\"" + info.Get("track_info.mid").Str + "\"],\"songtype\":[0],\"uin\":\"0\",\"loginflag\":1,\"platform\":\"23\"}}}&_=1599039471576"}.JSON() + jumpURL := "https://i.y.qq.com/v8/playsong.html?platform=11&appshare=android_qq&appversion=10030010&hosteuin=oKnlNenz7i-s7c**&songmid=" + info.Get("track_info.mid").Str + "&type=0&appsongtype=1&_wv=1&source=qq&ADTAG=qfshare" + content := info.Get("track_info.singer.0.name").String() if elem.Get("content") != "" { content = elem.Get("content") } return &message.MusicShareElement{ MusicType: message.QQMusic, - Title: name, + Title: info.Get("track_info.name").Str, Summary: content, Url: jumpURL, - PictureUrl: preview, - MusicUrl: purl, + PictureUrl: "https://y.gtimg.cn/music/photo_new/T002R180x180M000" + albumMid + ".jpg", + MusicUrl: pinfo.Get("url_mid.data.midurlinfo.0.purl").String(), }, nil - } - if elem.Get("type") == "163" { + case "163": info, err := global.NeteaseMusicSongInfo(id) if err != nil { return nil, err @@ -656,24 +698,19 @@ func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType if !info.Exists() { return nil, errors.New("song not found") } - name := info.Get("name").Str - jumpURL := "https://y.music.163.com/m/song/" + id - musicURL := "http://music.163.com/song/media/outer/url?id=" + id - picURL := info.Get("album.picUrl").Str artistName := "" if info.Get("artists.0").Exists() { - artistName = info.Get("artists.0.name").Str + artistName = info.Get("artists.0.name").String() } return &message.MusicShareElement{ MusicType: message.CloudMusic, - Title: name, + Title: info.Get("name").String(), Summary: artistName, - Url: jumpURL, - PictureUrl: picURL, - MusicUrl: musicURL, + Url: "https://y.music.163.com/m/song/" + id, + PictureUrl: info.Get("album.picUrl").String(), + MusicUrl: "https://music.163.com/song/media/outer/url?id=" + id, }, nil - } - if elem.Get("type") == "custom" { + case "custom": if elem.Get("subtype") != "" { var subType int switch elem.Get("subtype") { @@ -694,11 +731,11 @@ func (bot *CQBot) ConvertElement(elem msg.Element, sourceType message.SourceType Summary: elem.Get("content"), Url: elem.Get("url"), PictureUrl: elem.Get("image"), - MusicUrl: elem.Get("audio"), + MusicUrl: elem.Get("voice"), }, nil } - xml := fmt.Sprintf(``, - utils.XmlEscape(elem.Get("title")), elem.Get("url"), elem.Get("image"), elem.Get("audio"), utils.XmlEscape(elem.Get("title")), utils.XmlEscape(elem.Get("content"))) + xml := fmt.Sprintf(`%s%s`, + utils.XmlEscape(elem.Get("title")), elem.Get("url"), elem.Get("image"), elem.Get("voice"), utils.XmlEscape(elem.Get("title")), utils.XmlEscape(elem.Get("content"))) return &message.ServiceElement{ Id: 60, Content: xml,