1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-06-30 03:43:25 +00:00

Compare commits

...

38 Commits

Author SHA1 Message Date
2156b6083b update MiraiGo. 2021-01-16 06:00:33 +08:00
b47c2aeac6 Merge branch 'master' into dev 2021-01-16 05:59:31 +08:00
9beae584de Merge pull request #560 from wdvxdr1123/patch-2
fix #557
2021-01-14 02:31:34 +08:00
59cd986050 fix #561 2021-01-13 22:56:28 +08:00
7a9a021c54 fix codec path 2021-01-13 22:35:44 +08:00
de4de5052d fix #557 & remove some goto 2021-01-13 11:43:39 +08:00
b075280d2b fix build 2021-01-13 11:22:59 +08:00
4cb3d8f33e Merge pull request #559 from Ink-33/master
修复codec编译问题
2021-01-13 08:06:02 +08:00
0cd06daf08 Update github action 2021-01-13 04:05:50 +08:00
7ac31a8b76 Update github action 2021-01-13 04:00:01 +08:00
c9c6e48b8c add codec/codec_unsupported.go 2021-01-13 03:55:28 +08:00
0c3ba714f7 fix #558 2021-01-13 03:52:49 +08:00
1f7dcc4e91 fix forward img & video upload. 2021-01-13 02:58:54 +08:00
ef713bc59c fix show pic error. 2021-01-13 02:58:54 +08:00
85624d1a27 fix forward img & video upload. 2021-01-11 16:58:07 +08:00
b2d8a61a99 fix show pic error. 2021-01-11 14:36:29 +08:00
38a2c3a945 Merge pull request #550 from Ink-33/patch-1
Fix misspell
2021-01-11 00:53:32 +08:00
eaec0e545f fix thumb. 2021-01-11 00:50:50 +08:00
50f0f572bc fix multi-threading upload. 2021-01-11 00:45:49 +08:00
36532a8782 fix message. 2021-01-11 00:33:41 +08:00
620ecdbdbb update MiraiGo. 2021-01-11 00:22:59 +08:00
7737fc8457 update MiraiGo & qid support. 2021-01-10 21:30:35 +08:00
e03a989083 Fix misspell 2021-01-09 20:38:33 +08:00
56da9adeab Merge pull request #549 from wdvxdr1123/patch-2
fix file close
2021-01-09 17:56:11 +08:00
249e528457 support video cache file 2021-01-09 14:26:21 +08:00
1eeae13b4a fix file close 2021-01-09 12:23:24 +08:00
fe83ce716e update MiraiGo & fix user-agent & fix file lock. 2021-01-09 04:25:18 +08:00
a5a842a337 update MiraiGo. 2021-01-09 03:12:16 +08:00
be47ff3437 Merge pull request #548 from wdvxdr1123/patch-2
private video
2021-01-09 00:27:35 +08:00
392904f144 fix typo 2021-01-08 22:37:51 +08:00
a4c3ef8923 fix private video 2021-01-08 22:34:57 +08:00
816c142822 Merge pull request #547 from wdvxdr1123/patch-2
fix video
2021-01-08 22:18:02 +08:00
2e2002668b fix video 2021-01-08 22:14:23 +08:00
c58066e287 Merge pull request #544 from wdvxdr1123/patch-2
cq video
2021-01-08 17:38:30 +08:00
87ada9c57e update docs 2021-01-08 16:06:53 +08:00
b717e23626 feat CQ:video 2021-01-08 15:40:32 +08:00
f501b31152 encode mp4 2021-01-08 14:12:25 +08:00
2d020bc7f7 drop go-silk 2021-01-08 12:14:09 +08:00
16 changed files with 376 additions and 70 deletions

View File

@ -16,12 +16,16 @@ jobs:
matrix: matrix:
# build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/amd64 # build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/amd64
goos: [linux, windows, darwin] goos: [linux, windows, darwin]
goarch: ["386", amd64, arm] goarch: ["386", amd64, arm, arm64]
exclude: exclude:
- goos: darwin - goos: darwin
goarch: arm goarch: arm
- goos: darwin
goarch: arm64
- goos: darwin - goos: darwin
goarch: "386" goarch: "386"
- goos: windows
goarch: arm64
fail-fast: true fail-fast: true
steps: steps:

View File

