diff --git a/coolq/api.go b/coolq/api.go index b453c78..583122a 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -837,7 +837,7 @@ func (bot *CQBot) CQCanSendRecord() MSG { } func (bot *CQBot) CQOcrImage(imageId string) MSG { - img, err := bot.makeImageElem(map[string]string{"file": imageId}, true) + img, err := bot.makeImageOrVideoElem(map[string]string{"file": imageId}, false, true) if err != nil { log.Warnf("load image error: %v", err) return Failed(100, "LOAD_FILE_ERROR", err.Error()) diff --git a/coolq/bot.go b/coolq/bot.go index 0fc72fa..a0d0130 100644 --- a/coolq/bot.go +++ b/coolq/bot.go @@ -158,6 +158,15 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int newElem = append(newElem, gv) continue } + if i, ok := elem.(*LocalVideoElement); ok { // todo:cache & multiThread + gv, err := bot.Client.UploadGroupShortVideo(groupId, i.video, i.thumb) + if err != nil { + log.Warnf("警告: 群 %v 消息短视频上传失败: %v", groupId, err) + continue + } + newElem = append(newElem, gv) + continue + } if i, ok := elem.(*PokeElement); ok { if group := bot.Client.FindGroup(groupId); group != nil { if mem := group.FindMember(i.Target); mem != nil { @@ -272,7 +281,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, m *message.SendingMessage) in if i, ok := elem.(*message.VoiceElement); ok { fv, err := bot.Client.UploadPrivatePtt(target, i.Data) if err != nil { - log.Warnf("警告: 好友 %v 消息语音上传失败: %v", target, err) + log.Warnf("警告: 群 %v 消息语音上传失败: %v", target, err) continue } newElem = append(newElem, fv) diff --git a/coolq/cqcode.go b/coolq/cqcode.go index ba472ff..c2d250e 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -21,6 +21,7 @@ import ( "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" @@ -35,7 +36,8 @@ var paramReg = regexp.MustCompile(`,([\w\-.]+?)=([^,\]]+)`) var IgnoreInvalidCQCode = false var SplitUrl = false -const maxImageSize = 1024 * 1024 * 30 // 30MB +const maxImageSize = 1024 * 1024 * 30 // 30MB +const maxVideoSize = 1024 * 1024 * 100 // 100MB type PokeElement struct { Target int64 @@ -77,6 +79,13 @@ type LocalVoiceElement struct { Stream io.ReadSeeker } +type LocalVideoElement struct { + message.ShortVideoElement + File string + video io.ReadSeeker + thumb io.ReadSeeker +} + func (e *GiftElement) Type() message.ElementType { return message.At } @@ -426,7 +435,7 @@ func (bot *CQBot) ConvertStringMessage(msg string, group bool) (r []message.IMes } elem, err := bot.ToElement(t, params, group) if err != nil { - org := "[" + string(cqCode) + "]" + org := "[CQ:" + string(cqCode) + "]" if !IgnoreInvalidCQCode { log.Warnf("转换CQ码 %v 时出现错误: %v 将原样发送.", org, err) r = append(r, message.NewText(org)) @@ -547,7 +556,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf } return message.NewText(d["text"]), nil case "image": - img, err := bot.makeImageElem(d, group) + img, err := bot.makeImageOrVideoElem(d, false, group) if err != nil { return nil, err } @@ -769,11 +778,55 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf if maxHeight == 0 { maxHeight = 1000 } - img, err := bot.makeImageElem(d, group) + img, err := bot.makeImageOrVideoElem(d, false, group) if err != nil { return nil, errors.New("send cardimage faild") } return bot.makeShowPic(img, source, icon, minWidth, minHeight, maxWidth, maxHeight, group) + case "video": + if !group { + return nil, errors.New("unsupported private short video") + } + cache := d["cache"] + if cache == "" { + cache = "1" + } + file, err := bot.makeImageOrVideoElem(d, true, group) + if err != nil { + return nil, err + } + v := file.(*LocalVideoElement) + if cover, ok := d["cover"]; ok { + data, _ := global.FindFile(cover, cache, global.IMAGE_PATH) + v.thumb = bytes.NewReader(data) + } + if v.thumb == nil { + _ = global.ExtractCover(v.File, v.File+".jpg") + v.thumb, _ = os.Open(v.File + ".jpg") + } + v.video, _ = os.Open(v.File) + _, err = v.video.Seek(4, io.SeekStart) + if err != nil { + return nil, err + } + var header = make([]byte, 4) + _, err = v.video.Read(header) + if !bytes.Equal(header, []byte{0x66, 0x74, 0x79, 0x70}) { // ftyp + _, _ = v.video.Seek(0, io.SeekStart) + hash, _ := utils.GetMd5AndLength(v.video) + cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".mp4") + if global.PathExists(cacheFile) { + goto ok + } + err = global.EncodeMP4(v.File, cacheFile) + if err != nil { + return nil, err + } + ok: + v.video, _ = os.Open(cacheFile) + } + _, _ = v.video.Seek(0, io.SeekStart) + return v, nil default: return nil, errors.New("unsupported cq code: " + t) } @@ -815,7 +868,7 @@ func CQCodeUnescapeValue(content string) string { } // 图片 elem 生成器,单独拎出来,用于公用 -func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessageElement, error) { +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"] @@ -832,18 +885,22 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa _ = os.Remove(cacheFile) } thread, _ := strconv.Atoi(c) - if err := global.DownloadFileMultiThreading(f, cacheFile, maxImageSize, thread, nil); err != nil { + var maxSize = func() int64 { + if video { + return maxVideoSize + } + return maxImageSize + }() + if err := global.DownloadFileMultiThreading(f, cacheFile, maxSize, thread, nil); err != nil { return nil, err } + if video { + return &LocalVideoElement{ + File: cacheFile, + }, nil + } return &LocalImageElement{File: cacheFile}, 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 strings.HasPrefix(f, "file") { fu, err := url.Parse(f) if err != nil { @@ -856,10 +913,28 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa if err != nil { return nil, err } + if video { + goto videos + } if info.Size() == 0 || info.Size() >= maxImageSize { return nil, errors.New("invalid image size") } return &LocalImageElement{File: fu.Path}, nil + videos: + if info.Size() == 0 || info.Size() >= maxVideoSize { + return nil, errors.New("invalid video size") + } + return &LocalVideoElement{File: fu.Path}, nil + } + if video { // 短视频视频只支持以上两种 + return nil, errors.New("invalid video") + } + 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 } rawPath := path.Join(global.IMAGE_PATH, f) if !global.PathExists(rawPath) && global.PathExists(path.Join(global.IMAGE_PATH_OLD, f)) { @@ -869,7 +944,7 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa rawPath += ".cqimg" } if !global.PathExists(rawPath) && d["url"] != "" { - return bot.makeImageElem(map[string]string{"file": d["url"]}, group) + return bot.makeImageOrVideoElem(map[string]string{"file": d["url"]}, false, group) } if global.PathExists(rawPath) { file, err := os.Open(rawPath) @@ -886,9 +961,11 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa if len(b) < 20 { return nil, errors.New("invalid local file") } - var size int32 - var hash []byte - var url string + 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) @@ -909,27 +986,23 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa } if size == 0 { if url != "" { - return bot.makeImageElem(map[string]string{"file": url}, group) + 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) - if err != nil { - if url != "" { - return bot.makeImageElem(map[string]string{"file": url}, group) - } - return nil, err - } - return rsp, nil + rsp, err = bot.Client.QueryGroupImage(int64(rand.Uint32()), hash, size) + goto ok } - rsp, err := bot.Client.QueryFriendImage(int64(rand.Uint32()), hash, size) + rsp, err = bot.Client.QueryFriendImage(int64(rand.Uint32()), hash, size) + ok: if err != nil { if url != "" { - return bot.makeImageElem(map[string]string{"file": url}, group) + return bot.makeImageOrVideoElem(map[string]string{"file": url}, false, group) } return nil, err } diff --git a/global/codec.go b/global/codec.go index 346166c..26cd2dd 100644 --- a/global/codec.go +++ b/global/codec.go @@ -40,7 +40,7 @@ func EncoderSilk(data []byte) ([]byte, error) { } func EncodeMP4(src string, dst string) error { - cmd := exec.Command("ffmpeg", "-i", src, "-c", "copy", "-map", "0", dst) + cmd := exec.Command("ffmpeg", "-i", src, "-y", "-c", "copy", "-map", "0", dst) return cmd.Run() } diff --git a/go.mod b/go.mod index c2043a8..12ccf04 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp go 1.15 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20210105173234-72521dec9b56 + github.com/Mrs4s/MiraiGo v0.0.0-20210107163750-ce4834c2ba71 github.com/dustin/go-humanize v1.0.0 github.com/gin-contrib/pprof v1.3.0 github.com/gin-gonic/gin v1.6.3 diff --git a/go.sum b/go.sum index 3554dd5..ced49d9 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Mrs4s/MiraiGo v0.0.0-20210105173234-72521dec9b56 h1:U7kObHDk3RfaD81+1hA29gxHf3PfRGpX7dqR2UPNO0c= github.com/Mrs4s/MiraiGo v0.0.0-20210105173234-72521dec9b56/go.mod h1:HW2e375lCQiRwtuA/LV6ZVTsi7co1TRfBn+L5Ow77Bo= +github.com/Mrs4s/MiraiGo v0.0.0-20210107163750-ce4834c2ba71 h1:WAVoBY4G2BC5Dyw1A45r/sVua65hsBVYcncR42hEGrk= +github.com/Mrs4s/MiraiGo v0.0.0-20210107163750-ce4834c2ba71/go.mod h1:HW2e375lCQiRwtuA/LV6ZVTsi7co1TRfBn+L5Ow77Bo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=