package coolq
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
xml2 "encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"net/url"
"os"
"path"
"runtime"
"strconv"
"strings"
"time"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/Mrs4s/go-cqhttp/global"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
)
/*
var matchReg = regexp.MustCompile(`\[CQ:\w+?.*?]`)
var typeReg = regexp.MustCompile(`\[CQ:(\w+)`)
var paramReg = regexp.MustCompile(`,([\w\-.]+?)=([^,\]]+)`)
*/
// IgnoreInvalidCQCode 是否忽略无效CQ码
var IgnoreInvalidCQCode = false
// SplitURL 是否分割URL
var SplitURL = false
const maxImageSize = 1024 * 1024 * 30 // 30MB
const maxVideoSize = 1024 * 1024 * 100 // 100MB
// PokeElement 拍一拍
type PokeElement struct {
Target int64
}
// GiftElement 礼物
type GiftElement struct {
Target int64
GiftID message.GroupGift
}
// LocalImageElement 本地图片
type LocalImageElement struct {
message.ImageElement
Stream io.ReadSeeker
File string
}
// LocalVoiceElement 本地语音
type LocalVoiceElement struct {
message.VoiceElement
Stream io.ReadSeeker
}
// LocalVideoElement 本地视频
type LocalVideoElement struct {
message.ShortVideoElement
File string
thumb io.ReadSeeker
}
// Type 获取元素类型ID
func (e *GiftElement) Type() message.ElementType {
// Make message.IMessageElement Happy
return message.At
}
// GiftID 礼物ID数组
var GiftID = [...]message.GroupGift{
message.SweetWink,
message.HappyCola,
message.LuckyBracelet,
message.Cappuccino,
message.CatWatch,
message.FleeceGloves,
message.RainbowCandy,
message.Stronger,
message.LoveMicrophone,
message.HoldingYourHand,
message.CuteCat,
message.MysteryMask,
message.ImBusy,
message.LoveMask,
}
// Type 获取元素类型ID
func (e *PokeElement) Type() message.ElementType {
// Make message.IMessageElement Happy
return message.At
}
// ToArrayMessage 将消息元素数组转为MSG数组以用于消息上报
func ToArrayMessage(e []message.IMessageElement, id int64, isRaw ...bool) (r []MSG) {
r = []MSG{}
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 {
r = append(r, MSG{
"type": "reply",
"data": map[string]string{"id": fmt.Sprint(toGlobalID(id, reply.(*message.ReplyElement).ReplySeq))},
})
}
for _, elem := range e {
var m MSG
switch o := elem.(type) {
case *message.TextElement:
m = MSG{
"type": "text",
"data": map[string]string{"text": o.Content},
}
case *message.LightAppElement:
// m = MSG{
// "type": "text",
// "data": map[string]string{"text": o.Content},
// }
m = MSG{
"type": "json",
"data": map[string]string{"data": o.Content},
}
case *message.AtElement:
if o.Target == 0 {
m = MSG{
"type": "at",
"data": map[string]string{"qq": "all"},
}
} else {
m = MSG{
"type": "at",
"data": map[string]string{"qq": fmt.Sprint(o.Target)},
}
}
case *message.RedBagElement:
m = MSG{
"type": "redbag",
"data": map[string]string{"title": o.Title},
}
case *message.ForwardElement:
m = MSG{
"type": "forward",
"data": map[string]string{"id": o.ResId},
}
case *message.FaceElement:
m = MSG{
"type": "face",
"data": map[string]string{"id": fmt.Sprint(o.Index)},
}
case *message.VoiceElement:
if ur {
m = MSG{
"type": "record",
"data": map[string]string{"file": o.Name},
}
} else {
m = MSG{
"type": "record",
"data": map[string]string{"file": o.Name, "url": o.Url},
}
}
case *message.ShortVideoElement:
if ur {
m = MSG{
"type": "video",
"data": map[string]string{"file": o.Name},
}
} else {
m = MSG{
"type": "video",
"data": map[string]string{"file": o.Name, "url": o.Url},
}
}
case *message.ImageElement:
if ur {
m = MSG{
"type": "image",
"data": map[string]string{"file": o.Filename},
}
} else {
m = MSG{
"type": "image",
"data": map[string]string{"file": o.Filename, "url": o.Url},
}
}
case *message.GroupImageElement:
if ur {
m = MSG{
"type": "image",
"data": map[string]string{"file": hex.EncodeToString(o.Md5) + ".image"},
}
} else {
m = MSG{
"type": "image",
"data": map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": CQCodeEscapeText(o.Url)},
}
}
case *message.FriendImageElement:
if ur {
m = MSG{
"type": "image",
"data": map[string]string{"file": hex.EncodeToString(o.Md5) + ".image"},
}
} else {
m = MSG{
"type": "image",
"data": map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": CQCodeEscapeText(o.Url)},
}
}
case *message.GroupFlashImgElement:
return []MSG{{
"type": "image",
"data": map[string]string{"file": o.Filename, "type": "flash"},
}}
case *message.FriendFlashImgElement:
return []MSG{{
"type": "image",
"data": map[string]string{"file": o.Filename, "type": "flash"},
}}
case *message.ServiceElement:
if isOk := strings.Contains(o.Content, " 0 {
if _, ok := r[0].(*message.ReplyElement); ok {
log.Warnf("警告: 一条信息只能包含一个 Reply 元素.")
return
}
}
mid, err := strconv.Atoi(params["id"])
customText := params["text"]
if err == nil {
org := bot.GetMessage(int32(mid))
if org != nil {
r = append([]message.IMessageElement{
&message.ReplyElement{
ReplySeq: org["message-id"].(int32),
Sender: org["sender"].(message.Sender).Uin,
Time: org["time"].(int32),
Elements: bot.ConvertStringMessage(org["message"].(string), isGroup),
},
}, r...)
return
}
} else if customText != "" {
sender, err := strconv.ParseInt(params["qq"], 10, 64)
if err != nil {
log.Warnf("警告:自定义 Reply 元素中必须包含Uin")
return
}
msgTime, err := strconv.ParseInt(params["time"], 10, 64)
if err != nil {
msgTime = time.Now().Unix()
}
r = append([]message.IMessageElement{
&message.ReplyElement{
ReplySeq: int32(0),
Sender: sender,
Time: int32(msgTime),
Elements: bot.ConvertStringMessage(customText, isGroup),
},
}, r...)
return
}
}
if t == "forward" { // 单独处理转发
if id, ok := params["id"]; ok {
r = []message.IMessageElement{bot.Client.DownloadForwardMessage(id)}
return
}
}
elem, err := bot.ToElement(t, params, isGroup)
if err != nil {
org := "[CQ:" + string(cqCode) + "]"
if !IgnoreInvalidCQCode {
log.Warnf("转换CQ码 %v 时出现错误: %v 将原样发送.", org, err)
r = append(r, message.NewText(org))
} else {
log.Warnf("转换CQ码 %v 时出现错误: %v 将忽略.", org, err)
}
return
}
switch i := elem.(type) {
case message.IMessageElement:
r = append(r, i)
case []message.IMessageElement:
r = append(r, i...)
}
}
for hasNext() {
ch := next()
switch stat {
case 0:
if isCQCodeBegin(ch) {
saveTempText()
tempText = append(tempText, []rune("[CQ:")...)
move(3)
stat = 1
} else {
tempText = append(tempText, ch)
}
case 1:
if isCQCodeBegin(ch) {
move(-1)
stat = 0
} else if ch == ']' {
saveCQCode()
stat = 0
} else {
cqCode = append(cqCode, ch)
tempText = append(tempText, ch)
}
}
}
saveTempText()
return
}
// ConvertObjectMessage 将消息JSON对象转为消息元素数组
func (bot *CQBot) ConvertObjectMessage(m gjson.Result, isGroup bool) (r []message.IMessageElement) {
convertElem := func(e gjson.Result) {
t := e.Get("type").Str
if t == "reply" && isGroup {
if len(r) > 0 {
if _, ok := r[0].(*message.ReplyElement); ok {
log.Warnf("警告: 一条信息只能包含一个 Reply 元素.")
return
}
}
mid, err := strconv.Atoi(e.Get("data").Get("id").String())
customText := e.Get("data").Get("text").String()
if err == nil {
org := bot.GetMessage(int32(mid))
if org != nil {
r = append([]message.IMessageElement{
&message.ReplyElement{
ReplySeq: org["message-id"].(int32),
Sender: org["sender"].(message.Sender).Uin,
Time: org["time"].(int32),
Elements: bot.ConvertStringMessage(org["message"].(string), isGroup),
},
}, r...)
return
}
} else if customText != "" {
sender, err := strconv.ParseInt(e.Get("data").Get("qq").String(), 10, 64)
if err != nil {
log.Warnf("警告:自定义 Reply 元素中必须包含Uin")
return
}
msgTime, err := strconv.ParseInt(e.Get("data").Get("time").String(), 10, 64)
if err != nil {
msgTime = time.Now().Unix()
}
r = append([]message.IMessageElement{
&message.ReplyElement{
ReplySeq: int32(0),
Sender: sender,
Time: int32(msgTime),
Elements: bot.ConvertStringMessage(customText, isGroup),
},
}, r...)
return
}
}
if t == "forward" {
r = []message.IMessageElement{bot.Client.DownloadForwardMessage(e.Get("data.id").String())}
return
}
d := make(map[string]string)
e.Get("data").ForEach(func(key, value gjson.Result) bool {
d[key.Str] = value.String()
return true
})
elem, err := bot.ToElement(t, d, isGroup)
if err != nil {
log.Warnf("转换CQ码到MiraiGo Element时出现错误: %v 将忽略本段CQ码.", err)
return
}
switch i := elem.(type) {
case message.IMessageElement:
r = append(r, i)
case []message.IMessageElement:
r = append(r, i...)
}
}
if m.Type == gjson.String {
return bot.ConvertStringMessage(m.Str, isGroup)
}
if m.IsArray() {
for _, e := range m.Array() {
convertElem(e)
}
}
if m.IsObject() {
convertElem(m)
}
return
}
// ToElement 将解码后的CQCode转换为Element.
//
// 返回 interface{} 存在三种类型
//
// message.IMessageElement []message.IMessageElement nil
func (bot *CQBot) ToElement(t string, d map[string]string, isGroup bool) (m interface{}, err error) {
switch t {
case "text":
if SplitURL {
var ret []message.IMessageElement
for _, text := range global.SplitURL(d["text"]) {
ret = append(ret, message.NewText(text))
}
return ret, nil
}
return message.NewText(d["text"]), nil
case "image":
img, err := bot.makeImageOrVideoElem(d, false, isGroup)
if err != nil {
return nil, err
}
tp := d["type"]
if tp != "show" && tp != "flash" {
return img, nil
}
if i, ok := img.(*LocalImageElement); ok { // 秀图,闪照什么的就直接传了吧
if isGroup {
img, err = bot.UploadLocalImageAsGroup(1, i)
} else {
img, err = bot.UploadLocalImageAsPrivate(1, i)
}
if err != nil {
return nil, err
}
}
switch tp {
case "flash":
if i, ok := img.(*message.GroupImageElement); ok {
return &message.GroupFlashPicElement{GroupImageElement: *i}, nil
}
if i, ok := img.(*message.FriendImageElement); ok {
return &message.FriendFlashPicElement{FriendImageElement: *i}, nil
}
case "show":
id, _ := strconv.ParseInt(d["id"], 10, 64)
if id < 40000 || id >= 40006 {
id = 40000
}
if i, ok := img.(*message.GroupImageElement); ok {
return &message.GroupShowPicElement{GroupImageElement: *i, EffectId: int32(id)}, nil
}
return img, nil // 私聊还没做
}
case "poke":
t, _ := strconv.ParseInt(d["qq"], 10, 64)
return &PokeElement{Target: t}, nil
case "gift":
if !isGroup {
return nil, errors.New("private gift unsupported") // no free private gift
}
t, _ := strconv.ParseInt(d["qq"], 10, 64)
id, _ := strconv.Atoi(d["id"])
if id < 0 || id >= 14 {
return nil, errors.New("invalid gift id")
}
return &GiftElement{Target: t, GiftID: GiftID[id]}, nil
case "tts":
defer func() {
if r := recover(); r != nil {
m = nil
err = errors.New("tts 转换失败")
}
}()
data, err := bot.Client.GetTts(d["text"])
if err != nil {
return nil, err
}
return &message.VoiceElement{Data: data}, nil
case "record":
f := d["file"]
data, err := global.FindFile(f, d["cache"], global.VoicePath)
if err == global.ErrSyntax {
data, err = global.FindFile(f, d["cache"], global.VoicePathOld)
}
if err != nil {
return nil, err
}
if !global.IsAMRorSILK(data) {
data, err = global.EncoderSilk(data)
if err != nil {
return nil, err
}
}
return &message.VoiceElement{Data: data}, nil
case "face":
id, err := strconv.Atoi(d["id"])
if err != nil {
return nil, err
}
return message.NewFace(int32(id)), nil
case "at":
qq := d["qq"]
if qq == "all" {
return message.AtAll(), nil
}
t, _ := strconv.ParseInt(qq, 10, 64)
return message.NewAt(t), nil
case "share":
return message.NewUrlShare(d["url"], d["title"], d["content"], d["image"]), nil
case "music":
if d["type"] == "qq" {
info, err := global.QQMusicSongInfo(d["id"])
if err != nil {
return nil, err
}
if !info.Get("track_info").Exists() {
return nil, errors.New("song not found")
}
aid := strconv.FormatInt(info.Get("track_info.album.id").Int(), 10)
name := info.Get("track_info.name").Str
mid := info.Get("track_info.mid").Str
albumMid := info.Get("track_info.album.mid").Str
pinfo, _ := global.GetBytes("http://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")
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 := gjson.ParseBytes(pinfo).Get("url_mid.data.midurlinfo.0.purl").Str
preview := "http://y.gtimg.cn/music/photo_new/T002R180x180M000" + albumMid + ".jpg"
if len(aid) < 2 {
return nil, errors.New("song error")
}
content := info.Get("track_info.singer.0.name").Str
if d["content"] != "" {
content = d["content"]
}
return &message.MusicShareElement{
MusicType: message.QQMusic,
Title: name,
Summary: content,
Url: jumpURL,
PictureUrl: preview,
MusicUrl: purl,
}, nil
}
if d["type"] == "163" {
info, err := global.NeteaseMusicSongInfo(d["id"])
if err != nil {
return nil, err
}
if !info.Exists() {
return nil, errors.New("song not found")
}
name := info.Get("name").Str
jumpURL := "https://y.music.163.com/m/song/" + d["id"]
musicURL := "http://music.163.com/song/media/outer/url?id=" + d["id"]
picURL := info.Get("album.picUrl").Str
artistName := ""
if info.Get("artists.0").Exists() {
artistName = info.Get("artists.0.name").Str
}
return &message.MusicShareElement{
MusicType: message.CloudMusic,
Title: name,
Summary: artistName,
Url: jumpURL,
PictureUrl: picURL,
MusicUrl: musicURL,
}, nil
}
if d["type"] == "custom" {
if d["subtype"] != "" {
var subtype = map[string]int{
"qq": message.QQMusic,
"163": message.CloudMusic,
"migu": message.MiguMusic,
"kugou": message.KugouMusic,
"kuwo": message.KuwoMusic,
}
var musicType = 0
if tp, ok := subtype[d["subtype"]]; ok {
musicType = tp
}
return &message.MusicShareElement{
MusicType: musicType,
Title: d["title"],
Summary: d["content"],
Url: d["url"],
PictureUrl: d["image"],
MusicUrl: d["purl"],
}, nil
}
xml := fmt.Sprintf(`- %s%s
`,
XMLEscape(d["title"]), d["url"], d["image"], d["audio"], XMLEscape(d["title"]), XMLEscape(d["content"]))
return &message.ServiceElement{
Id: 60,
Content: xml,
SubType: "music",
}, nil
}
return nil, errors.New("unsupported music type: " + d["type"])
case "xml":
resID := d["resid"]
template := CQCodeEscapeValue(d["data"])
i, _ := strconv.ParseInt(resID, 10, 64)
msg := message.NewRichXml(template, i)
return msg, nil
case "json":
resID := d["resid"]
i, _ := strconv.ParseInt(resID, 10, 64)
if i == 0 {
// 默认情况下走小程序通道
msg := message.NewLightApp(d["data"])
return msg, nil
}
// resid不为0的情况下走富文本通道,后续补全透传service Id,此处暂时不处理 TODO
msg := message.NewRichJson(d["data"])
return msg, nil
case "cardimage":
source := d["source"]
icon := d["icon"]
minWidth, _ := strconv.ParseInt(d["minwidth"], 10, 64)
if minWidth == 0 {
minWidth = 200
}
minHeight, _ := strconv.ParseInt(d["minheight"], 10, 64)
if minHeight == 0 {
minHeight = 200
}
maxWidth, _ := strconv.ParseInt(d["maxwidth"], 10, 64)
if maxWidth == 0 {
maxWidth = 500
}
maxHeight, _ := strconv.ParseInt(d["maxheight"], 10, 64)
if maxHeight == 0 {
maxHeight = 1000
}
img, err := bot.makeImageOrVideoElem(d, false, isGroup)
if err != nil {
return nil, errors.New("send cardimage faild")
}
return bot.makeShowPic(img, source, icon, minWidth, minHeight, maxWidth, maxHeight, isGroup)
case "video":
cache := d["cache"]
if cache == "" {
cache = "1"
}
file, err := bot.makeImageOrVideoElem(d, true, isGroup)
if err != nil {
return nil, err
}
v := file.(*LocalVideoElement)
if v.File == "" {
return v, nil
}
var data []byte
if cover, ok := d["cover"]; ok {
data, _ = global.FindFile(cover, cache, global.ImagePath)
} else {
_ = global.ExtractCover(v.File, v.File+".jpg")
data, _ = ioutil.ReadFile(v.File + ".jpg")
}
v.thumb = bytes.NewReader(data)
video, _ := os.Open(v.File)
defer video.Close()
_, err = video.Seek(4, io.SeekStart)
if err != nil {
return nil, err
}
var header = make([]byte, 4)
_, err = video.Read(header)
if err != nil {
return nil, err
}
if !bytes.Equal(header, []byte{0x66, 0x74, 0x79, 0x70}) { // check file header ftyp
_, _ = video.Seek(0, io.SeekStart)
hash, _ := utils.ComputeMd5AndLength(video)
cacheFile := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".mp4")
if global.PathExists(cacheFile) && cache == "1" {
goto ok
}
err = global.EncodeMP4(v.File, cacheFile)
if err != nil {
return nil, err
}
ok:
v.File = cacheFile
}
return v, nil
default:
return nil, errors.New("unsupported cq code: " + t)
}
return nil, nil
}
// XMLEscape 将字符串c转义为XML字符串
func XMLEscape(c string) string {
buf := new(bytes.Buffer)
_ = xml2.EscapeText(buf, []byte(c))
return buf.String()
}
/*CQCodeEscapeText 将字符串raw中部分字符转义
& -> &
[ -> [
] -> ]
*/
func CQCodeEscapeText(raw string) string {
ret := raw
ret = strings.ReplaceAll(ret, "&", "&")
ret = strings.ReplaceAll(ret, "[", "[")
ret = strings.ReplaceAll(ret, "]", "]")
return ret
}
/*CQCodeEscapeValue 将字符串value中部分字符转义
, -> ,
& -> &
[ -> [
] -> ]
*/
func CQCodeEscapeValue(value string) string {
ret := CQCodeEscapeText(value)
ret = strings.ReplaceAll(ret, ",", ",")
return ret
}
/*CQCodeUnescapeText 将字符串content中部分字符反转义
& -> &
[ -> [
] -> ]
*/
func CQCodeUnescapeText(content string) string {
ret := content
ret = strings.ReplaceAll(ret, "[", "[")
ret = strings.ReplaceAll(ret, "]", "]")
ret = strings.ReplaceAll(ret, "&", "&")
return ret
}
/*CQCodeUnescapeValue 将字符串content中部分字符反转义
, -> ,
& -> &
[ -> [
] -> ]
*/
func CQCodeUnescapeValue(content string) string {
ret := strings.ReplaceAll(content, ",", ",")
ret = CQCodeUnescapeText(ret)
return ret
}
// makeImageOrVideoElem 图片 elem 生成器,单独拎出来,用于公用
func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video, group bool) (message.IMessageElement, error) {
f := d["file"]
if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") {
cache := d["cache"]
c := d["c"]
if cache == "" {
cache = "1"
}
hash := md5.Sum([]byte(f))
cacheFile := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".cache")
var maxSize = func() int64 {
if video {
return maxVideoSize
}
return maxImageSize
}()
thread, _ := strconv.Atoi(c)
if global.PathExists(cacheFile) && cache == "1" {
goto hasCacheFile
}
if global.PathExists(cacheFile) {
_ = os.Remove(cacheFile)
}
if err := global.DownloadFileMultiThreading(f, cacheFile, maxSize, thread, nil); err != nil {
return nil, err
}
hasCacheFile:
if video {
return &LocalVideoElement{File: cacheFile}, nil
}
return &LocalImageElement{File: cacheFile}, nil
}
if strings.HasPrefix(f, "file") {
fu, err := url.Parse(f)
if err != nil {
return nil, err
}
if strings.HasPrefix(fu.Path, "/") && runtime.GOOS == `windows` {
fu.Path = fu.Path[1:]
}
info, err := os.Stat(fu.Path)
if err != nil {
if !os.IsExist(err) {
return nil, errors.New("file not found")
}
return nil, err
}
if video {
if info.Size() == 0 || info.Size() >= maxVideoSize {
return nil, errors.New("invalid video size")
}
return &LocalVideoElement{File: fu.Path}, nil
}
if info.Size() == 0 || info.Size() >= maxImageSize {
return nil, errors.New("invalid image size")
}
return &LocalImageElement{File: fu.Path}, nil
}
rawPath := path.Join(global.ImagePath, f)
if video {
rawPath = path.Join(global.VideoPath, f)
if !global.PathExists(rawPath) {
return nil, errors.New("invalid video")
}
if path.Ext(rawPath) == ".video" {
b, _ := ioutil.ReadFile(rawPath)
r := binary.NewReader(b)
return &LocalVideoElement{ShortVideoElement: message.ShortVideoElement{ // todo 检查缓存是否有效
Md5: r.ReadBytes(16),
ThumbMd5: r.ReadBytes(16),
Size: r.ReadInt32(),
ThumbSize: r.ReadInt32(),
Name: r.ReadString(),
Uuid: r.ReadAvailable(),
}}, nil
}
return &LocalVideoElement{File: rawPath}, nil
}
if strings.HasPrefix(f, "base64") {
b, err := base64.StdEncoding.DecodeString(strings.ReplaceAll(f, "base64://", ""))
if err != nil {
return nil, err
}
return &LocalImageElement{Stream: bytes.NewReader(b)}, nil
}
if !global.PathExists(rawPath) && global.PathExists(path.Join(global.ImagePathOld, f)) {
rawPath = path.Join(global.ImagePathOld, f)
}
if !global.PathExists(rawPath) && global.PathExists(rawPath+".cqimg") {
rawPath += ".cqimg"
}
if !global.PathExists(rawPath) && d["url"] != "" {
return bot.makeImageOrVideoElem(map[string]string{"file": d["url"]}, false, group)
}
if global.PathExists(rawPath) {
file, err := os.Open(rawPath)
if err != nil {
return nil, err
}
if path.Ext(rawPath) != ".image" && path.Ext(rawPath) != ".cqimg" {
return &LocalImageElement{Stream: file}, nil
}
b, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
if len(b) < 20 {
return nil, errors.New("invalid local file")
}
var (
size int32
hash []byte
url string
)
if path.Ext(rawPath) == ".cqimg" {
for _, line := range strings.Split(global.ReadAllText(rawPath), "\n") {
kv := strings.SplitN(line, "=", 2)
switch kv[0] {
case "md5":
hash, _ = hex.DecodeString(strings.ReplaceAll(kv[1], "\r", ""))
case "size":
t, _ := strconv.Atoi(strings.ReplaceAll(kv[1], "\r", ""))
size = int32(t)
}
}
} else {
r := binary.NewReader(b)
hash = r.ReadBytes(16)
size = r.ReadInt32()
r.ReadString()
url = r.ReadString()
}
if size == 0 {
if url != "" {
return bot.makeImageOrVideoElem(map[string]string{"file": url}, false, group)
}
return nil, errors.New("img size is 0")
}
if len(hash) != 16 {
return nil, errors.New("invalid hash")
}
var rsp message.IMessageElement
if group {
rsp, err = bot.Client.QueryGroupImage(int64(rand.Uint32()), hash, size)
goto ok
}
rsp, err = bot.Client.QueryFriendImage(int64(rand.Uint32()), hash, size)
ok:
if err != nil {
if url != "" {
return bot.makeImageOrVideoElem(map[string]string{"file": url}, false, group)
}
return nil, err
}
return rsp, nil
}
return nil, errors.New("invalid image")
}
// makeShowPic 一种xml 方式发送的群消息图片
func (bot *CQBot) makeShowPic(elem message.IMessageElement, source string, icon string, minWidth int64, minHeight int64, maxWidth int64, maxHeight int64, group bool) ([]message.IMessageElement, error) {
xml := ""
var suf message.IMessageElement
if i, ok := elem.(*LocalImageElement); ok {
if !group {
gm, err := bot.UploadLocalImageAsPrivate(1, i)
if err != nil {
log.Warnf("警告: 好友消息 %v 消息图片上传失败: %v", 1, err)
return nil, err
}
suf = gm
xml = fmt.Sprintf(` `, "", gm.Md5, gm.Md5, len(i.Data), "", minWidth, minHeight, maxWidth, maxHeight, source, icon)
} else {
gm, err := bot.UploadLocalImageAsGroup(1, i)
if err != nil {
log.Warnf("警告: 群 %v 消息图片上传失败: %v", 1, err)
return nil, err
}
suf = gm
xml = fmt.Sprintf(` `, "", gm.Md5, gm.Md5, len(i.Data), "", minWidth, minHeight, maxWidth, maxHeight, source, icon)
}
}
if i, ok := elem.(*message.GroupImageElement); ok {
xml = fmt.Sprintf(` `, "", i.Md5, i.Md5, 0, "", minWidth, minHeight, maxWidth, maxHeight, source, icon)
suf = i
}
if i, ok := elem.(*message.FriendImageElement); ok {
xml = fmt.Sprintf(` `, "", i.Md5, i.Md5, 0, "", minWidth, minHeight, maxWidth, maxHeight, source, icon)
suf = i
}
if xml != "" {
// log.Warn(xml)
ret := []message.IMessageElement{suf}
ret = append(ret, message.NewRichXml(xml, 5))
return ret, nil
}
return nil, errors.New("生成xml图片消息失败")
}