@ -10,12 +10,16 @@ jobs:
matrix: matrix:
# build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/386, darwin/amd64 # build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/386, darwin/amd64
goos: [linux, windows, darwin] goos: [linux, windows, darwin]
goarch: ["386", amd64, arm] goarch: ["386", amd64, arm, arm64]
exclude: exclude:
- goos: darwin - goos: darwin
goarch: arm goarch: arm
- goos: darwin
goarch: arm64
- goos: darwin - goos: darwin
goarch: "386" goarch: "386"
- goos: windows
goarch: arm64
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -30,4 +34,4 @@ jobs:
goarch: ${{ matrix.goarch }} goarch: ${{ matrix.goarch }}
goversion: "https://golang.org/dl/go1.15.3.linux-amd64.tar.gz" goversion: "https://golang.org/dl/go1.15.3.linux-amd64.tar.gz"
ldflags: -w -s -X "github.com/Mrs4s/go-cqhttp/coolq.Version=${{ env.RELEASE_VERSION }}" ldflags: -w -s -X "github.com/Mrs4s/go-cqhttp/coolq.Version=${{ env.RELEASE_VERSION }}"

5
.gitignore vendored
View File

@ -1,2 +1,7 @@
vendor/ vendor/
.idea .idea
config.hjson
device.json
codec/
data/
logs/

View File

@ -303,7 +303,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
var newElem []message.IMessageElement var newElem []message.IMessageElement
for _, elem := range content { for _, elem := range content {
if img, ok := elem.(*LocalImageElement); ok { if img, ok := elem.(*LocalImageElement); ok {
gm, err := bot.Client.UploadGroupImage(groupId, img.Stream) gm, err := bot.UploadLocalImageAsGroup(groupId, img)
if err != nil { if err != nil {
log.Warnf("警告:群 %v 图片上传失败: %v", groupId, err) log.Warnf("警告:群 %v 图片上传失败: %v", groupId, err)
continue continue
@ -311,6 +311,15 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
newElem = append(newElem, gm) newElem = append(newElem, gm)
continue continue
} }
if video, ok := elem.(*LocalVideoElement); ok {
gm, err := bot.UploadLocalVideo(groupId, video)
if err != nil {
log.Warnf("警告:群 %v 视频上传失败: %v", groupId, err)
continue
}
newElem = append(newElem, gm)
continue
}
newElem = append(newElem, elem) newElem = append(newElem, elem)
} }
nodes = append(nodes, &message.ForwardNode{ nodes = append(nodes, &message.ForwardNode{
@ -345,7 +354,7 @@ func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}, autoEscape b
var str string var str string
if m, ok := i.(gjson.Result); ok { if m, ok := i.(gjson.Result); ok {
if m.Type == gjson.JSON { if m.Type == gjson.JSON {
elem := bot.ConvertObjectMessage(m, true) elem := bot.ConvertObjectMessage(m, false)
mid := bot.SendPrivateMessage(userId, &message.SendingMessage{Elements: elem}) mid := bot.SendPrivateMessage(userId, &message.SendingMessage{Elements: elem})
if mid == -1 { if mid == -1 {
return Failed(100, "SEND_MSG_API_ERROR", "请参考输出") return Failed(100, "SEND_MSG_API_ERROR", "请参考输出")
@ -649,6 +658,7 @@ func (bot *CQBot) CQGetStrangerInfo(userId int64) MSG {
return OK(MSG{ return OK(MSG{
"user_id": info.Uin, "user_id": info.Uin,
"nickname": info.Nickname, "nickname": info.Nickname,
"qid": info.Qid,
"sex": func() string { "sex": func() string {
if info.Sex == 1 { if info.Sex == 1 {
return "female" return "female"
@ -837,7 +847,7 @@ func (bot *CQBot) CQCanSendRecord() MSG {
} }
func (bot *CQBot) CQOcrImage(imageId string) 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 { if err != nil {
log.Warnf("load image error: %v", err) log.Warnf("load image error: %v", err)
return Failed(100, "LOAD_FILE_ERROR", err.Error()) return Failed(100, "LOAD_FILE_ERROR", err.Error())

View File

@ -3,8 +3,11 @@ package coolq
import ( import (
"bytes" "bytes"
"encoding/gob" "encoding/gob"
"encoding/hex"
"fmt" "fmt"
"github.com/Mrs4s/MiraiGo/utils"
"hash/crc32" "hash/crc32"
"io"
"os" "os"
"path" "path"
"runtime/debug" "runtime/debug"
@ -125,6 +128,22 @@ func (bot *CQBot) UploadLocalImageAsGroup(groupCode int64, img *LocalImageElemen
return bot.Client.UploadGroupImageByFile(groupCode, img.File) return bot.Client.UploadGroupImageByFile(groupCode, img.File)
} }
func (bot *CQBot) UploadLocalVideo(target int64, v *LocalVideoElement) (*message.ShortVideoElement, error) {
if v.File != "" {
video, err := os.Open(v.File)
if err != nil {
return nil, err
}
defer video.Close()
hash, _ := utils.ComputeMd5AndLength(io.MultiReader(video, v.thumb))
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache")
_, _ = video.Seek(0, io.SeekStart)
_, _ = v.thumb.Seek(0, io.SeekStart)
return bot.Client.UploadGroupShortVideo(target, video, v.thumb, cacheFile)
}
return &v.ShortVideoElement, nil
}
func (bot *CQBot) UploadLocalImageAsPrivate(userId int64, img *LocalImageElement) (*message.FriendImageElement, error) { func (bot *CQBot) UploadLocalImageAsPrivate(userId int64, img *LocalImageElement) (*message.FriendImageElement, error) {
if img.Stream != nil { if img.Stream != nil {
return bot.Client.UploadPrivateImage(userId, img.Stream) return bot.Client.UploadPrivateImage(userId, img.Stream)
@ -134,6 +153,7 @@ func (bot *CQBot) UploadLocalImageAsPrivate(userId int64, img *LocalImageElement
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close()
return bot.Client.UploadPrivateImage(userId, f) return bot.Client.UploadPrivateImage(userId, f)
} }
@ -158,6 +178,15 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
newElem = append(newElem, gv) newElem = append(newElem, gv)
continue continue
} }
if i, ok := elem.(*LocalVideoElement); ok {
gv, err := bot.UploadLocalVideo(groupId, i)
if err != nil {
log.Warnf("警告: 群 %v 消息短视频上传失败: %v", groupId, err)
continue
}
newElem = append(newElem, gv)
continue
}
if i, ok := elem.(*PokeElement); ok { if i, ok := elem.(*PokeElement); ok {
if group := bot.Client.FindGroup(groupId); group != nil { if group := bot.Client.FindGroup(groupId); group != nil {
if mem := group.FindMember(i.Target); mem != nil { if mem := group.FindMember(i.Target); mem != nil {
@ -272,12 +301,21 @@ func (bot *CQBot) SendPrivateMessage(target int64, m *message.SendingMessage) in
if i, ok := elem.(*message.VoiceElement); ok { if i, ok := elem.(*message.VoiceElement); ok {
fv, err := bot.Client.UploadPrivatePtt(target, i.Data) fv, err := bot.Client.UploadPrivatePtt(target, i.Data)
if err != nil { if err != nil {
log.Warnf("警告: 好友 %v 消息语音上传失败: %v", target, err) log.Warnf("警告: 私聊 %v 消息语音上传失败: %v", target, err)
continue continue
} }
newElem = append(newElem, fv) newElem = append(newElem, fv)
continue continue
} }
if i, ok := elem.(*LocalVideoElement); ok {
gv, err := bot.UploadLocalVideo(target, i)
if err != nil {
log.Warnf("警告: 私聊 %v 消息短视频上传失败: %v", target, err)
continue
}
newElem = append(newElem, gv)
continue
}
if i, ok := elem.(*QQMusicElement); ok { if i, ok := elem.(*QQMusicElement); ok {
var msgStyle uint32 = 4 var msgStyle uint32 = 4
if i.MusicUrl == "" { if i.MusicUrl == "" {

View File

@ -21,6 +21,7 @@ import (
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
@ -35,7 +36,8 @@ var paramReg = regexp.MustCompile(`,([\w\-.]+?)=([^,\]]+)`)
var IgnoreInvalidCQCode = false var IgnoreInvalidCQCode = false
var SplitUrl = 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 { type PokeElement struct {
Target int64 Target int64
@ -77,6 +79,12 @@ type LocalVoiceElement struct {
Stream io.ReadSeeker Stream io.ReadSeeker
} }
type LocalVideoElement struct {
message.ShortVideoElement
File string
thumb io.ReadSeeker
}
func (e *GiftElement) Type() message.ElementType { func (e *GiftElement) Type() message.ElementType {
return message.At return message.At
} }
@ -426,7 +434,7 @@ func (bot *CQBot) ConvertStringMessage(msg string, group bool) (r []message.IMes
} }
elem, err := bot.ToElement(t, params, group) elem, err := bot.ToElement(t, params, group)
if err != nil { if err != nil {
org := "[" + string(cqCode) + "]" org := "[CQ:" + string(cqCode) + "]"
if !IgnoreInvalidCQCode { if !IgnoreInvalidCQCode {
log.Warnf("转换CQ码 %v 时出现错误: %v 将原样发送.", org, err) log.Warnf("转换CQ码 %v 时出现错误: %v 将原样发送.", org, err)
r = append(r, message.NewText(org)) r = append(r, message.NewText(org))
@ -547,7 +555,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
} }
return message.NewText(d["text"]), nil return message.NewText(d["text"]), nil
case "image": case "image":
img, err := bot.makeImageElem(d, group) img, err := bot.makeImageOrVideoElem(d, false, group)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -605,7 +613,6 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
} }
}() }()
data, err := bot.Client.GetTts(d["text"]) data, err := bot.Client.GetTts(d["text"])
ioutil.WriteFile("tts.silk", data, 777)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -620,7 +627,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
return nil, err return nil, err
} }
if !global.IsAMRorSILK(data) { if !global.IsAMRorSILK(data) {
data, err = global.Encoder(data) data, err = global.EncoderSilk(data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -770,11 +777,55 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
if maxHeight == 0 { if maxHeight == 0 {
maxHeight = 1000 maxHeight = 1000
} }
img, err := bot.makeImageElem(d, group) img, err := bot.makeImageOrVideoElem(d, false, group)
if err != nil { if err != nil {
return nil, errors.New("send cardimage faild") return nil, errors.New("send cardimage faild")
} }
return bot.makeShowPic(img, source, icon, minWidth, minHeight, maxWidth, maxHeight, group) return bot.makeShowPic(img, source, icon, minWidth, minHeight, maxWidth, maxHeight, group)
case "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 v.File == "" {
return v, nil
}
var data []byte
if cover, ok := d["cover"]; ok {
data, _ = global.FindFile(cover, cache, global.IMAGE_PATH)
} 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 !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.CACHE_PATH, 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: default:
return nil, errors.New("unsupported cq code: " + t) return nil, errors.New("unsupported cq code: " + t)
} }
@ -816,7 +867,7 @@ func CQCodeUnescapeValue(content string) string {
} }
// 图片 elem 生成器,单独拎出来,用于公用 // 图片 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"] f := d["file"]
if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") { if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") {
cache := d["cache"] cache := d["cache"]
@ -826,25 +877,28 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa
} }
hash := md5.Sum([]byte(f)) hash := md5.Sum([]byte(f))
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache") cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache")
var maxSize = func() int64 {
if video {
return maxVideoSize
}
return maxImageSize
}()
thread, _ := strconv.Atoi(c)
if global.PathExists(cacheFile) && cache == "1" { if global.PathExists(cacheFile) && cache == "1" {
return &LocalImageElement{File: cacheFile}, nil goto hasCacheFile
} }
if global.PathExists(cacheFile) { if global.PathExists(cacheFile) {
_ = os.Remove(cacheFile) _ = os.Remove(cacheFile)
} }
thread, _ := strconv.Atoi(c) if err := global.DownloadFileMultiThreading(f, cacheFile, maxSize, thread, nil); err != nil {
if err := global.DownloadFileMultiThreading(f, cacheFile, maxImageSize, thread, nil); err != nil {
return nil, err return nil, err
} }
hasCacheFile:
if video {
return &LocalVideoElement{File: cacheFile}, nil
}
return &LocalImageElement{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") { if strings.HasPrefix(f, "file") {
fu, err := url.Parse(f) fu, err := url.Parse(f)
if err != nil { if err != nil {
@ -855,14 +909,50 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa
} }
info, err := os.Stat(fu.Path) info, err := os.Stat(fu.Path)
if err != nil { if err != nil {
if !os.IsExist(err) {
return nil, errors.New("file not found")
}
return nil, err 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 { if info.Size() == 0 || info.Size() >= maxImageSize {
return nil, errors.New("invalid image size") return nil, errors.New("invalid image size")
} }
return &LocalImageElement{File: fu.Path}, nil return &LocalImageElement{File: fu.Path}, nil
} }
rawPath := path.Join(global.IMAGE_PATH, f) rawPath := path.Join(global.IMAGE_PATH, f)
if video {
rawPath = path.Join(global.VIDEO_PATH, 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
} else {
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.IMAGE_PATH_OLD, f)) { if !global.PathExists(rawPath) && global.PathExists(path.Join(global.IMAGE_PATH_OLD, f)) {
rawPath = path.Join(global.IMAGE_PATH_OLD, f) rawPath = path.Join(global.IMAGE_PATH_OLD, f)
} }
@ -870,7 +960,7 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa
rawPath += ".cqimg" rawPath += ".cqimg"
} }
if !global.PathExists(rawPath) && d["url"] != "" { 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) { if global.PathExists(rawPath) {
file, err := os.Open(rawPath) file, err := os.Open(rawPath)
@ -887,9 +977,11 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa
if len(b) < 20 { if len(b) < 20 {
return nil, errors.New("invalid local file") return nil, errors.New("invalid local file")
} }
var size int32 var (
var hash []byte size int32
var url string hash []byte
url string
)
if path.Ext(rawPath) == ".cqimg" { if path.Ext(rawPath) == ".cqimg" {
for _, line := range strings.Split(global.ReadAllText(rawPath), "\n") { for _, line := range strings.Split(global.ReadAllText(rawPath), "\n") {
kv := strings.SplitN(line, "=", 2) kv := strings.SplitN(line, "=", 2)
@ -910,27 +1002,23 @@ func (bot *CQBot) makeImageElem(d map[string]string, group bool) (message.IMessa
} }
if size == 0 { if size == 0 {
if url != "" { 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") return nil, errors.New("img size is 0")
} }
if len(hash) != 16 { if len(hash) != 16 {
return nil, errors.New("invalid hash") return nil, errors.New("invalid hash")
} }
var rsp message.IMessageElement
if group { if group {
rsp, err := bot.Client.QueryGroupImage(int64(rand.Uint32()), hash, size) rsp, err = bot.Client.QueryGroupImage(int64(rand.Uint32()), hash, size)
if err != nil { goto ok
if url != "" {
return bot.makeImageElem(map[string]string{"file": url}, group)
}
return nil, err
}
return rsp, nil
} }
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 err != nil {
if url != "" { if url != "" {
return bot.makeImageElem(map[string]string{"file": url}, group) return bot.makeImageOrVideoElem(map[string]string{"file": url}, false, group)
} }
return nil, err return nil, err
} }
@ -945,7 +1033,7 @@ func (bot *CQBot) makeShowPic(elem message.IMessageElement, source string, icon
var suf message.IMessageElement var suf message.IMessageElement
if i, ok := elem.(*LocalImageElement); ok { if i, ok := elem.(*LocalImageElement); ok {
if group == false { if group == false {
gm, err := bot.Client.UploadPrivateImage(1, i.Stream) gm, err := bot.UploadLocalImageAsPrivate(1, i)
if err != nil { if err != nil {
log.Warnf("警告: 好友消息 %v 消息图片上传失败: %v", 1, err) log.Warnf("警告: 好友消息 %v 消息图片上传失败: %v", 1, err)
return nil, err return nil, err
@ -953,7 +1041,7 @@ func (bot *CQBot) makeShowPic(elem message.IMessageElement, source string, icon
suf = gm suf = gm
xml = fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="5" templateID="12345" action="" brief="&#91;分享&#93;我看到一张很赞的图片,分享给你,快来看!" sourceMsgId="0" url="%s" flag="0" adverSign="0" multiMsgFlag="0"><item layout="0" advertiser_id="0" aid="0"><image uuid="%x" md5="%x" GroupFiledid="0" filesize="%d" local_path="%s" minWidth="%d" minHeight="%d" maxWidth="%d" maxHeight="%d" /></item><source name="%s" icon="%s" action="" appid="-1" /></msg>`, "", gm.Md5, gm.Md5, len(i.Data), "", minWidth, minHeight, maxWidth, maxHeight, source, icon) xml = fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="5" templateID="12345" action="" brief="&#91;分享&#93;我看到一张很赞的图片,分享给你,快来看!" sourceMsgId="0" url="%s" flag="0" adverSign="0" multiMsgFlag="0"><item layout="0" advertiser_id="0" aid="0"><image uuid="%x" md5="%x" GroupFiledid="0" filesize="%d" local_path="%s" minWidth="%d" minHeight="%d" maxWidth="%d" maxHeight="%d" /></item><source name="%s" icon="%s" action="" appid="-1" /></msg>`, "", gm.Md5, gm.Md5, len(i.Data), "", minWidth, minHeight, maxWidth, maxHeight, source, icon)
} else { } else {
gm, err := bot.Client.UploadGroupImage(1, i.Stream) gm, err := bot.UploadLocalImageAsGroup(1, i)
if err != nil { if err != nil {
log.Warnf("警告: 群 %v 消息图片上传失败: %v", 1, err) log.Warnf("警告: 群 %v 消息图片上传失败: %v", 1, err)
return nil, err return nil, err

View File

@ -566,7 +566,9 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
if !global.PathExists(path.Join(global.VIDEO_PATH, filename)) { if !global.PathExists(path.Join(global.VIDEO_PATH, filename)) {
_ = ioutil.WriteFile(path.Join(global.VIDEO_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.VIDEO_PATH, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.Write(i.ThumbMd5)
w.WriteUInt32(uint32(i.Size)) w.WriteUInt32(uint32(i.Size))
w.WriteUInt32(uint32(i.ThumbSize))
w.WriteString(i.Name) w.WriteString(i.Name)
w.Write(i.Uuid) w.Write(i.Uuid)
}), 0644) }), 0644)

View File

@ -270,6 +270,20 @@ Type: `node`
} }
] ]
```` ````
### 短视频消息
Type: `video`
范围: **发送/接收**
参数:
| 参数名 | 类型 | 说明 |
| ------- | ------ | ------------------------------------------------|
| `file` | string | 支持http和file发送 |
| `cover` | string | 视频封面支持httpfile和base64发送格式必须为jpg |
| `c` | `2` `3`| 通过网络下载视频时的线程数, 默认单线程. (在资源不支持并发时会自动处理)|
示例: `[CQ:image,file=file:///C:\\Users\Richard\Pictures\1.mp4]`
### XML 消息 ### XML 消息

View File

@ -4,31 +4,26 @@ import (
"crypto/md5" "crypto/md5"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "github.com/Mrs4s/go-cqhttp/global/codec"
"path"
"sync"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/wdvxdr1123/go-silk/silk" "io/ioutil"
"os/exec"
"path"
) )
var codec silk.Encoder var useSilkCodec = true
var useCodec = true
var once sync.Once
func InitCodec() { func InitCodec() {
once.Do(func() { log.Info("正在加载silk编码器...")
log.Info("正在加载silk编码器...") err := codec.Init()
err := codec.Init("data/cache", "codec") if err != nil {
if err != nil { log.Error(err)
log.Error(err) useSilkCodec = false
useCodec = false }
}
})
} }
func Encoder(data []byte) ([]byte, error) { func EncoderSilk(data []byte) ([]byte, error) {
if useCodec == false { if useSilkCodec == false {
return nil, errors.New("no silk encoder") return nil, errors.New("no silk encoder")
} }
h := md5.New() h := md5.New()
@ -43,3 +38,13 @@ func Encoder(data []byte) ([]byte, error) {
} }
return slk, nil return slk, nil
} }
func EncodeMP4(src string, dst string) error { // -y 覆盖文件
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-c", "copy", "-map", "0", dst)
return cmd.Run()
}
func ExtractCover(src string, dst string) error {
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-r", "1", "-f", "image2", dst)
return cmd.Run()
}

95
global/codec/codec.go Normal file
View File

@ -0,0 +1,95 @@
// +build linux windows darwin
// +build 386 amd64 arm arm64
package codec
import (
"errors"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"runtime"
)
const (
silkCachePath = "data/cache"
encoderPath = "codec"
)
func downloadCodec(url string) (err error) {
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
err = ioutil.WriteFile(getEncoderFilePath(), body, os.ModePerm)
return
}
func getEncoderFilePath() string {
encoderFile := path.Join(encoderPath, runtime.GOOS+"-"+runtime.GOARCH+"-encoder")
if runtime.GOOS == "windows" {
encoderFile = encoderFile + ".exe"
}
return encoderFile
}
func Init() error {
if !fileExist(silkCachePath) {
_ = os.MkdirAll(silkCachePath, os.ModePerm)
}
if !fileExist(encoderPath) {
_ = os.MkdirAll(encoderPath, os.ModePerm)
}
p := getEncoderFilePath()
if !fileExist(p) {
if err := downloadCodec("https://cdn.jsdelivr.net/gh/wdvxdr1123/tosilk/codec/" + runtime.GOOS + "-" + runtime.GOARCH + "-encoder"); err != nil {
return errors.New("下载依赖失败")
}
}
return nil
}
func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) {
// 1. 写入缓存文件
rawPath := path.Join(silkCachePath, tempName+".wav")
err := ioutil.WriteFile(rawPath, record, os.ModePerm)
if err != nil {
return nil, err
}
defer os.Remove(rawPath)
// 2.转换pcm
pcmPath := path.Join(silkCachePath, tempName+".pcm")
cmd := exec.Command("ffmpeg", "-i", rawPath, "-f", "s16le", "-ar", "24000", "-ac", "1", pcmPath)
if err = cmd.Run(); err != nil {
return nil, err
}
defer os.Remove(pcmPath)
// 3. 转silk
silkPath := path.Join(silkCachePath, tempName+".silk")
cmd = exec.Command(getEncoderFilePath(), pcmPath, silkPath, "-rate", "24000", "-quiet", "-tencent")
if err = cmd.Run(); err != nil {
return nil, err
}
if useCache == false {
defer os.Remove(silkPath)
}
return ioutil.ReadFile(silkPath)
}
// FileExist 检查文件是否存在
func fileExist(path string) bool {
if runtime.GOOS == "windows" {
path = path + ".exe"
}
_, err := os.Lstat(path)
return !os.IsNotExist(err)
}

View File

@ -0,0 +1,13 @@
// +build !386,!arm64,!amd64,!arm
package codec
import "errors"
func Init() error {
return errors.New("Unsupport arch now")
}
func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) {
return nil, errors.New("Unsupport arch now")
}

View File

@ -0,0 +1,13 @@
// +build !windows,!linux,!darwin
package codec
import "errors"
func Init() error {
return errors.New("not support now")
}
func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) {
return nil, errors.New("not support now")
}

View File

@ -216,7 +216,7 @@ func UpdateFromStream(updateWith io.Reader) (err error, errRecover error) {
return return
} }
// move the new exectuable in to become the new program // move the new executable in to become the new program
err = os.Rename(newPath, updatePath) err = os.Rename(newPath, updatePath)
if err != nil { if err != nil {

View File

@ -23,7 +23,7 @@ import (
var ( var (
client = &http.Client{ client = &http.Client{
Timeout: time.Second * 30, Timeout: time.Second * 120,
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: func(request *http.Request) (u *url.URL, e error) { Proxy: func(request *http.Request) (u *url.URL, e error) {
if Proxy == "" { if Proxy == "" {
@ -47,6 +47,8 @@ var (
Proxy string Proxy string
ErrOverSize = errors.New("oversize") ErrOverSize = errors.New("oversize")
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
) )
func GetBytes(url string) ([]byte, error) { func GetBytes(url string) ([]byte, error) {
@ -54,7 +56,7 @@ func GetBytes(url string) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header["User-Agent"] = []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.61"} req.Header["User-Agent"] = []string{UserAgent}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -74,7 +76,7 @@ func GetBytes(url string) ([]byte, error) {
return body, nil return body, nil
} }
func DownloadFile(url, path string, limit int64) error { func DownloadFile(url, path string, limit int64, headers map[string]string) error {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666) file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil { if err != nil {
return err return err
@ -84,6 +86,14 @@ func DownloadFile(url, path string, limit int64) error {
if err != nil { if err != nil {
return err return err
} }
if headers != nil {
for k, v := range headers {
req.Header.Set(k, v)
}
}
if _, ok := headers["User-Agent"]; ok {
req.Header["User-Agent"] = []string{UserAgent}
}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
@ -101,7 +111,7 @@ func DownloadFile(url, path string, limit int64) error {
func DownloadFileMultiThreading(url, path string, limit int64, threadCount int, headers map[string]string) error { func DownloadFileMultiThreading(url, path string, limit int64, threadCount int, headers map[string]string) error {
if threadCount < 2 { if threadCount < 2 {
return DownloadFile(url, path, limit) return DownloadFile(url, path, limit, headers)
} }
type BlockMetaData struct { type BlockMetaData struct {
BeginOffset int64 BeginOffset int64
@ -133,6 +143,9 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
req.Header.Set(k, v) req.Header.Set(k, v)
} }
} }
if _, ok := headers["User-Agent"]; ok {
req.Header["User-Agent"] = []string{UserAgent}
}
req.Header.Set("range", "bytes=0-") req.Header.Set("range", "bytes=0-")
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
@ -194,6 +207,9 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
req.Header.Set(k, v) req.Header.Set(k, v)
} }
} }
if _, ok := headers["User-Agent"]; ok {
req.Header["User-Agent"] = []string{UserAgent}
}
req.Header.Set("range", "bytes="+strconv.FormatInt(block.BeginOffset, 10)+"-"+strconv.FormatInt(block.EndOffset, 10)) req.Header.Set("range", "bytes="+strconv.FormatInt(block.BeginOffset, 10)+"-"+strconv.FormatInt(block.EndOffset, 10))
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {

3
go.mod
View File

@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp
go 1.15 go 1.15
require ( require (
github.com/Mrs4s/MiraiGo v0.0.0-20210105173234-72521dec9b56 github.com/Mrs4s/MiraiGo v0.0.0-20210115215446-8ee19f60514b
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/gin-contrib/pprof v1.3.0 github.com/gin-contrib/pprof v1.3.0
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.6.3
@ -20,7 +20,6 @@ require (
github.com/syndtr/goleveldb v1.0.0 github.com/syndtr/goleveldb v1.0.0
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816
github.com/tidwall/gjson v1.6.7 github.com/tidwall/gjson v1.6.7
github.com/wdvxdr1123/go-silk v0.0.0-20201210140933-bcdbcb2f1093
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324

8
go.sum
View File

@ -1,7 +1,9 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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-20210110160231-b83dd4cf38a5 h1:ee6LafOcoVM0nox2UxiIJgomgRP4pDJe5aeT/rq2o/U=
github.com/Mrs4s/MiraiGo v0.0.0-20210105173234-72521dec9b56/go.mod h1:HW2e375lCQiRwtuA/LV6ZVTsi7co1TRfBn+L5Ow77Bo= github.com/Mrs4s/MiraiGo v0.0.0-20210110160231-b83dd4cf38a5/go.mod h1:HW2e375lCQiRwtuA/LV6ZVTsi7co1TRfBn+L5Ow77Bo=
github.com/Mrs4s/MiraiGo v0.0.0-20210115215446-8ee19f60514b h1:am1XzFc9Q5wSLLkrhjyDf5/IWq3qgNwJl2zzIRp2haw=
github.com/Mrs4s/MiraiGo v0.0.0-20210115215446-8ee19f60514b/go.mod h1:HW2e375lCQiRwtuA/LV6ZVTsi7co1TRfBn+L5Ow77Bo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -110,8 +112,6 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/wdvxdr1123/go-silk v0.0.0-20201210140933-bcdbcb2f1093 h1:t38EBwI2hFJz1sQJ402VqzdA3eMidkY1c3w/8z0SftA=
github.com/wdvxdr1123/go-silk v0.0.0-20201210140933-bcdbcb2f1093/go.mod h1:5q9LFlBr+yX/J8Jd/9wHdXwkkjFkNyQIS7kX2Lgx/Zs=
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 h1:4UJw9if55Fu3HOwbfcaQlJ27p3oeJU2JZqoeT3ITJQk= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 h1:4UJw9if55Fu3HOwbfcaQlJ27p3oeJU2JZqoeT3ITJQk=
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189/go.mod h1:rIrm5geMiBhPQkdfUm8gDFi/WiHneOp1i9KjmJqc+9I= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189/go.mod h1:rIrm5geMiBhPQkdfUm8gDFi/WiHneOp1i9KjmJqc+9I=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=