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

Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	main.go
This commit is contained in:
xuye 2021-01-24 17:29:26 +08:00
commit 61c9939f03
21 changed files with 716 additions and 383 deletions

View File

@ -4,6 +4,7 @@ import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"io/ioutil" "io/ioutil"
"math"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -260,10 +261,11 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
SenderId: sender.Uin, SenderId: sender.Uin,
SenderName: (&sender).DisplayName(), SenderName: (&sender).DisplayName(),
Time: func() int32 { Time: func() int32 {
if hasCustom { msgTime := m["time"].(int32)
if hasCustom && msgTime == 0 {
return int32(ts.Unix()) return int32(ts.Unix())
} }
return m["time"].(int32) return msgTime
}(), }(),
Message: bot.ConvertStringMessage(m["message"].(string), true), Message: bot.ConvertStringMessage(m["message"].(string), true),
}) })
@ -273,6 +275,10 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
return return
} }
uin, _ := strconv.ParseInt(e.Get("data.uin").Str, 10, 64) uin, _ := strconv.ParseInt(e.Get("data.uin").Str, 10, 64)
msgTime, err := strconv.ParseInt(e.Get("data.time").Str, 10, 64)
if err != nil {
msgTime = ts.Unix()
}
name := e.Get("data.name").Str name := e.Get("data.name").Str
c := e.Get("data.content") c := e.Get("data.content")
if c.IsArray() { if c.IsArray() {
@ -292,7 +298,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
nodes = append(nodes, &message.ForwardNode{ nodes = append(nodes, &message.ForwardNode{
SenderId: uin, SenderId: uin,
SenderName: name, SenderName: name,
Time: int32(ts.Unix()), Time: int32(msgTime),
Message: []message.IMessageElement{bot.Client.UploadGroupForwardMessage(groupId, &message.ForwardMessage{Nodes: taowa})}, Message: []message.IMessageElement{bot.Client.UploadGroupForwardMessage(groupId, &message.ForwardMessage{Nodes: taowa})},
}) })
return return
@ -325,7 +331,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
nodes = append(nodes, &message.ForwardNode{ nodes = append(nodes, &message.ForwardNode{
SenderId: uin, SenderId: uin,
SenderName: name, SenderName: name,
Time: int32(ts.Unix()), Time: int32(msgTime),
Message: newElem, Message: newElem,
}) })
return return
@ -343,7 +349,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
if len(sendNodes) > 0 { if len(sendNodes) > 0 {
gm := bot.Client.SendGroupForwardMessage(groupId, &message.ForwardMessage{Nodes: sendNodes}) gm := bot.Client.SendGroupForwardMessage(groupId, &message.ForwardMessage{Nodes: sendNodes})
return OK(MSG{ return OK(MSG{
"message_id": ToGlobalId(groupId, gm.Id), "message_id": bot.InsertGroupMessage(gm),
}) })
} }
return Failed(100) return Failed(100)
@ -662,8 +668,11 @@ func (bot *CQBot) CQGetStrangerInfo(userId int64) MSG {
"sex": func() string { "sex": func() string {
if info.Sex == 1 { if info.Sex == 1 {
return "female" return "female"
} else if info.Sex == 0 {
return "male"
} }
return "male" // unknown = 0x2
return "unknown"
}(), }(),
"age": info.Age, "age": info.Age,
"level": info.Level, "level": info.Level,
@ -730,10 +739,10 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
} }
func (bot *CQBot) CQGetImage(file string) MSG { func (bot *CQBot) CQGetImage(file string) MSG {
if !global.PathExists(path.Join(global.IMAGE_PATH, file)) { if !global.PathExists(path.Join(global.ImagePath, file)) {
return Failed(100) return Failed(100)
} }
if b, err := ioutil.ReadFile(path.Join(global.IMAGE_PATH, file)); err == nil { if b, err := ioutil.ReadFile(path.Join(global.ImagePath, file)); err == nil {
r := binary.NewReader(b) r := binary.NewReader(b)
r.ReadBytes(16) r.ReadBytes(16)
msg := MSG{ msg := MSG{
@ -741,7 +750,7 @@ func (bot *CQBot) CQGetImage(file string) MSG {
"filename": r.ReadString(), "filename": r.ReadString(),
"url": r.ReadString(), "url": r.ReadString(),
} }
local := path.Join(global.CACHE_PATH, file+"."+path.Ext(msg["filename"].(string))) local := path.Join(global.CachePath, file+"."+path.Ext(msg["filename"].(string)))
if !global.PathExists(local) { if !global.PathExists(local) {
if data, err := global.GetBytes(msg["url"].(string)); err == nil { if data, err := global.GetBytes(msg["url"].(string)); err == nil {
_ = ioutil.WriteFile(local, data, 0644) _ = ioutil.WriteFile(local, data, 0644)
@ -756,7 +765,7 @@ func (bot *CQBot) CQGetImage(file string) MSG {
func (bot *CQBot) CQDownloadFile(url string, headers map[string]string, threadCount int) MSG { func (bot *CQBot) CQDownloadFile(url string, headers map[string]string, threadCount int) MSG {
hash := md5.Sum([]byte(url)) hash := md5.Sum([]byte(url))
file := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache") file := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".cache")
if global.PathExists(file) { if global.PathExists(file) {
if err := os.Remove(file); err != nil { if err := os.Remove(file); err != nil {
log.Warnf("删除缓存文件 %v 时出现错误: %v", file, err) log.Warnf("删除缓存文件 %v 时出现错误: %v", file, err)
@ -804,10 +813,11 @@ func (bot *CQBot) CQGetMessage(messageId int32) MSG {
gid, isGroup := msg["group"] gid, isGroup := msg["group"]
raw := msg["message"].(string) raw := msg["message"].(string)
return OK(MSG{ return OK(MSG{
"message_id": messageId, "message_id": messageId,
"real_id": msg["message-id"], "real_id": msg["message-id"],
"group": isGroup, "message_seq": msg["message-id"],
"group_id": gid, "group": isGroup,
"group_id": gid,
"message_type": func() string { "message_type": func() string {
if isGroup { if isGroup {
return "group" return "group"
@ -838,6 +848,57 @@ func (bot *CQBot) CQGetGroupSystemMessages() MSG {
return OK(msg) return OK(msg)
} }
func (bot *CQBot) CQGetGroupMessageHistory(groupId int64, seq int64) MSG {
if g := bot.Client.FindGroup(groupId); g == nil {
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
}
if seq == 0 {
g, err := bot.Client.GetGroupInfo(groupId)
if err != nil {
return Failed(100, "GROUP_INFO_API_ERROR", err.Error())
}
seq = g.LastMsgSeq
}
msg, err := bot.Client.GetGroupMessages(groupId, int64(math.Max(float64(seq-19), 1)), seq)
if err != nil {
log.Warnf("获取群历史消息失败: %v", err)
return Failed(100, "MESSAGES_API_ERROR", err.Error())
}
var ms []MSG
for _, m := range msg {
id := m.Id
if bot.db != nil {
id = bot.InsertGroupMessage(m)
}
t := bot.formatGroupMessage(m)
t["message_id"] = id
ms = append(ms, t)
}
return OK(MSG{
"messages": ms,
})
}
func (bot *CQBot) CQGetOnlineClients(noCache bool) MSG {
if noCache {
if err := bot.Client.RefreshStatus(); err != nil {
log.Warnf("刷新客户端状态时出现问题 %v", err)
return Failed(100, "REFRESH_STATUS_ERROR", err.Error())
}
}
var d []MSG
for _, oc := range bot.Client.OnlineClients {
d = append(d, MSG{
"app_id": oc.AppId,
"device_name": oc.DeviceName,
"device_kind": oc.DeviceKind,
})
}
return OK(MSG{
"clients": d,
})
}
func (bot *CQBot) CQCanSendImage() MSG { func (bot *CQBot) CQCanSendImage() MSG {
return OK(MSG{"yes": true}) return OK(MSG{"yes": true})
} }
@ -867,7 +928,7 @@ func (bot *CQBot) CQReloadEventFilter() MSG {
func (bot *CQBot) CQSetGroupPortrait(groupId int64, file, cache string) MSG { func (bot *CQBot) CQSetGroupPortrait(groupId int64, file, cache string) MSG {
if g := bot.Client.FindGroup(groupId); g != nil { if g := bot.Client.FindGroup(groupId); g != nil {
img, err := global.FindFile(file, cache, global.IMAGE_PATH) img, err := global.FindFile(file, cache, global.ImagePath)
if err != nil { if err != nil {
log.Warnf("set group portrait error: %v", err) log.Warnf("set group portrait error: %v", err)
return Failed(100, "LOAD_FILE_ERROR", err.Error()) return Failed(100, "LOAD_FILE_ERROR", err.Error())
@ -958,11 +1019,19 @@ func Failed(code int, msg ...string) MSG {
func convertGroupMemberInfo(groupId int64, m *client.GroupMemberInfo) MSG { func convertGroupMemberInfo(groupId int64, m *client.GroupMemberInfo) MSG {
return MSG{ return MSG{
"group_id": groupId, "group_id": groupId,
"user_id": m.Uin, "user_id": m.Uin,
"nickname": m.Nickname, "nickname": m.Nickname,
"card": m.CardName, "card": m.CardName,
"sex": "unknown", "sex": func() string {
if m.Gender == 1 {
return "female"
} else if m.Gender == 0 {
return "male"
}
// unknown = 0xff
return "unknown"
}(),
"age": 0, "age": 0,
"area": "", "area": "",
"join_time": m.JoinTime, "join_time": m.JoinTime,

View File

@ -5,7 +5,6 @@ import (
"encoding/gob" "encoding/gob"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/Mrs4s/MiraiGo/utils"
"hash/crc32" "hash/crc32"
"io" "io"
"os" "os"
@ -14,6 +13,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
@ -41,7 +42,7 @@ type MSG map[string]interface{}
var ForceFragmented = false var ForceFragmented = false
func NewQQBot(cli *client.QQClient, conf *global.JsonConfig) *CQBot { func NewQQBot(cli *client.QQClient, conf *global.JSONConfig) *CQBot {
bot := &CQBot{ bot := &CQBot{
Client: cli, Client: cli,
} }
@ -76,6 +77,7 @@ func NewQQBot(cli *client.QQClient, conf *global.JsonConfig) *CQBot {
bot.Client.OnNewFriendAdded(bot.friendAddedEvent) bot.Client.OnNewFriendAdded(bot.friendAddedEvent)
bot.Client.OnGroupInvited(bot.groupInvitedEvent) bot.Client.OnGroupInvited(bot.groupInvitedEvent)
bot.Client.OnUserWantJoinGroup(bot.groupJoinReqEvent) bot.Client.OnUserWantJoinGroup(bot.groupJoinReqEvent)
bot.Client.OnOtherClientStatusChanged(bot.otherClientStatusChangedEvent)
go func() { go func() {
i := conf.HeartbeatInterval i := conf.HeartbeatInterval
if i < 0 { if i < 0 {
@ -136,7 +138,7 @@ func (bot *CQBot) UploadLocalVideo(target int64, v *LocalVideoElement) (*message
} }
defer video.Close() defer video.Close()
hash, _ := utils.ComputeMd5AndLength(io.MultiReader(video, v.thumb)) hash, _ := utils.ComputeMd5AndLength(io.MultiReader(video, v.thumb))
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache") cacheFile := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".cache")
_, _ = video.Seek(0, io.SeekStart) _, _ = video.Seek(0, io.SeekStart)
_, _ = v.thumb.Seek(0, io.SeekStart) _, _ = v.thumb.Seek(0, io.SeekStart)
return bot.Client.UploadGroupShortVideo(target, video, v.thumb, cacheFile) return bot.Client.UploadGroupShortVideo(target, video, v.thumb, cacheFile)
@ -271,13 +273,7 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
ret := bot.Client.SendGroupMessage(groupId, m, ForceFragmented) ret := bot.Client.SendGroupMessage(groupId, m, ForceFragmented)
if ret == nil || ret.Id == -1 { if ret == nil || ret.Id == -1 {
log.Warnf("群消息发送失败: 账号可能被风控.") log.Warnf("群消息发送失败: 账号可能被风控.")
if !ForceFragmented { return -1
log.Warnf("将尝试分片发送...")
ret = bot.Client.SendGroupMessage(groupId, m, true)
}
if ret == nil || ret.Id == -1 {
return -1
}
} }
return bot.InsertGroupMessage(ret) return bot.InsertGroupMessage(ret)
} }
@ -456,7 +452,7 @@ func (bot *CQBot) Release() {
} }
func (bot *CQBot) dispatchEventMessage(m MSG) { func (bot *CQBot) dispatchEventMessage(m MSG) {
if global.EventFilter != nil && global.EventFilter.Eval(global.MSG(m)) == false { if global.EventFilter != nil && !global.EventFilter.Eval(global.MSG(m)) {
log.Debug("Event filtered!") log.Debug("Event filtered!")
return return
} }
@ -477,6 +473,57 @@ func (bot *CQBot) dispatchEventMessage(m MSG) {
} }
} }
func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) MSG {
cqm := ToStringMessage(m.Elements, m.GroupCode, true)
gm := MSG{
"anonymous": nil,
"font": 0,
"group_id": m.GroupCode,
"message": ToFormattedMessage(m.Elements, m.GroupCode, false),
"message_type": "group",
"message_seq": m.Id,
"post_type": "message",
"raw_message": cqm,
"self_id": bot.Client.Uin,
"sender": MSG{
"age": 0,
"area": "",
"level": "",
"sex": "unknown",
"user_id": m.Sender.Uin,
},
"sub_type": "normal",
"time": time.Now().Unix(),
"user_id": m.Sender.Uin,
}
if m.Sender.IsAnonymous() {
gm["anonymous"] = MSG{
"flag": m.Sender.AnonymousInfo.AnonymousId + "|" + m.Sender.AnonymousInfo.AnonymousNick,
"id": m.Sender.Uin,
"name": m.Sender.AnonymousInfo.AnonymousNick,
}
gm["sender"].(MSG)["nickname"] = "匿名消息"
gm["sub_type"] = "anonymous"
} else {
mem := bot.Client.FindGroup(m.GroupCode).FindMember(m.Sender.Uin)
ms := gm["sender"].(MSG)
ms["role"] = func() string {
switch mem.Permission {
case client.Owner:
return "owner"
case client.Administrator:
return "admin"
default:
return "member"
}
}()
ms["nickname"] = mem.Nickname
ms["card"] = mem.CardName
ms["title"] = mem.SpecialTitle
}
return gm
}
func formatGroupName(group *client.GroupInfo) string { func formatGroupName(group *client.GroupInfo) string {
return fmt.Sprintf("%s(%d)", group.Name, group.Code) return fmt.Sprintf("%s(%d)", group.Name, group.Code)
} }

View File

@ -18,6 +18,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/message"
@ -132,7 +133,7 @@ func ToArrayMessage(e []message.IMessageElement, code int64, raw ...bool) (r []M
}) })
} }
for _, elem := range e { for _, elem := range e {
m := MSG{} var m MSG
switch o := elem.(type) { switch o := elem.(type) {
case *message.TextElement: case *message.TextElement:
m = MSG{ m = MSG{
@ -369,7 +370,7 @@ func (bot *CQBot) ConvertStringMessage(msg string, group bool) (r []message.IMes
saveTempText := func() { saveTempText := func() {
if len(tempText) != 0 { if len(tempText) != 0 {
if SplitUrl { if SplitUrl {
for _, t := range global.SplitUrl(CQCodeUnescapeValue(string(tempText))) { for _, t := range global.SplitURL(CQCodeUnescapeValue(string(tempText))) {
r = append(r, message.NewText(t)) r = append(r, message.NewText(t))
} }
} else { } else {
@ -411,6 +412,7 @@ func (bot *CQBot) ConvertStringMessage(msg string, group bool) (r []message.IMes
} }
} }
mid, err := strconv.Atoi(params["id"]) mid, err := strconv.Atoi(params["id"])
customText := params["text"]
if err == nil { if err == nil {
org := bot.GetMessage(int32(mid)) org := bot.GetMessage(int32(mid))
if org != nil { if org != nil {
@ -424,6 +426,25 @@ func (bot *CQBot) ConvertStringMessage(msg string, group bool) (r []message.IMes
}, r...) }, r...)
return 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, group),
},
}, r...)
return
} }
} }
if t == "forward" { // 单独处理转发 if t == "forward" { // 单独处理转发
@ -490,6 +511,7 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, group bool) (r []message.
} }
} }
mid, err := strconv.Atoi(e.Get("data").Get("id").String()) mid, err := strconv.Atoi(e.Get("data").Get("id").String())
customText := e.Get("data").Get("text").String()
if err == nil { if err == nil {
org := bot.GetMessage(int32(mid)) org := bot.GetMessage(int32(mid))
if org != nil { if org != nil {
@ -503,6 +525,25 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, group bool) (r []message.
}, r...) }, r...)
return 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, group),
},
}, r...)
return
} }
} }
if t == "forward" { if t == "forward" {
@ -548,7 +589,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
case "text": case "text":
if SplitUrl { if SplitUrl {
var ret []message.IMessageElement var ret []message.IMessageElement
for _, text := range global.SplitUrl(d["text"]) { for _, text := range global.SplitURL(d["text"]) {
ret = append(ret, message.NewText(text)) ret = append(ret, message.NewText(text))
} }
return ret, nil return ret, nil
@ -619,9 +660,9 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
return &message.VoiceElement{Data: data}, nil return &message.VoiceElement{Data: data}, nil
case "record": case "record":
f := d["file"] f := d["file"]
data, err := global.FindFile(f, d["cache"], global.VOICE_PATH) data, err := global.FindFile(f, d["cache"], global.VoicePath)
if err == global.ErrSyntax { if err == global.ErrSyntax {
data, err = global.FindFile(f, d["cache"], global.VOICE_PATH_OLD) data, err = global.FindFile(f, d["cache"], global.VoicePathOld)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -797,7 +838,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
} }
var data []byte var data []byte
if cover, ok := d["cover"]; ok { if cover, ok := d["cover"]; ok {
data, _ = global.FindFile(cover, cache, global.IMAGE_PATH) data, _ = global.FindFile(cover, cache, global.ImagePath)
} else { } else {
_ = global.ExtractCover(v.File, v.File+".jpg") _ = global.ExtractCover(v.File, v.File+".jpg")
data, _ = ioutil.ReadFile(v.File + ".jpg") data, _ = ioutil.ReadFile(v.File + ".jpg")
@ -811,10 +852,13 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (m interf
} }
var header = make([]byte, 4) var header = make([]byte, 4)
_, err = video.Read(header) _, err = video.Read(header)
if err != nil {
return nil, err
}
if !bytes.Equal(header, []byte{0x66, 0x74, 0x79, 0x70}) { // check file header ftyp if !bytes.Equal(header, []byte{0x66, 0x74, 0x79, 0x70}) { // check file header ftyp
_, _ = video.Seek(0, io.SeekStart) _, _ = video.Seek(0, io.SeekStart)
hash, _ := utils.ComputeMd5AndLength(video) hash, _ := utils.ComputeMd5AndLength(video)
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".mp4") cacheFile := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".mp4")
if global.PathExists(cacheFile) && cache == "1" { if global.PathExists(cacheFile) && cache == "1" {
goto ok goto ok
} }
@ -876,7 +920,7 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video, group bool) (
cache = "1" cache = "1"
} }
hash := md5.Sum([]byte(f)) hash := md5.Sum([]byte(f))
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache") cacheFile := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".cache")
var maxSize = func() int64 { var maxSize = func() int64 {
if video { if video {
return maxVideoSize return maxVideoSize
@ -925,9 +969,9 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video, group bool) (
} }
return &LocalImageElement{File: fu.Path}, nil return &LocalImageElement{File: fu.Path}, nil
} }
rawPath := path.Join(global.IMAGE_PATH, f) rawPath := path.Join(global.ImagePath, f)
if video { if video {
rawPath = path.Join(global.VIDEO_PATH, f) rawPath = path.Join(global.VideoPath, f)
if !global.PathExists(rawPath) { if !global.PathExists(rawPath) {
return nil, errors.New("invalid video") return nil, errors.New("invalid video")
} }
@ -953,8 +997,8 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video, group bool) (
} }
return &LocalImageElement{Stream: bytes.NewReader(b)}, nil 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.ImagePathOld, f)) {
rawPath = path.Join(global.IMAGE_PATH_OLD, f) rawPath = path.Join(global.ImagePathOld, f)
} }
if !global.PathExists(rawPath) && global.PathExists(rawPath+".cqimg") { if !global.PathExists(rawPath) && global.PathExists(rawPath+".cqimg") {
rawPath += ".cqimg" rawPath += ".cqimg"
@ -1032,7 +1076,7 @@ func (bot *CQBot) makeShowPic(elem message.IMessageElement, source string, icon
xml := "" xml := ""
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 {
gm, err := bot.UploadLocalImageAsPrivate(1, i) gm, err := bot.UploadLocalImageAsPrivate(1, i)
if err != nil { if err != nil {
log.Warnf("警告: 好友消息 %v 消息图片上传失败: %v", 1, err) log.Warnf("警告: 好友消息 %v 消息图片上传失败: %v", 1, err)

View File

@ -17,15 +17,17 @@ import (
var format = "string" var format = "string"
//SetMessageFormat 设置消息上报格式默认为string
func SetMessageFormat(f string) { func SetMessageFormat(f string) {
format = f format = f
} }
func ToFormattedMessage(e []message.IMessageElement, code int64, raw ...bool) (r interface{}) { //ToFormattedMessage 将给定[]message.IMessageElement转换为通过coolq.SetMessageFormat所定义的消息上报格式
func ToFormattedMessage(e []message.IMessageElement, id int64, raw ...bool) (r interface{}) {
if format == "string" { if format == "string" {
r = ToStringMessage(e, code, raw...) r = ToStringMessage(e, id, raw...)
} else if format == "array" { } else if format == "array" {
r = ToArrayMessage(e, code, raw...) r = ToArrayMessage(e, id, raw...)
} }
return return
} }
@ -91,52 +93,8 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
id = bot.InsertGroupMessage(m) id = bot.InsertGroupMessage(m)
} }
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id) log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
gm := MSG{ gm := bot.formatGroupMessage(m)
"anonymous": nil, gm["message_id"] = id
"font": 0,
"group_id": m.GroupCode,
"message": ToFormattedMessage(m.Elements, m.GroupCode, false),
"message_id": id,
"message_type": "group",
"post_type": "message",
"raw_message": cqm,
"self_id": c.Uin,
"sender": MSG{
"age": 0,
"area": "",
"level": "",
"sex": "unknown",
"user_id": m.Sender.Uin,
},
"sub_type": "normal",
"time": time.Now().Unix(),
"user_id": m.Sender.Uin,
}
if m.Sender.IsAnonymous() {
gm["anonymous"] = MSG{
"flag": m.Sender.AnonymousInfo.AnonymousId + "|" + m.Sender.AnonymousInfo.AnonymousNick,
"id": m.Sender.Uin,
"name": m.Sender.AnonymousInfo.AnonymousNick,
}
gm["sender"].(MSG)["nickname"] = "匿名消息"
gm["sub_type"] = "anonymous"
} else {
mem := c.FindGroup(m.GroupCode).FindMember(m.Sender.Uin)
ms := gm["sender"].(MSG)
ms["role"] = func() string {
switch mem.Permission {
case client.Owner:
return "owner"
case client.Administrator:
return "admin"
default:
return "member"
}
}()
ms["nickname"] = mem.Nickname
ms["card"] = mem.CardName
ms["title"] = mem.SpecialTitle
}
bot.dispatchEventMessage(gm) bot.dispatchEventMessage(gm)
} }
@ -455,6 +413,27 @@ func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupR
}) })
} }
func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.OtherClientStatusChangedEvent) {
if e.Online {
log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
} else {
log.Infof("Bot 账号在客户端 %v (%v) 登出.", e.Client.DeviceName, e.Client.DeviceKind)
}
bot.dispatchEventMessage(MSG{
"post_type": "notice",
"notice_type": "client_status",
"online": e.Online,
"client": MSG{
"app_id": e.Client.AppId,
"device_name": e.Client.DeviceName,
"device_kind": e.Client.DeviceKind,
},
"self_id": c.Uin,
"time": time.Now().Unix(),
})
}
func (bot *CQBot) groupIncrease(groupCode, operatorUin, userUin int64) MSG { func (bot *CQBot) groupIncrease(groupCode, operatorUin, userUin int64) MSG {
return MSG{ return MSG{
"post_type": "notice", "post_type": "notice",
@ -499,8 +478,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
switch i := elem.(type) { switch i := elem.(type) {
case *message.ImageElement: case *message.ImageElement:
filename := hex.EncodeToString(i.Md5) + ".image" filename := hex.EncodeToString(i.Md5) + ".image"
if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { if !global.PathExists(path.Join(global.ImagePath, filename)) {
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.ImagePath, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.WriteUInt32(uint32(i.Size)) w.WriteUInt32(uint32(i.Size))
w.WriteString(i.Filename) w.WriteString(i.Filename)
@ -510,8 +489,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
i.Filename = filename i.Filename = filename
case *message.GroupImageElement: case *message.GroupImageElement:
filename := hex.EncodeToString(i.Md5) + ".image" filename := hex.EncodeToString(i.Md5) + ".image"
if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { if !global.PathExists(path.Join(global.ImagePath, filename)) {
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.ImagePath, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.WriteUInt32(uint32(i.Size)) w.WriteUInt32(uint32(i.Size))
w.WriteString(filename) w.WriteString(filename)
@ -520,8 +499,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
} }
case *message.FriendImageElement: case *message.FriendImageElement:
filename := hex.EncodeToString(i.Md5) + ".image" filename := hex.EncodeToString(i.Md5) + ".image"
if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { if !global.PathExists(path.Join(global.ImagePath, filename)) {
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.ImagePath, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.WriteUInt32(uint32(0)) // 发送时会调用url, 大概没事 w.WriteUInt32(uint32(0)) // 发送时会调用url, 大概没事
w.WriteString(filename) w.WriteString(filename)
@ -530,8 +509,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
} }
case *message.GroupFlashImgElement: case *message.GroupFlashImgElement:
filename := hex.EncodeToString(i.Md5) + ".image" filename := hex.EncodeToString(i.Md5) + ".image"
if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { if !global.PathExists(path.Join(global.ImagePath, filename)) {
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.ImagePath, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.WriteUInt32(uint32(i.Size)) w.WriteUInt32(uint32(i.Size))
w.WriteString(i.Filename) w.WriteString(i.Filename)
@ -541,8 +520,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
i.Filename = filename i.Filename = filename
case *message.FriendFlashImgElement: case *message.FriendFlashImgElement:
filename := hex.EncodeToString(i.Md5) + ".image" filename := hex.EncodeToString(i.Md5) + ".image"
if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { if !global.PathExists(path.Join(global.ImagePath, filename)) {
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.ImagePath, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.WriteUInt32(uint32(i.Size)) w.WriteUInt32(uint32(i.Size))
w.WriteString(i.Filename) w.WriteString(i.Filename)
@ -553,18 +532,18 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) {
case *message.VoiceElement: case *message.VoiceElement:
i.Name = strings.ReplaceAll(i.Name, "{", "") i.Name = strings.ReplaceAll(i.Name, "{", "")
i.Name = strings.ReplaceAll(i.Name, "}", "") i.Name = strings.ReplaceAll(i.Name, "}", "")
if !global.PathExists(path.Join(global.VOICE_PATH, i.Name)) { if !global.PathExists(path.Join(global.VoicePath, i.Name)) {
b, err := global.GetBytes(i.Url) b, err := global.GetBytes(i.Url)
if err != nil { if err != nil {
log.Warnf("语音文件 %v 下载失败: %v", i.Name, err) log.Warnf("语音文件 %v 下载失败: %v", i.Name, err)
continue continue
} }
_ = ioutil.WriteFile(path.Join(global.VOICE_PATH, i.Name), b, 0644) _ = ioutil.WriteFile(path.Join(global.VoicePath, i.Name), b, 0644)
} }
case *message.ShortVideoElement: case *message.ShortVideoElement:
filename := hex.EncodeToString(i.Md5) + ".video" filename := hex.EncodeToString(i.Md5) + ".video"
if !global.PathExists(path.Join(global.VIDEO_PATH, filename)) { if !global.PathExists(path.Join(global.VideoPath, filename)) {
_ = ioutil.WriteFile(path.Join(global.VIDEO_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = ioutil.WriteFile(path.Join(global.VideoPath, filename), binary.NewWriterF(func(w *binary.Writer) {
w.Write(i.Md5) w.Write(i.Md5)
w.Write(i.ThumbMd5) w.Write(i.ThumbMd5)
w.WriteUInt32(uint32(i.Size)) w.WriteUInt32(uint32(i.Size))

View File

@ -94,13 +94,23 @@ Type : `reply`
范围: **发送/接收** 范围: **发送/接收**
> 注意: 如果id存在则优先处理id
参数: 参数:
| 参数名 | 类型 | 说明 | | 参数名 | 类型 | 说明 |
| ------ | ---- | ------------------------------------- | | ------ | ---- | ------------------------------------- |
| `id` | int | 回复时所引用的消息id, 必须为本群消息. | | `id` | int | 回复时所引用的消息id, 必须为本群消息. |
| `text` | string | 自定义回复的信息 |
| `qq` | int64 | 自定义回复时的自定义QQ, 如果使用自定义信息必须指定. |
| `time` | int64 | 自定义回复时的时间, 格式为Unix时间 |
示例: `[CQ:reply,id=123456]`
\
自定义回复示例: `[CQ:reply,text=Hello World,qq=10086,time=3376656000]`
示例: `[CQ:reply,id=123456]`
### 红包 ### 红包
@ -197,7 +207,7 @@ Type: `node`
| `id` | int32 | 转发消息id | 直接引用他人的消息合并转发, 实际查看顺序为原消息发送顺序 **与下面的自定义消息二选一** | | `id` | int32 | 转发消息id | 直接引用他人的消息合并转发, 实际查看顺序为原消息发送顺序 **与下面的自定义消息二选一** |
| `name` | string | 发送者显示名字 | 用于自定义消息 (自定义消息并合并转发,实际查看顺序为自定义消息段顺序) | | `name` | string | 发送者显示名字 | 用于自定义消息 (自定义消息并合并转发,实际查看顺序为自定义消息段顺序) |
| `uin` | int64 | 发送者QQ号 | 用于自定义消息 | | `uin` | int64 | 发送者QQ号 | 用于自定义消息 |
| `content` | message | 具体消息 | 用于自定义消息 **不支持引用回复** | | `content` | message | 具体消息 | 用于自定义消息 |
特殊说明: **需要使用单独的API `/send_group_forward_msg` 发送并且由于消息段较为复杂仅支持Array形式入参。 如果引用消息和自定义消息同时出现,实际查看顺序将取消息段顺序. 另外按 [CQHTTP](https://cqhttp.cc/docs/4.15/#/Message?id=格式) 文档说明, `data` 应全为字符串, 但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃** 特殊说明: **需要使用单独的API `/send_group_forward_msg` 发送并且由于消息段较为复杂仅支持Array形式入参。 如果引用消息和自定义消息同时出现,实际查看顺序将取消息段顺序. 另外按 [CQHTTP](https://cqhttp.cc/docs/4.15/#/Message?id=格式) 文档说明, `data` 应全为字符串, 但由于需要接收`message` 类型的消息, 所以 *仅限此Type的content字段* 支持Array套娃**
@ -259,7 +269,8 @@ Type: `node`
"data": { "data": {
"name": "自定义发送者", "name": "自定义发送者",
"uin": "10086", "uin": "10086",
"content": "我是自定义消息" "content": "我是自定义消息",
"time": "3376656000"
} }
}, },
{ {
@ -539,6 +550,12 @@ Type: `tts`
| `group_id` | int64 | 群号 | | `group_id` | int64 | 群号 |
| `messages` | forward node[] | 自定义转发消息, 具体看CQCode | | `messages` | forward node[] | 自定义转发消息, 具体看CQCode |
响应数据
| 字段 | 类型 | 说明 |
| ------------ | ------ | ------ |
| `message_id` | string | 消息id |
### 获取中文分词 ### 获取中文分词
终结点: `/.get_word_slices` 终结点: `/.get_word_slices`
@ -814,6 +831,49 @@ JSON数组:
> 通过这个API下载的文件能直接放入CQ码作为图片或语音发送 > 通过这个API下载的文件能直接放入CQ码作为图片或语音发送
> 调用后会阻塞直到下载完成后才会返回数据,请注意下载大文件时的超时 > 调用后会阻塞直到下载完成后才会返回数据,请注意下载大文件时的超时
### 获取群消息历史记录
终结点:`/get_group_msg_history`
**参数**
| 字段 | 类型 | 说明 |
| ---------- | ------ | ------------------------- |
| `message_seq` | int64 | 起始消息序号, 可通过 `get_msg` 获得 |
| `group_id` | int64 | 群号 |
**响应数据**
| 字段 | 类型 | 说明 |
| ---------- | ---------- | ------------ |
| `messages` | []Message | 从起始序号开始的前19条消息 |
> 不提供起始序号将默认获取最新的消息
### 获取当前账号在线客户端列表
终结点:`/get_online_clients`
**参数**
| 字段 | 类型 | 说明 |
| ---------- | ------ | ------------------------- |
| `no_cache` | bool | 是否无视缓存 |
**响应数据**
| 字段 | 类型 | 说明 |
| ---------- | ---------- | ------------ |
| `clients` | []Device | 在线客户端列表 |
**Device**
| 字段 | 类型 | 说明 |
| ---------- | ---------- | ------------ |
| `app_id` | int64 | 客户端ID |
| `device_name` | string | 设备名称 |
| `device_kind` | string | 设备类型 |
### 获取用户VIP信息 ### 获取用户VIP信息
终结点:`/_get_vip_info` 终结点:`/_get_vip_info`
@ -977,3 +1037,14 @@ JSON数组:
| `name` | string | | 文件名 | | `name` | string | | 文件名 |
| `size` | int64 | | 文件大小 | | `size` | int64 | | 文件大小 |
| `url` | string | | 下载链接 | | `url` | string | | 下载链接 |
### 其他客户端在线状态变更
**上报数据**
| 字段 | 类型 | 可能的值 | 说明 |
| ------------- | ------ | -------------- | -------- |
| `post_type` | string | `notice` | 上报类型 |
| `notice_type` | string | `client_status` | 消息类型 |
| `client` | Device | | 客户端信息 |
| `online` | bool | | 当前是否在线 |

View File

@ -4,15 +4,17 @@ import (
"crypto/md5" "crypto/md5"
"errors" "errors"
"fmt" "fmt"
"github.com/Mrs4s/go-cqhttp/global/codec"
log "github.com/sirupsen/logrus"
"io/ioutil" "io/ioutil"
"os/exec" "os/exec"
"path" "path"
"github.com/Mrs4s/go-cqhttp/global/codec"
log "github.com/sirupsen/logrus"
) )
var useSilkCodec = true var useSilkCodec = true
//InitCodec 初始化Silk编码器
func InitCodec() { func InitCodec() {
log.Info("正在加载silk编码器...") log.Info("正在加载silk编码器...")
err := codec.Init() err := codec.Init()
@ -22,12 +24,16 @@ func InitCodec() {
} }
} }
//EncoderSilk 将音频编码为Silk
func EncoderSilk(data []byte) ([]byte, error) { func EncoderSilk(data []byte) ([]byte, error) {
if useSilkCodec == false { if !useSilkCodec {
return nil, errors.New("no silk encoder") return nil, errors.New("no silk encoder")
} }
h := md5.New() h := md5.New()
h.Write(data) _, err := h.Write(data)
if err != nil {
return nil, err
}
tempName := fmt.Sprintf("%x", h.Sum(nil)) tempName := fmt.Sprintf("%x", h.Sum(nil))
if silkPath := path.Join("data/cache", tempName+".silk"); PathExists(silkPath) { if silkPath := path.Join("data/cache", tempName+".silk"); PathExists(silkPath) {
return ioutil.ReadFile(silkPath) return ioutil.ReadFile(silkPath)
@ -39,12 +45,19 @@ func EncoderSilk(data []byte) ([]byte, error) {
return slk, nil return slk, nil
} }
//EncodeMP4 将给定视频文件编码为MP4
func EncodeMP4(src string, dst string) error { // -y 覆盖文件 func EncodeMP4(src string, dst string) error { // -y 覆盖文件
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-c", "copy", "-map", "0", dst) cmd1 := exec.Command("ffmpeg", "-i", src, "-y", "-c", "copy", "-map", "0", dst)
return cmd.Run() err := cmd1.Run()
if err != nil {
cmd2 := exec.Command("ffmpeg", "-i", src, "-y", "-c:v", "h264", "-c:a", "mp3", dst)
return cmd2.Run()
}
return err
} }
func ExtractCover(src string, dst string) error { //ExtractCover 获取给定视频文件的Cover
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-r", "1", "-f", "image2", dst) func ExtractCover(src string, target string) error {
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-r", "1", "-f", "image2", target)
return cmd.Run() return cmd.Run()
} }

View File

@ -40,6 +40,7 @@ func getEncoderFilePath() string {
return encoderFile return encoderFile
} }
//Init 下载Silk编码器
func Init() error { func Init() error {
if !fileExist(silkCachePath) { if !fileExist(silkCachePath) {
_ = os.MkdirAll(silkCachePath, os.ModePerm) _ = os.MkdirAll(silkCachePath, os.ModePerm)
@ -56,6 +57,7 @@ func Init() error {
return nil return nil
} }
//EncodeToSilk 将音频编码为Silk
func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) { func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) {
// 1. 写入缓存文件 // 1. 写入缓存文件
rawPath := path.Join(silkCachePath, tempName+".wav") rawPath := path.Join(silkCachePath, tempName+".wav")
@ -79,7 +81,7 @@ func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error)
if err = cmd.Run(); err != nil { if err = cmd.Run(); err != nil {
return nil, err return nil, err
} }
if useCache == false { if !useCache {
defer os.Remove(silkPath) defer os.Remove(silkPath)
} }
return ioutil.ReadFile(silkPath) return ioutil.ReadFile(silkPath)

View File

@ -4,10 +4,12 @@ package codec
import "errors" import "errors"
//Init 下载silk编码器
func Init() error { func Init() error {
return errors.New("Unsupport arch now") return errors.New("Unsupport arch now")
} }
//EncodeToSilk 将音频编码为Silk
func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) { func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) {
return nil, errors.New("Unsupport arch now") return nil, errors.New("Unsupport arch now")
} }

View File

@ -4,10 +4,12 @@ package codec
import "errors" import "errors"
//Init 下载silk编码器
func Init() error { func Init() error {
return errors.New("not support now") return errors.New("not support now")
} }
//EncodeToSilk 将音频编码为Silk
func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) { func EncodeToSilk(record []byte, tempName string, useCache bool) ([]byte, error) {
return nil, errors.New("not support now") return nil, errors.New("not support now")
} }

View File

@ -12,6 +12,7 @@ import (
var json = jsoniter.ConfigCompatibleWithStandardLibrary var json = jsoniter.ConfigCompatibleWithStandardLibrary
//DefaultConfigWithComments 为go-cqhttp的默认配置文件
var DefaultConfigWithComments = ` var DefaultConfigWithComments = `
/* /*
go-cqhttp 默认配置文件 go-cqhttp 默认配置文件
@ -134,7 +135,8 @@ var DefaultConfigWithComments = `
} }
` `
type JsonConfig struct { //JSONConfig Config对应的结构体
type JSONConfig struct {
Uin int64 `json:"uin"` Uin int64 `json:"uin"`
Password string `json:"password"` Password string `json:"password"`
EncryptPassword bool `json:"encrypt_password"` EncryptPassword bool `json:"encrypt_password"`
@ -153,39 +155,41 @@ type JsonConfig struct {
} `json:"_rate_limit"` } `json:"_rate_limit"`
IgnoreInvalidCQCode bool `json:"ignore_invalid_cqcode"` IgnoreInvalidCQCode bool `json:"ignore_invalid_cqcode"`
ForceFragmented bool `json:"force_fragmented"` ForceFragmented bool `json:"force_fragmented"`
FixUrl bool `json:"fix_url"` FixURL bool `json:"fix_url"`
ProxyRewrite string `json:"proxy_rewrite"` ProxyRewrite string `json:"proxy_rewrite"`
HeartbeatInterval time.Duration `json:"heartbeat_interval"` HeartbeatInterval time.Duration `json:"heartbeat_interval"`
HttpConfig *GoCQHttpConfig `json:"http_config"` HTTPConfig *GoCQHTTPConfig `json:"http_config"`
WSConfig *GoCQWebsocketConfig `json:"ws_config"` WSConfig *GoCQWebSocketConfig `json:"ws_config"`
ReverseServers []*GoCQReverseWebsocketConfig `json:"ws_reverse_servers"` ReverseServers []*GoCQReverseWebSocketConfig `json:"ws_reverse_servers"`
PostMessageFormat string `json:"post_message_format"` PostMessageFormat string `json:"post_message_format"`
UseSSOAddress bool `json:"use_sso_address"` UseSSOAddress bool `json:"use_sso_address"`
Debug bool `json:"debug"` Debug bool `json:"debug"`
LogLevel string `json:"log_level"` LogLevel string `json:"log_level"`
WebUi *GoCqWebUi `json:"web_ui"` WebUI *GoCQWebUI `json:"web_ui"`
} }
type CQHttpApiConfig struct { //CQHTTPAPIConfig HTTPAPI对应的Config结构体
type CQHTTPAPIConfig struct {
Host string `json:"host"` Host string `json:"host"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
UseHttp bool `json:"use_http"` UseHTTP bool `json:"use_http"`
WSHost string `json:"ws_host"` WSHost string `json:"ws_host"`
WSPort uint16 `json:"ws_port"` WSPort uint16 `json:"ws_port"`
UseWS bool `json:"use_ws"` UseWS bool `json:"use_ws"`
WSReverseUrl string `json:"ws_reverse_url"` WSReverseURL string `json:"ws_reverse_url"`
WSReverseApiUrl string `json:"ws_reverse_api_url"` WSReverseAPIURL string `json:"ws_reverse_api_url"`
WSReverseEventUrl string `json:"ws_reverse_event_url"` WSReverseEventURL string `json:"ws_reverse_event_url"`
WSReverseReconnectInterval uint16 `json:"ws_reverse_reconnect_interval"` WSReverseReconnectInterval uint16 `json:"ws_reverse_reconnect_interval"`
WSReverseReconnectOnCode1000 bool `json:"ws_reverse_reconnect_on_code_1000"` WSReverseReconnectOnCode1000 bool `json:"ws_reverse_reconnect_on_code_1000"`
UseWsReverse bool `json:"use_ws_reverse"` UseWsReverse bool `json:"use_ws_reverse"`
PostUrl string `json:"post_url"` PostURL string `json:"post_url"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
Secret string `json:"secret"` Secret string `json:"secret"`
PostMessageFormat string `json:"post_message_format"` PostMessageFormat string `json:"post_message_format"`
} }
type GoCQHttpConfig struct { //GoCQHTTPConfig 正向HTTP对应config结构体
type GoCQHTTPConfig struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Host string `json:"host"` Host string `json:"host"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
@ -193,29 +197,33 @@ type GoCQHttpConfig struct {
PostUrls map[string]string `json:"post_urls"` PostUrls map[string]string `json:"post_urls"`
} }
type GoCQWebsocketConfig struct { //GoCQWebSocketConfig 正向WebSocket对应Config结构体
type GoCQWebSocketConfig struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Host string `json:"host"` Host string `json:"host"`
Port uint16 `json:"port"` Port uint16 `json:"port"`
} }
type GoCQReverseWebsocketConfig struct { //GoCQReverseWebSocketConfig 反向WebSocket对应Config结构体
type GoCQReverseWebSocketConfig struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
ReverseUrl string `json:"reverse_url"` ReverseURL string `json:"reverse_url"`
ReverseApiUrl string `json:"reverse_api_url"` ReverseAPIURL string `json:"reverse_api_url"`
ReverseEventUrl string `json:"reverse_event_url"` ReverseEventURL string `json:"reverse_event_url"`
ReverseReconnectInterval uint16 `json:"reverse_reconnect_interval"` ReverseReconnectInterval uint16 `json:"reverse_reconnect_interval"`
} }
type GoCqWebUi struct { //GoCQWebUI WebUI对应Config结构体
type GoCQWebUI struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Host string `json:"host"` Host string `json:"host"`
WebUiPort uint64 `json:"web_ui_port"` WebUIPort uint64 `json:"web_ui_port"`
WebInput bool `json:"web_input"` WebInput bool `json:"web_input"`
} }
func DefaultConfig() *JsonConfig { //DefaultConfig 返回一份默认配置对应结构体
return &JsonConfig{ func DefaultConfig() *JSONConfig {
return &JSONConfig{
EnableDB: true, EnableDB: true,
ReLogin: struct { ReLogin: struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
@ -237,42 +245,43 @@ func DefaultConfig() *JsonConfig {
}, },
PostMessageFormat: "string", PostMessageFormat: "string",
ForceFragmented: false, ForceFragmented: false,
HttpConfig: &GoCQHttpConfig{ HTTPConfig: &GoCQHTTPConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 5700, Port: 5700,
PostUrls: map[string]string{}, PostUrls: map[string]string{},
}, },
WSConfig: &GoCQWebsocketConfig{ WSConfig: &GoCQWebSocketConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 6700, Port: 6700,
}, },
ReverseServers: []*GoCQReverseWebsocketConfig{ ReverseServers: []*GoCQReverseWebSocketConfig{
{ {
Enabled: false, Enabled: false,
ReverseUrl: "ws://you_websocket_universal.server", ReverseURL: "ws://you_websocket_universal.server",
ReverseApiUrl: "ws://you_websocket_api.server", ReverseAPIURL: "ws://you_websocket_api.server",
ReverseEventUrl: "ws://you_websocket_event.server", ReverseEventURL: "ws://you_websocket_event.server",
ReverseReconnectInterval: 3000, ReverseReconnectInterval: 3000,
}, },
}, },
WebUi: &GoCqWebUi{ WebUI: &GoCQWebUI{
Enabled: true, Enabled: true,
Host: "127.0.0.1", Host: "127.0.0.1",
WebInput: false, WebInput: false,
WebUiPort: 9999, WebUIPort: 9999,
}, },
} }
} }
func Load(p string) *JsonConfig { //Load 加载配置文件
func Load(p string) *JSONConfig {
if !PathExists(p) { if !PathExists(p) {
log.Warnf("尝试加载配置文件 %v 失败: 文件不存在", p) log.Warnf("尝试加载配置文件 %v 失败: 文件不存在", p)
return nil return nil
} }
var dat map[string]interface{} var dat map[string]interface{}
var c = JsonConfig{} var c = JSONConfig{}
err := hjson.Unmarshal([]byte(ReadAllText(p)), &dat) err := hjson.Unmarshal([]byte(ReadAllText(p)), &dat)
if err == nil { if err == nil {
b, _ := json.Marshal(dat) b, _ := json.Marshal(dat)
@ -287,7 +296,8 @@ func Load(p string) *JsonConfig {
return &c return &c
} }
func (c *JsonConfig) Save(p string) error { //Save 写入配置文件至path
func (c *JSONConfig) Save(path string) error {
data, err := hjson.MarshalWithOptions(c, hjson.EncoderOptions{ data, err := hjson.MarshalWithOptions(c, hjson.EncoderOptions{
Eol: "\n", Eol: "\n",
BracesSameLine: true, BracesSameLine: true,
@ -296,5 +306,5 @@ func (c *JsonConfig) Save(p string) error {
if err != nil { if err != nil {
return err return err
} }
return WriteAllText(p, string(data)) return WriteAllText(path, string(data))
} }

View File

@ -10,8 +10,14 @@ import (
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
//MSG 消息Map
type MSG map[string]interface{} type MSG map[string]interface{}
//Get 尝试从消息Map中取出key为s的值,若不存在则返回MSG{}
//
//若所给key对应的值的类型是global.MSG,则返回此值
//
//若所给key对应值的类型不是global.MSG,则返回MSG{"__str__": Val}
func (m MSG) Get(s string) MSG { func (m MSG) Get(s string) MSG {
if v, ok := m[s]; ok { if v, ok := m[s]; ok {
if msg, ok := v.(MSG); ok { if msg, ok := v.(MSG); ok {
@ -22,6 +28,7 @@ func (m MSG) Get(s string) MSG {
return MSG{} return MSG{}
} }
//String 将消息Map转化为String。若Map存在key "__str__",则返回此key对应的值,否则将输出整张消息Map对应的JSON字符串
func (m MSG) String() string { func (m MSG) String() string {
if str, ok := m["__str__"]; ok { if str, ok := m["__str__"]; ok {
return fmt.Sprint(str) return fmt.Sprint(str)
@ -30,6 +37,7 @@ func (m MSG) String() string {
return str return str
} }
//Filter 定义了一个消息上报过滤接口
type Filter interface { type Filter interface {
Eval(payload MSG) bool Eval(payload MSG) bool
} }
@ -39,6 +47,7 @@ type operationNode struct {
filter Filter filter Filter
} }
//NotOperator 定义了过滤器中Not操作符
type NotOperator struct { type NotOperator struct {
operand Filter operand Filter
} }
@ -52,10 +61,12 @@ func notOperatorConstruct(argument gjson.Result) *NotOperator {
return op return op
} }
//Eval 对payload执行Not过滤
func (op *NotOperator) Eval(payload MSG) bool { func (op *NotOperator) Eval(payload MSG) bool {
return !op.operand.Eval(payload) return !op.operand.Eval(payload)
} }
//AndOperator 定义了过滤器中And操作符
type AndOperator struct { type AndOperator struct {
operands []operationNode operands []operationNode
} }
@ -91,6 +102,7 @@ func andOperatorConstruct(argument gjson.Result) *AndOperator {
return op return op
} }
//Eval 对payload执行And过滤
func (andOperator *AndOperator) Eval(payload MSG) bool { func (andOperator *AndOperator) Eval(payload MSG) bool {
res := true res := true
for _, operand := range andOperator.operands { for _, operand := range andOperator.operands {
@ -104,13 +116,14 @@ func (andOperator *AndOperator) Eval(payload MSG) bool {
res = res && operand.filter.Eval(val) res = res && operand.filter.Eval(val)
} }
if res == false { if !res {
break break
} }
} }
return res return res
} }
//OrOperator 定义了过滤器中Or操作符
type OrOperator struct { type OrOperator struct {
operands []Filter operands []Filter
} }
@ -127,17 +140,19 @@ func orOperatorConstruct(argument gjson.Result) *OrOperator {
return op return op
} }
//Eval 对payload执行Or过滤
func (op *OrOperator) Eval(payload MSG) bool { func (op *OrOperator) Eval(payload MSG) bool {
res := false res := false
for _, operand := range op.operands { for _, operand := range op.operands {
res = res || operand.Eval(payload) res = res || operand.Eval(payload)
if res == true { if res {
break break
} }
} }
return res return res
} }
//EqualOperator 定义了过滤器中Equal操作符
type EqualOperator struct { type EqualOperator struct {
operand string operand string
} }
@ -148,10 +163,12 @@ func equalOperatorConstruct(argument gjson.Result) *EqualOperator {
return op return op
} }
//Eval 对payload执行Equal过滤
func (op *EqualOperator) Eval(payload MSG) bool { func (op *EqualOperator) Eval(payload MSG) bool {
return payload.String() == op.operand return payload.String() == op.operand
} }
//NotEqualOperator 定义了过滤器中NotEqual操作符
type NotEqualOperator struct { type NotEqualOperator struct {
operand string operand string
} }
@ -162,10 +179,12 @@ func notEqualOperatorConstruct(argument gjson.Result) *NotEqualOperator {
return op return op
} }
//Eval 对payload执行NotEqual过滤
func (op *NotEqualOperator) Eval(payload MSG) bool { func (op *NotEqualOperator) Eval(payload MSG) bool {
return !(payload.String() == op.operand) return !(payload.String() == op.operand)
} }
//InOperator 定义了过滤器中In操作符
type InOperator struct { type InOperator struct {
operandString string operandString string
operandArray []string operandArray []string
@ -188,6 +207,7 @@ func inOperatorConstruct(argument gjson.Result) *InOperator {
return op return op
} }
//Eval 对payload执行In过滤
func (op *InOperator) Eval(payload MSG) bool { func (op *InOperator) Eval(payload MSG) bool {
payloadStr := payload.String() payloadStr := payload.String()
if op.operandArray != nil { if op.operandArray != nil {
@ -201,6 +221,7 @@ func (op *InOperator) Eval(payload MSG) bool {
return strings.Contains(op.operandString, payloadStr) return strings.Contains(op.operandString, payloadStr)
} }
//ContainsOperator 定义了过滤器中Contains操作符
type ContainsOperator struct { type ContainsOperator struct {
operand string operand string
} }
@ -214,10 +235,12 @@ func containsOperatorConstruct(argument gjson.Result) *ContainsOperator {
return op return op
} }
//Eval 对payload执行Contains过滤
func (op *ContainsOperator) Eval(payload MSG) bool { func (op *ContainsOperator) Eval(payload MSG) bool {
return strings.Contains(payload.String(), op.operand) return strings.Contains(payload.String(), op.operand)
} }
//RegexOperator 定义了过滤器中Regex操作符
type RegexOperator struct { type RegexOperator struct {
regex *regexp.Regexp regex *regexp.Regexp
} }
@ -231,11 +254,13 @@ func regexOperatorConstruct(argument gjson.Result) *RegexOperator {
return op return op
} }
//Eval 对payload执行RegexO过滤
func (op *RegexOperator) Eval(payload MSG) bool { func (op *RegexOperator) Eval(payload MSG) bool {
matched := op.regex.MatchString(payload.String()) matched := op.regex.MatchString(payload.String())
return matched return matched
} }
//Generate 根据给定操作符名opName及操作符参数argument创建一个过滤器实例
func Generate(opName string, argument gjson.Result) Filter { func Generate(opName string, argument gjson.Result) Filter {
switch opName { switch opName {
case "not": case "not":
@ -259,8 +284,10 @@ func Generate(opName string, argument gjson.Result) Filter {
} }
} }
//EventFilter 初始化一个nil过滤器
var EventFilter Filter = nil var EventFilter Filter = nil
//BootFilter 启动事件过滤器
func BootFilter() { func BootFilter() {
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {

View File

@ -9,7 +9,6 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"github.com/kardianos/osext"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -21,75 +20,95 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/kardianos/osext"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var ( const (
IMAGE_PATH = path.Join("data", "images") //ImagePath go-cqhttp使用的图片缓存目录
IMAGE_PATH_OLD = path.Join("data", "image") ImagePath = "data/images"
VOICE_PATH = path.Join("data", "voices") //ImagePathOld 兼容旧版go-cqhtto使用的图片缓存目录
VOICE_PATH_OLD = path.Join("data", "record") ImagePathOld = "data/image"
VIDEO_PATH = path.Join("data", "videos") //VoicePath go-cqhttp使用的语音缓存目录
CACHE_PATH = path.Join("data", "cache") VoicePath = "data/voices"
//VoicePathOld 兼容旧版go-cqhtto使用的语音缓存目录
HEADER_AMR = []byte("#!AMR") VoicePathOld = "data/record"
HEADER_SILK = []byte("\x02#!SILK_V3") //VideoPath go-cqhttp使用的视频缓存目录
VideoPath = "data/videos"
ErrSyntax = errors.New("syntax error") //CachePath go-cqhttp使用的缓存目录
CachePath = "data/cache"
) )
var (
//ErrSyntax Path语法错误时返回的错误
ErrSyntax = errors.New("syntax error")
//HeaderAmr AMR文件头
HeaderAmr = []byte("#!AMR")
//HeaderSilk Silkv3文件头
HeaderSilk = []byte("\x02#!SILK_V3")
)
//PathExists 判断给定path是否存在
func PathExists(path string) bool { func PathExists(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)
return err == nil || os.IsExist(err) return err == nil || os.IsExist(err)
} }
//ReadAllText 读取给定path对应文件无法读取时返回空值
func ReadAllText(path string) string { func ReadAllText(path string) string {
b, err := ioutil.ReadFile(path) b, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
log.Error(err)
return "" return ""
} }
return string(b) return string(b)
} }
//WriteAllText 将给定text写入给定path
func WriteAllText(path, text string) error { func WriteAllText(path, text string) error {
return ioutil.WriteFile(path, []byte(text), 0644) return ioutil.WriteFile(path, []byte(text), 0644)
} }
//Check 检测err是否为nil
func Check(err error) { func Check(err error) {
if err != nil { if err != nil {
log.Fatalf("遇到错误: %v", err) log.Fatalf("遇到错误: %v", err)
} }
} }
//IsAMRorSILK 判断给定文件是否为Amr或Silk格式
func IsAMRorSILK(b []byte) bool { func IsAMRorSILK(b []byte) bool {
return bytes.HasPrefix(b, HEADER_AMR) || bytes.HasPrefix(b, HEADER_SILK) return bytes.HasPrefix(b, HeaderAmr) || bytes.HasPrefix(b, HeaderSilk)
} }
func FindFile(f, cache, PATH string) (data []byte, err error) { //FindFile 从给定的File寻找文件并返回文件byte数组。File是一个合法的URL。Path为文件寻找位置。
//对于HTTP/HTTPS形式的URLCache为"1"或空时表示启用缓存
func FindFile(file, cache, PATH string) (data []byte, err error) {
data, err = nil, ErrSyntax data, err = nil, ErrSyntax
if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") { if strings.HasPrefix(file, "http") || strings.HasPrefix(file, "https") {
if cache == "" { if cache == "" {
cache = "1" cache = "1"
} }
hash := md5.Sum([]byte(f)) hash := md5.Sum([]byte(file))
cacheFile := path.Join(CACHE_PATH, hex.EncodeToString(hash[:])+".cache") cacheFile := path.Join(CachePath, hex.EncodeToString(hash[:])+".cache")
if PathExists(cacheFile) && cache == "1" { if PathExists(cacheFile) && cache == "1" {
return ioutil.ReadFile(cacheFile) return ioutil.ReadFile(cacheFile)
} }
data, err = GetBytes(f) data, err = GetBytes(file)
_ = ioutil.WriteFile(cacheFile, data, 0644) _ = ioutil.WriteFile(cacheFile, data, 0644)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if strings.HasPrefix(f, "base64") { } else if strings.HasPrefix(file, "base64") {
data, err = base64.StdEncoding.DecodeString(strings.ReplaceAll(f, "base64://", "")) data, err = base64.StdEncoding.DecodeString(strings.ReplaceAll(file, "base64://", ""))
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if strings.HasPrefix(f, "file") { } else if strings.HasPrefix(file, "file") {
var fu *url.URL var fu *url.URL
fu, err = url.Parse(f) fu, err = url.Parse(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,8 +119,8 @@ func FindFile(f, cache, PATH string) (data []byte, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if PathExists(path.Join(PATH, f)) { } else if PathExists(path.Join(PATH, file)) {
data, err = ioutil.ReadFile(path.Join(PATH, f)) data, err = ioutil.ReadFile(path.Join(PATH, file))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,19 +128,20 @@ func FindFile(f, cache, PATH string) (data []byte, err error) {
return return
} }
//DelFile 删除一个给定path并返回删除结果
func DelFile(path string) bool { func DelFile(path string) bool {
err := os.Remove(path) err := os.Remove(path)
if err != nil { if err != nil {
// 删除失败 // 删除失败
log.Error(err) log.Error(err)
return false return false
} else {
// 删除成功
log.Info(path + "删除成功")
return true
} }
// 删除成功
log.Info(path + "删除成功")
return true
} }
//ReadAddrFile 从给定path中读取合法的IP地址与端口,每个IP地址以换行符"\n"作为分隔
func ReadAddrFile(path string) []*net.TCPAddr { func ReadAddrFile(path string) []*net.TCPAddr {
d, err := ioutil.ReadFile(path) d, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
@ -140,10 +160,12 @@ func ReadAddrFile(path string) []*net.TCPAddr {
return ret return ret
} }
//WriteCounter 写入量计算实例
type WriteCounter struct { type WriteCounter struct {
Total uint64 Total uint64
} }
//Write 方法将写入的byte长度追加至写入的总长度Total中
func (wc *WriteCounter) Write(p []byte) (int, error) { func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p) n := len(p)
wc.Total += uint64(n) wc.Total += uint64(n)
@ -151,12 +173,13 @@ func (wc *WriteCounter) Write(p []byte) (int, error) {
return n, nil return n, nil
} }
func (wc WriteCounter) PrintProgress() { //PrintProgress 方法将打印当前的总写入量
func (wc *WriteCounter) PrintProgress() {
fmt.Printf("\r%s", strings.Repeat(" ", 35)) fmt.Printf("\r%s", strings.Repeat(" ", 35))
fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total)) fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total))
} }
// UpdateFromStream copy form getlantern/go-update //UpdateFromStream copy form getlantern/go-update
func UpdateFromStream(updateWith io.Reader) (err error, errRecover error) { func UpdateFromStream(updateWith io.Reader) (err error, errRecover error) {
updatePath, err := osext.Executable() updatePath, err := osext.Executable()
if err != nil { if err != nil {

View File

@ -5,11 +5,8 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"fmt" "fmt"
"github.com/guonaihong/gout"
"github.com/pkg/errors"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -18,12 +15,14 @@ import (
"sync" "sync"
"time" "time"
"github.com/guonaihong/gout"
"github.com/pkg/errors"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
var ( var (
client = &http.Client{ client = &http.Client{
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 == "" {
@ -31,26 +30,24 @@ var (
} }
return url.Parse(Proxy) return url.Parse(Proxy)
}, },
DialContext: (&net.Dialer{ ForceAttemptHTTP2: true,
Timeout: 30 * time.Second, MaxConnsPerHost: 0,
KeepAlive: 30 * time.Second, MaxIdleConns: 0,
}).DialContext, MaxIdleConnsPerHost: 999,
ForceAttemptHTTP2: true,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
MaxConnsPerHost: 0,
MaxIdleConns: 0,
MaxIdleConnsPerHost: 999,
}, },
} }
//Proxy 存储Config.proxy_rewrite,用于设置代理
Proxy string Proxy string
//ErrOverSize 响应主体过大时返回此错误
ErrOverSize = errors.New("oversize") ErrOverSize = errors.New("oversize")
//UserAgent HTTP请求时使用的UA
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" 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"
) )
//GetBytes 对给定URL发送Get请求返回响应主体
func GetBytes(url string) ([]byte, error) { func GetBytes(url string) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
@ -76,6 +73,7 @@ func GetBytes(url string) ([]byte, error) {
return body, nil return body, nil
} }
//DownloadFile 将给定URL对应的文件下载至给定Path
func DownloadFile(url, path string, limit int64, headers map[string]string) 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 {
@ -86,12 +84,12 @@ func DownloadFile(url, path string, limit int64, headers map[string]string) erro
if err != nil { if err != nil {
return err return err
} }
if headers != nil {
for k, v := range headers { for k, v := range headers {
req.Header.Set(k, v) req.Header.Set(k, v)
}
} }
if _, ok := headers["User-Agent"]; ok {
if _, ok := headers["User-Agent"]; !ok {
req.Header["User-Agent"] = []string{UserAgent} req.Header["User-Agent"] = []string{UserAgent}
} }
resp, err := client.Do(req) resp, err := client.Do(req)
@ -109,6 +107,7 @@ func DownloadFile(url, path string, limit int64, headers map[string]string) erro
return nil return nil
} }
//DownloadFileMultiThreading 使用threadCount个线程将给定URL对应的文件下载至给定Path
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, headers) return DownloadFile(url, path, limit, headers)
@ -138,12 +137,12 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
if err != nil { if err != nil {
return err return err
} }
if headers != nil {
for k, v := range headers { for k, v := range headers {
req.Header.Set(k, v) req.Header.Set(k, v)
}
} }
if _, ok := headers["User-Agent"]; ok { if _, ok := headers["User-Agent"]; !ok {
req.Header["User-Agent"] = []string{UserAgent} req.Header["User-Agent"] = []string{UserAgent}
} }
req.Header.Set("range", "bytes=0-") req.Header.Set("range", "bytes=0-")
@ -168,9 +167,9 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
blockSize := func() int64 { blockSize := func() int64 {
if contentLength > 1024*1024 { if contentLength > 1024*1024 {
return (contentLength / int64(threadCount)) - 10 return (contentLength / int64(threadCount)) - 10
} else {
return contentLength
} }
return contentLength
}() }()
if blockSize == contentLength { if blockSize == contentLength {
return copyStream(resp.Body) return copyStream(resp.Body)
@ -189,7 +188,7 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
}) })
return nil return nil
} }
return errors.New("unknown status code.") return errors.New("unknown status code")
} }
// 下载分块 // 下载分块
downloadBlock := func(block *BlockMetaData) error { downloadBlock := func(block *BlockMetaData) error {
@ -202,11 +201,11 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
_, _ = file.Seek(block.BeginOffset, io.SeekStart) _, _ = file.Seek(block.BeginOffset, io.SeekStart)
writer := bufio.NewWriter(file) writer := bufio.NewWriter(file)
defer writer.Flush() defer writer.Flush()
if headers != nil {
for k, v := range headers { for k, v := range headers {
req.Header.Set(k, v) req.Header.Set(k, v)
}
} }
if _, ok := headers["User-Agent"]; ok { if _, ok := headers["User-Agent"]; ok {
req.Header["User-Agent"] = []string{UserAgent} req.Header["User-Agent"] = []string{UserAgent}
} }
@ -266,6 +265,7 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
return lastErr return lastErr
} }
//GetSliderTicket 通过给定的验证链接raw和id,获取验证结果Ticket
func GetSliderTicket(raw, id string) (string, error) { func GetSliderTicket(raw, id string) (string, error) {
var rsp string var rsp string
if err := gout.POST("https://api.shkong.com/gocqhttpapi/task").SetJSON(gout.H{ if err := gout.POST("https://api.shkong.com/gocqhttpapi/task").SetJSON(gout.H{
@ -281,6 +281,7 @@ func GetSliderTicket(raw, id string) (string, error) {
return g.Get("ticket").Str, nil return g.Get("ticket").Str, nil
} }
//QQMusicSongInfo 通过给定id在QQ音乐上查找曲目信息
func QQMusicSongInfo(id string) (gjson.Result, error) { func QQMusicSongInfo(id string) (gjson.Result, error) {
d, err := GetBytes(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:` + id + `},%22module%22:%22music.pf_song_detail_svr%22}}`) d, err := GetBytes(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:` + id + `},%22module%22:%22music.pf_song_detail_svr%22}}`)
if err != nil { if err != nil {
@ -289,6 +290,7 @@ func QQMusicSongInfo(id string) (gjson.Result, error) {
return gjson.ParseBytes(d).Get("songinfo.data"), nil return gjson.ParseBytes(d).Get("songinfo.data"), nil
} }
//NeteaseMusicSongInfo 通过给定id在wdd音乐上查找曲目信息
func NeteaseMusicSongInfo(id string) (gjson.Result, error) { func NeteaseMusicSongInfo(id string) (gjson.Result, error) {
d, err := GetBytes(fmt.Sprintf("http://music.163.com/api/song/detail/?id=%s&ids=%%5B%s%%5D", id, id)) d, err := GetBytes(fmt.Sprintf("http://music.163.com/api/song/detail/?id=%s&ids=%%5B%s%%5D", id, id))
if err != nil { if err != nil {

View File

@ -21,6 +21,15 @@ var falseSet = map[string]struct{}{
"0": {}, "0": {},
} }
//EnsureBool 判断给定的p是否可表示为合法Bool类型,否则返回defaultVal
//
//支持的合法类型有
//
//type bool
//
//type gjson.True or gjson.False
//
//type string "true","yes","1" or "false","no","0" (case insensitive)
func EnsureBool(p interface{}, defaultVal bool) bool { func EnsureBool(p interface{}, defaultVal bool) bool {
var str string var str string
if b, ok := p.(bool); ok { if b, ok := p.(bool); ok {
@ -54,9 +63,13 @@ func EnsureBool(p interface{}, defaultVal bool) bool {
} }
// VersionNameCompare 检查版本名是否需要更新, 仅适用于 go-cqhttp 的版本命名规则 // VersionNameCompare 检查版本名是否需要更新, 仅适用于 go-cqhttp 的版本命名规则
//
// 例: v0.9.29-fix2 == v0.9.29-fix2 -> false // 例: v0.9.29-fix2 == v0.9.29-fix2 -> false
//
// v0.9.29-fix1 < v0.9.29-fix2 -> true // v0.9.29-fix1 < v0.9.29-fix2 -> true
//
// v0.9.29-fix2 > v0.9.29-fix1 -> false // v0.9.29-fix2 > v0.9.29-fix1 -> false
//
// v0.9.29-fix2 < v0.9.30 -> true // v0.9.29-fix2 < v0.9.30 -> true
func VersionNameCompare(current, remote string) bool { func VersionNameCompare(current, remote string) bool {
sp := regexp.MustCompile(`[0-9]\d*`) sp := regexp.MustCompile(`[0-9]\d*`)
@ -72,8 +85,9 @@ func VersionNameCompare(current, remote string) bool {
return len(cur) < len(re) return len(cur) < len(re)
} }
func SplitUrl(s string) []string { //SplitURL 将给定URL字符串分割为两部分用于URL预处理防止风控
reg := regexp.MustCompile(`[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?`) func SplitURL(s string) []string {
reg := regexp.MustCompile(`(?i)[a-z\d][-a-z\d]{0,62}(\.[a-z\d][-a-z\d]{0,62})+\.?`)
idx := reg.FindAllStringIndex(s, -1) idx := reg.FindAllStringIndex(s, -1)
if len(idx) == 0 { if len(idx) == 0 {
return []string{s} return []string{s}

View File

@ -9,13 +9,15 @@ import (
var limiter *rate.Limiter var limiter *rate.Limiter
var limitEnable = false var limitEnable = false
//RateLimit 执行API调用速率限制
func RateLimit(ctx context.Context) { func RateLimit(ctx context.Context) {
if limitEnable { if limitEnable {
_ = limiter.Wait(ctx) _ = limiter.Wait(ctx)
} }
} }
func InitLimiter(r float64, b int) { //InitLimiter 初始化速率限制器
func InitLimiter(frequency float64, bucketSize int) {
limitEnable = true limitEnable = true
limiter = rate.NewLimiter(rate.Limit(r), b) limiter = rate.NewLimiter(rate.Limit(frequency), bucketSize)
} }

6
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-20210115215446-8ee19f60514b github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a
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
@ -13,7 +13,7 @@ require (
github.com/json-iterator/go v1.1.10 github.com/json-iterator/go v1.1.10
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/lestrrat-go/strftime v1.0.3 // indirect github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.7.0
@ -21,6 +21,6 @@ require (
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/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/term v0.0.0-20201210144234-2321bbc49cbf
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
) )

23
go.sum
View File

@ -1,9 +1,7 @@
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-20210110160231-b83dd4cf38a5 h1:ee6LafOcoVM0nox2UxiIJgomgRP4pDJe5aeT/rq2o/U= github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a h1:ov5QCDpZpsr+geKLVON7E63UqrpNF0oTiKuZwbKwEmo=
github.com/Mrs4s/MiraiGo v0.0.0-20210110160231-b83dd4cf38a5/go.mod h1:HW2e375lCQiRwtuA/LV6ZVTsi7co1TRfBn+L5Ow77Bo= github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a/go.mod h1:9V7DdSwpEfCKQNvLZhRnFJFkelTU0tPLfwR5l6UFF1Y=
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=
@ -68,8 +66,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.0.3 h1:qqOPU7y+TM8Y803I8fG9c/DyKG3xH/xkng6keC1015Q= github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
github.com/lestrrat-go/strftime v1.0.3/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@ -100,10 +98,8 @@ github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFd
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk= github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk=
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA= github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA=
github.com/tidwall/gjson v1.6.3/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE= github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
@ -115,8 +111,6 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
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=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -126,7 +120,6 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -138,10 +131,11 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
@ -152,7 +146,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

85
main.go
View File

@ -16,12 +16,13 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"github.com/Mrs4s/go-cqhttp/server" "github.com/Mrs4s/go-cqhttp/server"
"github.com/guonaihong/gout" "github.com/guonaihong/gout"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/term"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/client"
@ -34,31 +35,31 @@ import (
) )
var json = jsoniter.ConfigCompatibleWithStandardLibrary var json = jsoniter.ConfigCompatibleWithStandardLibrary
var conf *global.JsonConfig var conf *global.JSONConfig
func init() { func init() {
if global.PathExists("cqhttp.json") { if global.PathExists("cqhttp.json") {
log.Info("发现 cqhttp.json 将在五秒后尝试导入配置,按 Ctrl+C 取消.") log.Info("发现 cqhttp.json 将在五秒后尝试导入配置,按 Ctrl+C 取消.")
log.Warn("警告: 该操作会删除 cqhttp.json 并覆盖 config.hjson 文件.") log.Warn("警告: 该操作会删除 cqhttp.json 并覆盖 config.hjson 文件.")
time.Sleep(time.Second * 5) time.Sleep(time.Second * 5)
conf := global.CQHttpApiConfig{} conf := global.CQHTTPAPIConfig{}
if err := json.Unmarshal([]byte(global.ReadAllText("cqhttp.json")), &conf); err != nil { if err := json.Unmarshal([]byte(global.ReadAllText("cqhttp.json")), &conf); err != nil {
log.Fatalf("读取文件 cqhttp.json 失败: %v", err) log.Fatalf("读取文件 cqhttp.json 失败: %v", err)
} }
goConf := global.DefaultConfig() goConf := global.DefaultConfig()
goConf.AccessToken = conf.AccessToken goConf.AccessToken = conf.AccessToken
goConf.HttpConfig.Host = conf.Host goConf.HTTPConfig.Host = conf.Host
goConf.HttpConfig.Port = conf.Port goConf.HTTPConfig.Port = conf.Port
goConf.WSConfig.Host = conf.WSHost goConf.WSConfig.Host = conf.WSHost
goConf.WSConfig.Port = conf.WSPort goConf.WSConfig.Port = conf.WSPort
if conf.PostUrl != "" { if conf.PostURL != "" {
goConf.HttpConfig.PostUrls[conf.PostUrl] = conf.Secret goConf.HTTPConfig.PostUrls[conf.PostURL] = conf.Secret
} }
if conf.UseWsReverse { if conf.UseWsReverse {
goConf.ReverseServers[0].Enabled = true goConf.ReverseServers[0].Enabled = true
goConf.ReverseServers[0].ReverseUrl = conf.WSReverseUrl goConf.ReverseServers[0].ReverseURL = conf.WSReverseURL
goConf.ReverseServers[0].ReverseApiUrl = conf.WSReverseApiUrl goConf.ReverseServers[0].ReverseAPIURL = conf.WSReverseAPIURL
goConf.ReverseServers[0].ReverseEventUrl = conf.WSReverseEventUrl goConf.ReverseServers[0].ReverseEventURL = conf.WSReverseEventURL
goConf.ReverseServers[0].ReverseReconnectInterval = conf.WSReverseReconnectInterval goConf.ReverseServers[0].ReverseReconnectInterval = conf.WSReverseReconnectInterval
} }
if err := goConf.Save("config.hjson"); err != nil { if err := goConf.Save("config.hjson"); err != nil {
@ -89,29 +90,30 @@ func init() {
log.AddHook(global.NewLocalHook(w, logFormatter, global.GetLogLevel(conf.LogLevel)...)) log.AddHook(global.NewLocalHook(w, logFormatter, global.GetLogLevel(conf.LogLevel)...))
if !global.PathExists(global.IMAGE_PATH) { if !global.PathExists(global.ImagePath) {
if err := os.MkdirAll(global.IMAGE_PATH, 0755); err != nil { if err := os.MkdirAll(global.ImagePath, 0755); err != nil {
log.Fatalf("创建图片缓存文件夹失败: %v", err) log.Fatalf("创建图片缓存文件夹失败: %v", err)
} }
} }
if !global.PathExists(global.VOICE_PATH) { if !global.PathExists(global.VoicePath) {
if err := os.MkdirAll(global.VOICE_PATH, 0755); err != nil { if err := os.MkdirAll(global.VoicePath, 0755); err != nil {
log.Fatalf("创建语音缓存文件夹失败: %v", err) log.Fatalf("创建语音缓存文件夹失败: %v", err)
} }
} }
if !global.PathExists(global.VIDEO_PATH) { if !global.PathExists(global.VideoPath) {
if err := os.MkdirAll(global.VIDEO_PATH, 0755); err != nil { if err := os.MkdirAll(global.VideoPath, 0755); err != nil {
log.Fatalf("创建视频缓存文件夹失败: %v", err) log.Fatalf("创建视频缓存文件夹失败: %v", err)
} }
} }
if !global.PathExists(global.CACHE_PATH) { if !global.PathExists(global.CachePath) {
if err := os.MkdirAll(global.CACHE_PATH, 0755); err != nil { if err := os.MkdirAll(global.CachePath, 0755); err != nil {
log.Fatalf("创建发送图片缓存文件夹失败: %v", err) log.Fatalf("创建发送图片缓存文件夹失败: %v", err)
} }
} }
} }
func main() { func main() {
var byteKey []byte var byteKey []byte
var isFastStart = false var isFastStart = false
arg := os.Args arg := os.Args
@ -147,7 +149,7 @@ func main() {
log.Warnf("已开启Debug模式.") log.Warnf("已开启Debug模式.")
log.Debugf("开发交流群: 192548878") log.Debugf("开发交流群: 192548878")
server.Debug = true server.Debug = true
if conf.WebUi == nil || !conf.WebUi.Enabled { if conf.WebUI == nil || !conf.WebUI.Enabled {
log.Warnf("警告: 在Debug模式下未启用WebUi服务, 将无法进行性能分析.") log.Warnf("警告: 在Debug模式下未启用WebUi服务, 将无法进行性能分析.")
} }
} }
@ -165,7 +167,7 @@ func main() {
} }
if conf.EncryptPassword && conf.PasswordEncrypted == "" { if conf.EncryptPassword && conf.PasswordEncrypted == "" {
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)") log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
byteKey, _ := terminal.ReadPassword(int(os.Stdin.Fd())) byteKey, _ := term.ReadPassword(int(os.Stdin.Fd()))
key := md5.Sum(byteKey) key := md5.Sum(byteKey)
if encrypted := EncryptPwd(conf.Password, key[:]); encrypted != "" { if encrypted := EncryptPwd(conf.Password, key[:]); encrypted != "" {
conf.Password = "" conf.Password = ""
@ -189,7 +191,7 @@ func main() {
os.Exit(0) os.Exit(0)
} }
}() }()
byteKey, _ = terminal.ReadPassword(int(os.Stdin.Fd())) byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
cancel <- struct{}{} cancel <- struct{}{}
} else { } else {
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.") log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
@ -242,26 +244,26 @@ func main() {
log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ") log.Infof("收到服务器地址更新通知, 将在下一次重连时应用. ")
return true return true
}) })
if conf.WebUi == nil { if conf.WebUI == nil {
conf.WebUi = &global.GoCqWebUi{ conf.WebUI = &global.GoCQWebUI{
Enabled: true, Enabled: true,
WebInput: false, WebInput: false,
Host: "0.0.0.0", Host: "0.0.0.0",
WebUiPort: 9999, WebUIPort: 9999,
} }
} }
if conf.WebUi.WebUiPort <= 0 { if conf.WebUI.WebUIPort <= 0 {
conf.WebUi.WebUiPort = 9999 conf.WebUI.WebUIPort = 9999
} }
if conf.WebUi.Host == "" { if conf.WebUI.Host == "" {
conf.WebUi.Host = "127.0.0.1" conf.WebUI.Host = "127.0.0.1"
} }
global.Proxy = conf.ProxyRewrite global.Proxy = conf.ProxyRewrite
b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUi.Host, conf.WebUi.WebUiPort), cli) b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUI.Host, conf.WebUI.WebUIPort), cli)
c := server.Console c := server.Console
r := server.Restart r := server.Restart
go checkUpdate() go checkUpdate()
signal.Notify(c, os.Interrupt, os.Kill) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
select { select {
case <-c: case <-c:
b.Release() b.Release()
@ -272,6 +274,7 @@ func main() {
} }
} }
//EncryptPwd 通过给定key加密给定pwd
func EncryptPwd(pwd string, key []byte) string { func EncryptPwd(pwd string, key []byte) string {
tea := binary.NewTeaCipher(key) tea := binary.NewTeaCipher(key)
if tea == nil { if tea == nil {
@ -280,6 +283,7 @@ func EncryptPwd(pwd string, key []byte) string {
return base64.StdEncoding.EncodeToString(tea.Encrypt([]byte(pwd))) return base64.StdEncoding.EncodeToString(tea.Encrypt([]byte(pwd)))
} }
//DecryptPwd 通过给定key解密给定ePwd
func DecryptPwd(ePwd string, key []byte) string { func DecryptPwd(ePwd string, key []byte) string {
defer func() { defer func() {
if pan := recover(); pan != nil { if pan := recover(); pan != nil {
@ -321,7 +325,7 @@ func checkUpdate() {
log.Infof("检查更新完成. 当前已运行最新版本.") log.Infof("检查更新完成. 当前已运行最新版本.")
} }
func selfUpdate(imageUrl string) { func selfUpdate(imageURL string) {
console := bufio.NewReader(os.Stdin) console := bufio.NewReader(os.Stdin)
readLine := func() (str string) { readLine := func() (str string) {
str, _ = console.ReadString('\n') str, _ = console.ReadString('\n')
@ -349,8 +353,8 @@ func selfUpdate(imageUrl string) {
url := fmt.Sprintf( url := fmt.Sprintf(
"%v/Mrs4s/go-cqhttp/releases/download/%v/go-cqhttp-%v-%v-%v", "%v/Mrs4s/go-cqhttp/releases/download/%v/go-cqhttp-%v-%v-%v",
func() string { func() string {
if imageUrl != "" { if imageURL != "" {
return imageUrl return imageURL
} }
return "https://github.com" return "https://github.com"
}(), }(),
@ -392,7 +396,7 @@ func selfUpdate(imageUrl string) {
} }
func restart(Args []string) { func restart(Args []string) {
cmd := &exec.Cmd{} var cmd *exec.Cmd
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
file, err := exec.LookPath(Args[0]) file, err := exec.LookPath(Args[0])
if err != nil { if err != nil {
@ -419,10 +423,11 @@ func restart(Args []string) {
Stdout: os.Stdout, Stdout: os.Stdout,
} }
} }
cmd.Start() _ = cmd.Start()
} }
func getConfig() *global.JsonConfig { func getConfig() *global.JSONConfig {
var conf *global.JSONConfig
if global.PathExists("config.json") { if global.PathExists("config.json") {
conf = global.Load("config.json") conf = global.Load("config.json")
_ = conf.Save("config.hjson") _ = conf.Save("config.hjson")
@ -432,16 +437,16 @@ func getConfig() *global.JsonConfig {
uin, _ := strconv.ParseInt(os.Getenv("UIN"), 10, 64) uin, _ := strconv.ParseInt(os.Getenv("UIN"), 10, 64)
pwd := os.Getenv("PASS") pwd := os.Getenv("PASS")
post := os.Getenv("HTTP_POST") post := os.Getenv("HTTP_POST")
conf = &global.JsonConfig{ conf = &global.JSONConfig{
Uin: uin, Uin: uin,
Password: pwd, Password: pwd,
HttpConfig: &global.GoCQHttpConfig{ HTTPConfig: &global.GoCQHTTPConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 5700, Port: 5700,
PostUrls: map[string]string{}, PostUrls: map[string]string{},
}, },
WSConfig: &global.GoCQWebsocketConfig{ WSConfig: &global.GoCQWebSocketConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 6700, Port: 6700,
@ -450,7 +455,7 @@ func getConfig() *global.JsonConfig {
Debug: os.Getenv("DEBUG") == "true", Debug: os.Getenv("DEBUG") == "true",
} }
if post != "" { if post != "" {
conf.HttpConfig.PostUrls[post] = os.Getenv("HTTP_SECRET") conf.HTTPConfig.PostUrls[post] = os.Getenv("HTTP_SECRET")
} }
} else { } else {
conf = global.Load("config.hjson") conf = global.Load("config.hjson")

View File

@ -5,8 +5,6 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/gin-contrib/pprof"
"image" "image"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -14,8 +12,12 @@ import (
"os/signal" "os/signal"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/gin-contrib/pprof"
"github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/global"
@ -34,13 +36,13 @@ var Console = make(chan os.Signal, 1)
var Restart = make(chan struct{}, 1) var Restart = make(chan struct{}, 1)
var JsonConfig *global.JsonConfig var JSONConfig *global.JSONConfig
type webServer struct { type webServer struct {
engine *gin.Engine engine *gin.Engine
bot *coolq.CQBot bot *coolq.CQBot
Cli *client.QQClient Cli *client.QQClient
Conf *global.JsonConfig //old config Conf *global.JSONConfig //old config
Console *bufio.Reader Console *bufio.Reader
} }
@ -68,7 +70,7 @@ func Failed(code int, msg string) coolq.MSG {
func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot { func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot {
s.Cli = cli s.Cli = cli
s.Conf = GetConf() s.Conf = GetConf()
JsonConfig = s.Conf JSONConfig = s.Conf
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
s.engine = gin.New() s.engine = gin.New()
@ -79,7 +81,7 @@ func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot {
go func() { go func() {
//开启端口监听 //开启端口监听
if s.Conf.WebUi != nil && s.Conf.WebUi.Enabled { if s.Conf.WebUI != nil && s.Conf.WebUI.Enabled {
if Debug { if Debug {
pprof.Register(s.engine) pprof.Register(s.engine)
log.Debugf("pprof 性能分析服务已启动在 http://%v/debug/pprof, 如果有任何性能问题请下载报告并提交给开发者", addr) log.Debugf("pprof 性能分析服务已启动在 http://%v/debug/pprof, 如果有任何性能问题请下载报告并提交给开发者", addr)
@ -91,14 +93,14 @@ func (s *webServer) Run(addr string, cli *client.QQClient) *coolq.CQBot {
log.Error(err) log.Error(err)
log.Infof("请检查端口是否被占用.") log.Infof("请检查端口是否被占用.")
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c <-c
os.Exit(1) os.Exit(1)
} }
} else { } else {
//关闭端口监听 //关闭端口监听
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c <-c
os.Exit(1) os.Exit(1)
} }
@ -155,13 +157,17 @@ func (s *webServer) Dologin() {
os.Exit(0) os.Exit(0)
} }
rsp, err = cli.SubmitTicket(ticket) rsp, err = cli.SubmitTicket(ticket)
if err != nil {
log.Warnf("错误: " + err.Error())
os.Exit(0)
}
continue continue
case client.NeedCaptcha: case client.NeedCaptcha:
_ = ioutil.WriteFile("captcha.jpg", rsp.CaptchaImage, 0644) _ = ioutil.WriteFile("captcha.jpg", rsp.CaptchaImage, 0644)
img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage)) img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage))
fmt.Println(asciiart.New("image", img).Art) fmt.Println(asciiart.New("image", img).Art)
if conf.WebUi != nil && conf.WebUi.WebInput { if conf.WebUI != nil && conf.WebUI.WebInput {
log.Warnf("请输入验证码 (captcha.jpg) (http://%s:%d/admin/do_web_write 输入)", conf.WebUi.Host, conf.WebUi.WebUiPort) log.Warnf("请输入验证码 (captcha.jpg) (http://%s:%d/admin/do_web_write 输入)", conf.WebUI.Host, conf.WebUI.WebUIPort)
text = <-WebInput text = <-WebInput
} else { } else {
log.Warn("请输入验证码 (captcha.jpg) (Enter 提交)") log.Warn("请输入验证码 (captcha.jpg) (Enter 提交)")
@ -206,8 +212,8 @@ func (s *webServer) Dologin() {
return return
case client.UnsafeDeviceError: case client.UnsafeDeviceError:
log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证并重启Bot.", rsp.VerifyUrl) log.Warnf("账号已开启设备锁,请前往 -> %v <- 验证并重启Bot.", rsp.VerifyUrl)
if conf.WebUi != nil && conf.WebUi.WebInput { if conf.WebUI != nil && conf.WebUI.WebInput {
log.Infof(" (http://%s:%d/admin/do_web_write 确认后继续)....", conf.WebUi.Host, conf.WebUi.WebUiPort) log.Infof(" (http://%s:%d/admin/do_web_write 确认后继续)....", conf.WebUI.Host, conf.WebUI.WebUIPort)
text = <-WebInput text = <-WebInput
} else { } else {
log.Infof("按 Enter 继续....") log.Infof("按 Enter 继续....")
@ -260,7 +266,7 @@ func (s *webServer) Dologin() {
global.BootFilter() global.BootFilter()
global.InitCodec() global.InitCodec()
coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode
coolq.SplitUrl = conf.FixUrl coolq.SplitUrl = conf.FixURL
coolq.ForceFragmented = conf.ForceFragmented coolq.ForceFragmented = conf.ForceFragmented
log.Info("资源初始化完成, 开始处理信息.") log.Info("资源初始化完成, 开始处理信息.")
log.Info("アトリは、高性能ですから!") log.Info("アトリは、高性能ですから!")
@ -323,9 +329,9 @@ func (s *webServer) admin(c *gin.Context) {
} }
// 获取当前配置文件信息 // 获取当前配置文件信息
func GetConf() *global.JsonConfig { func GetConf() *global.JSONConfig {
if JsonConfig != nil { if JSONConfig != nil {
return JsonConfig return JSONConfig
} }
conf := global.Load("config.hjson") conf := global.Load("config.hjson")
return conf return conf
@ -385,7 +391,7 @@ func AuthMiddleWare() gin.HandlerFunc {
} }
func (s *webServer) DoReLogin() { // TODO: 协议层的 ReLogin func (s *webServer) DoReLogin() { // TODO: 协议层的 ReLogin
JsonConfig = nil JSONConfig = nil
conf := GetConf() conf := GetConf()
OldConf := s.Conf OldConf := s.Conf
cli := client.NewClient(conf.Uin, conf.Password) cli := client.NewClient(conf.Uin, conf.Password)
@ -424,7 +430,7 @@ func (s *webServer) DoReLogin() { // TODO: 协议层的 ReLogin
s.Cli = cli s.Cli = cli
s.Dologin() s.Dologin()
//关闭之前的 server //关闭之前的 server
if OldConf.HttpConfig != nil && OldConf.HttpConfig.Enabled { if OldConf.HTTPConfig != nil && OldConf.HTTPConfig.Enabled {
HttpServer.ShutDown() HttpServer.ShutDown()
} }
//if OldConf.WSConfig != nil && OldConf.WSConfig.Enabled { //if OldConf.WSConfig != nil && OldConf.WSConfig.Enabled {
@ -437,31 +443,31 @@ func (s *webServer) DoReLogin() { // TODO: 协议层的 ReLogin
func (s *webServer) UpServer() { func (s *webServer) UpServer() {
conf := GetConf() conf := GetConf()
if conf.HttpConfig != nil && conf.HttpConfig.Enabled { if conf.HTTPConfig != nil && conf.HTTPConfig.Enabled {
go HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, s.bot) go HttpServer.Run(fmt.Sprintf("%s:%d", conf.HTTPConfig.Host, conf.HTTPConfig.Port), conf.AccessToken, s.bot)
for k, v := range conf.HttpConfig.PostUrls { for k, v := range conf.HTTPConfig.PostUrls {
NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, s.bot) NewHttpClient().Run(k, v, conf.HTTPConfig.Timeout, s.bot)
} }
} }
if conf.WSConfig != nil && conf.WSConfig.Enabled { if conf.WSConfig != nil && conf.WSConfig.Enabled {
go WebsocketServer.Run(fmt.Sprintf("%s:%d", conf.WSConfig.Host, conf.WSConfig.Port), conf.AccessToken, s.bot) go WebSocketServer.Run(fmt.Sprintf("%s:%d", conf.WSConfig.Host, conf.WSConfig.Port), conf.AccessToken, s.bot)
} }
for _, rc := range conf.ReverseServers { for _, rc := range conf.ReverseServers {
go NewWebsocketClient(rc, conf.AccessToken, s.bot).Run() go NewWebSocketClient(rc, conf.AccessToken, s.bot).Run()
} }
} }
// 暂不支持ws服务的重启 // 暂不支持ws服务的重启
func (s *webServer) ReloadServer() { func (s *webServer) ReloadServer() {
conf := GetConf() conf := GetConf()
if conf.HttpConfig != nil && conf.HttpConfig.Enabled { if conf.HTTPConfig != nil && conf.HTTPConfig.Enabled {
go HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, s.bot) go HttpServer.Run(fmt.Sprintf("%s:%d", conf.HTTPConfig.Host, conf.HTTPConfig.Port), conf.AccessToken, s.bot)
for k, v := range conf.HttpConfig.PostUrls { for k, v := range conf.HTTPConfig.PostUrls {
NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, s.bot) NewHttpClient().Run(k, v, conf.HTTPConfig.Timeout, s.bot)
} }
} }
for _, rc := range conf.ReverseServers { for _, rc := range conf.ReverseServers {
go NewWebsocketClient(rc, conf.AccessToken, s.bot).Run() go NewWebSocketClient(rc, conf.AccessToken, s.bot).Run()
} }
} }
@ -472,7 +478,6 @@ func AdminDoRestart(s *webServer, c *gin.Context) {
s.Cli = nil s.Cli = nil
s.DoReLogin() s.DoReLogin()
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
return
} }
// 进程重启 // 进程重启
@ -485,7 +490,6 @@ func AdminProcessRestart(s *webServer, c *gin.Context) {
func AdminDoRestartDocker(s *webServer, c *gin.Context) { func AdminDoRestartDocker(s *webServer, c *gin.Context) {
Console <- os.Kill Console <- os.Kill
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
return
} }
// web输入 html 页面 // web输入 html 页面
@ -527,7 +531,7 @@ func AdminDoConfigBase(s *webServer, c *gin.Context) {
log.Fatalf("保存 config.hjson 时出现错误: %v", err) log.Fatalf("保存 config.hjson 时出现错误: %v", err)
c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err))) c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err)))
} else { } else {
JsonConfig = nil JSONConfig = nil
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
} }
} }
@ -536,23 +540,23 @@ func AdminDoConfigBase(s *webServer, c *gin.Context) {
func AdminDoConfigHttp(s *webServer, c *gin.Context) { func AdminDoConfigHttp(s *webServer, c *gin.Context) {
conf := GetConf() conf := GetConf()
p, _ := strconv.ParseUint(c.PostForm("port"), 10, 16) p, _ := strconv.ParseUint(c.PostForm("port"), 10, 16)
conf.HttpConfig.Port = uint16(p) conf.HTTPConfig.Port = uint16(p)
conf.HttpConfig.Host = c.PostForm("host") conf.HTTPConfig.Host = c.PostForm("host")
if c.PostForm("enable") == "true" { if c.PostForm("enable") == "true" {
conf.HttpConfig.Enabled = true conf.HTTPConfig.Enabled = true
} else { } else {
conf.HttpConfig.Enabled = false conf.HTTPConfig.Enabled = false
} }
t, _ := strconv.ParseInt(c.PostForm("timeout"), 10, 32) t, _ := strconv.ParseInt(c.PostForm("timeout"), 10, 32)
conf.HttpConfig.Timeout = int32(t) conf.HTTPConfig.Timeout = int32(t)
if c.PostForm("post_url") != "" { if c.PostForm("post_url") != "" {
conf.HttpConfig.PostUrls[c.PostForm("post_url")] = c.PostForm("post_secret") conf.HTTPConfig.PostUrls[c.PostForm("post_url")] = c.PostForm("post_secret")
} }
if err := conf.Save("config.hjson"); err != nil { if err := conf.Save("config.hjson"); err != nil {
log.Fatalf("保存 config.hjson 时出现错误: %v", err) log.Fatalf("保存 config.hjson 时出现错误: %v", err)
c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err))) c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err)))
} else { } else {
JsonConfig = nil JSONConfig = nil
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
} }
} }
@ -572,7 +576,7 @@ func AdminDoConfigWs(s *webServer, c *gin.Context) {
log.Fatalf("保存 config.hjson 时出现错误: %v", err) log.Fatalf("保存 config.hjson 时出现错误: %v", err)
c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err))) c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err)))
} else { } else {
JsonConfig = nil JSONConfig = nil
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
} }
} }
@ -580,9 +584,9 @@ func AdminDoConfigWs(s *webServer, c *gin.Context) {
// 反向ws配置修改 // 反向ws配置修改
func AdminDoConfigReverse(s *webServer, c *gin.Context) { func AdminDoConfigReverse(s *webServer, c *gin.Context) {
conf := GetConf() conf := GetConf()
conf.ReverseServers[0].ReverseApiUrl = c.PostForm("reverse_api_url") conf.ReverseServers[0].ReverseAPIURL = c.PostForm("reverse_api_url")
conf.ReverseServers[0].ReverseUrl = c.PostForm("reverse_url") conf.ReverseServers[0].ReverseURL = c.PostForm("reverse_url")
conf.ReverseServers[0].ReverseEventUrl = c.PostForm("reverse_event_url") conf.ReverseServers[0].ReverseEventURL = c.PostForm("reverse_event_url")
t, _ := strconv.ParseUint(c.PostForm("reverse_reconnect_interval"), 10, 16) t, _ := strconv.ParseUint(c.PostForm("reverse_reconnect_interval"), 10, 16)
conf.ReverseServers[0].ReverseReconnectInterval = uint16(t) conf.ReverseServers[0].ReverseReconnectInterval = uint16(t)
if c.PostForm("enable") == "true" { if c.PostForm("enable") == "true" {
@ -594,7 +598,7 @@ func AdminDoConfigReverse(s *webServer, c *gin.Context) {
log.Fatalf("保存 config.hjson 时出现错误: %v", err) log.Fatalf("保存 config.hjson 时出现错误: %v", err)
c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err))) c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err)))
} else { } else {
JsonConfig = nil JSONConfig = nil
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
} }
} }
@ -613,7 +617,7 @@ func AdminDoConfigJson(s *webServer, c *gin.Context) {
log.Fatalf("保存 config.hjson 时出现错误: %v", err) log.Fatalf("保存 config.hjson 时出现错误: %v", err)
c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err))) c.JSON(200, Failed(502, "保存 config.hjson 时出现错误:"+fmt.Sprintf("%v", err)))
} else { } else {
JsonConfig = nil JSONConfig = nil
c.JSON(200, coolq.OK(coolq.MSG{})) c.JSON(200, coolq.OK(coolq.MSG{}))
} }
} }

View File

@ -123,7 +123,11 @@ func (c *httpClient) onBotPushEvent(m coolq.MSG) {
} }
if c.secret != "" { if c.secret != "" {
mac := hmac.New(sha1.New, []byte(c.secret)) mac := hmac.New(sha1.New, []byte(c.secret))
mac.Write([]byte(m.ToJson())) _, err := mac.Write([]byte(m.ToJson()))
if err != nil {
log.Error(err)
return nil
}
h["X-Signature"] = "sha1=" + hex.EncodeToString(mac.Sum(nil)) h["X-Signature"] = "sha1=" + hex.EncodeToString(mac.Sum(nil))
} }
return h return h
@ -417,6 +421,16 @@ func SetGroupAnonymousBan(s *httpServer, c *gin.Context) {
c.JSON(200, s.bot.CQSetGroupAnonymousBan(gid, flag, int32(d))) c.JSON(200, s.bot.CQSetGroupAnonymousBan(gid, flag, int32(d)))
} }
func GetGroupMessageHistory(s *httpServer, c *gin.Context) {
gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64)
seq, _ := strconv.ParseInt(getParam(c, "message_seq"), 10, 64)
c.JSON(200, s.bot.CQGetGroupMessageHistory(gid, seq))
}
func GetOnlineClients(s *httpServer, c *gin.Context) {
c.JSON(200, s.bot.CQGetOnlineClients(getParamOrDefault(c, "no_cache", "false") == "true"))
}
func HandleQuickOperation(s *httpServer, c *gin.Context) { func HandleQuickOperation(s *httpServer, c *gin.Context) {
if c.Request.Method != "POST" { if c.Request.Method != "POST" {
c.AbortWithStatus(404) c.AbortWithStatus(404)
@ -562,11 +576,13 @@ var httpApi = map[string]func(s *httpServer, c *gin.Context){
"reload_event_filter": ReloadEventFilter, "reload_event_filter": ReloadEventFilter,
"set_group_portrait": SetGroupPortrait, "set_group_portrait": SetGroupPortrait,
"set_group_anonymous_ban": SetGroupAnonymousBan, "set_group_anonymous_ban": SetGroupAnonymousBan,
"get_group_msg_history": GetGroupMessageHistory,
"download_file": DownloadFile, "download_file": DownloadFile,
".handle_quick_operation": HandleQuickOperation, ".handle_quick_operation": HandleQuickOperation,
".ocr_image": OcrImage, ".ocr_image": OcrImage,
"ocr_image": OcrImage, "ocr_image": OcrImage,
"get_group_at_all_remain": GetGroupAtAllRemain, "get_group_at_all_remain": GetGroupAtAllRemain,
"get_online_clients": GetOnlineClients,
".get_word_slices": GetWordSlices, ".get_word_slices": GetWordSlices,
} }
@ -576,9 +592,7 @@ func (s *httpServer) ShutDown() {
if err := s.Http.Shutdown(ctx); err != nil { if err := s.Http.Shutdown(ctx); err != nil {
log.Fatal("http Server Shutdown:", err) log.Fatal("http Server Shutdown:", err)
} }
select { <-ctx.Done()
case <-ctx.Done(): log.Println("timeout of 5 seconds.")
log.Println("timeout of 5 seconds.")
}
log.Println("http Server exiting") log.Println("http Server exiting")
} }

View File

@ -17,36 +17,38 @@ import (
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )
type websocketServer struct { type webSocketServer struct {
bot *coolq.CQBot bot *coolq.CQBot
token string token string
eventConn []*websocketConn eventConn []*webSocketConn
eventConnMutex sync.Mutex eventConnMutex sync.Mutex
handshake string handshake string
} }
type websocketClient struct { //WebSocketClient Websocket客户端实例
conf *global.GoCQReverseWebsocketConfig type WebSocketClient struct {
conf *global.GoCQReverseWebSocketConfig
token string token string
bot *coolq.CQBot bot *coolq.CQBot
universalConn *websocketConn universalConn *webSocketConn
eventConn *websocketConn eventConn *webSocketConn
} }
type websocketConn struct { type webSocketConn struct {
*websocket.Conn *websocket.Conn
sync.Mutex sync.Mutex
} }
var WebsocketServer = &websocketServer{} //WebSocketServer 初始化一个WebSocketServer实例
var WebSocketServer = &webSocketServer{}
var upgrader = websocket.Upgrader{ var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
return true return true
}, },
} }
func (s *websocketServer) Run(addr, authToken string, b *coolq.CQBot) { func (s *webSocketServer) Run(addr, authToken string, b *coolq.CQBot) {
s.token = authToken s.token = authToken
s.bot = b s.bot = b
s.handshake = fmt.Sprintf(`{"_post_method":2,"meta_event_type":"lifecycle","post_type":"meta_event","self_id":%d,"sub_type":"connect","time":%d}`, s.handshake = fmt.Sprintf(`{"_post_method":2,"meta_event_type":"lifecycle","post_type":"meta_event","self_id":%d,"sub_type":"connect","time":%d}`,
@ -61,29 +63,31 @@ func (s *websocketServer) Run(addr, authToken string, b *coolq.CQBot) {
}() }()
} }
func NewWebsocketClient(conf *global.GoCQReverseWebsocketConfig, authToken string, b *coolq.CQBot) *websocketClient { //NewWebSocketClient 初始化一个NWebSocket客户端
return &websocketClient{conf: conf, token: authToken, bot: b} func NewWebSocketClient(conf *global.GoCQReverseWebSocketConfig, authToken string, b *coolq.CQBot) *WebSocketClient {
return &WebSocketClient{conf: conf, token: authToken, bot: b}
} }
func (c *websocketClient) Run() { //Run 运行实例
func (c *WebSocketClient) Run() {
if !c.conf.Enabled { if !c.conf.Enabled {
return return
} }
if c.conf.ReverseUrl != "" { if c.conf.ReverseURL != "" {
c.connectUniversal() c.connectUniversal()
} else { } else {
if c.conf.ReverseApiUrl != "" { if c.conf.ReverseAPIURL != "" {
c.connectApi() c.connectAPI()
} }
if c.conf.ReverseEventUrl != "" { if c.conf.ReverseEventURL != "" {
c.connectEvent() c.connectEvent()
} }
} }
c.bot.OnEventPush(c.onBotPushEvent) c.bot.OnEventPush(c.onBotPushEvent)
} }
func (c *websocketClient) connectApi() { func (c *WebSocketClient) connectAPI() {
log.Infof("开始尝试连接到反向Websocket API服务器: %v", c.conf.ReverseApiUrl) log.Infof("开始尝试连接到反向Websocket API服务器: %v", c.conf.ReverseAPIURL)
header := http.Header{ header := http.Header{
"X-Client-Role": []string{"API"}, "X-Client-Role": []string{"API"},
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)}, "X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
@ -92,22 +96,22 @@ func (c *websocketClient) connectApi() {
if c.token != "" { if c.token != "" {
header["Authorization"] = []string{"Token " + c.token} header["Authorization"] = []string{"Token " + c.token}
} }
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseApiUrl, header) conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseAPIURL, header)
if err != nil { if err != nil {
log.Warnf("连接到反向Websocket API服务器 %v 时出现错误: %v", c.conf.ReverseApiUrl, err) log.Warnf("连接到反向Websocket API服务器 %v 时出现错误: %v", c.conf.ReverseAPIURL, err)
if c.conf.ReverseReconnectInterval != 0 { if c.conf.ReverseReconnectInterval != 0 {
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval)) time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
c.connectApi() c.connectAPI()
} }
return return
} }
log.Infof("已连接到反向Websocket API服务器 %v", c.conf.ReverseApiUrl) log.Infof("已连接到反向Websocket API服务器 %v", c.conf.ReverseAPIURL)
wrappedConn := &websocketConn{Conn: conn} wrappedConn := &webSocketConn{Conn: conn}
go c.listenApi(wrappedConn, false) go c.listenAPI(wrappedConn, false)
} }
func (c *websocketClient) connectEvent() { func (c *WebSocketClient) connectEvent() {
log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl) log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventURL)
header := http.Header{ header := http.Header{
"X-Client-Role": []string{"Event"}, "X-Client-Role": []string{"Event"},
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)}, "X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
@ -116,9 +120,9 @@ func (c *websocketClient) connectEvent() {
if c.token != "" { if c.token != "" {
header["Authorization"] = []string{"Token " + c.token} header["Authorization"] = []string{"Token " + c.token}
} }
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseEventUrl, header) conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseEventURL, header)
if err != nil { if err != nil {
log.Warnf("连接到反向Websocket Event服务器 %v 时出现错误: %v", c.conf.ReverseEventUrl, err) log.Warnf("连接到反向Websocket Event服务器 %v 时出现错误: %v", c.conf.ReverseEventURL, err)
if c.conf.ReverseReconnectInterval != 0 { if c.conf.ReverseReconnectInterval != 0 {
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval)) time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
c.connectEvent() c.connectEvent()
@ -133,12 +137,12 @@ func (c *websocketClient) connectEvent() {
log.Warnf("反向Websocket 握手时出现错误: %v", err) log.Warnf("反向Websocket 握手时出现错误: %v", err)
} }
log.Infof("已连接到反向Websocket Event服务器 %v", c.conf.ReverseEventUrl) log.Infof("已连接到反向Websocket Event服务器 %v", c.conf.ReverseEventURL)
c.eventConn = &websocketConn{Conn: conn} c.eventConn = &webSocketConn{Conn: conn}
} }
func (c *websocketClient) connectUniversal() { func (c *WebSocketClient) connectUniversal() {
log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl) log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseURL)
header := http.Header{ header := http.Header{
"X-Client-Role": []string{"Universal"}, "X-Client-Role": []string{"Universal"},
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)}, "X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
@ -147,9 +151,9 @@ func (c *websocketClient) connectUniversal() {
if c.token != "" { if c.token != "" {
header["Authorization"] = []string{"Token " + c.token} header["Authorization"] = []string{"Token " + c.token}
} }
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseUrl, header) conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseURL, header)
if err != nil { if err != nil {
log.Warnf("连接到反向Websocket Universal服务器 %v 时出现错误: %v", c.conf.ReverseUrl, err) log.Warnf("连接到反向Websocket Universal服务器 %v 时出现错误: %v", c.conf.ReverseURL, err)
if c.conf.ReverseReconnectInterval != 0 { if c.conf.ReverseReconnectInterval != 0 {
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval)) time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
c.connectUniversal() c.connectUniversal()
@ -164,12 +168,12 @@ func (c *websocketClient) connectUniversal() {
log.Warnf("反向Websocket 握手时出现错误: %v", err) log.Warnf("反向Websocket 握手时出现错误: %v", err)
} }
wrappedConn := &websocketConn{Conn: conn} wrappedConn := &webSocketConn{Conn: conn}
go c.listenApi(wrappedConn, true) go c.listenAPI(wrappedConn, true)
c.universalConn = wrappedConn c.universalConn = wrappedConn
} }
func (c *websocketClient) listenApi(conn *websocketConn, u bool) { func (c *WebSocketClient) listenAPI(conn *webSocketConn, u bool) {
defer conn.Close() defer conn.Close()
for { for {
_, buf, err := conn.ReadMessage() _, buf, err := conn.ReadMessage()
@ -184,12 +188,12 @@ func (c *websocketClient) listenApi(conn *websocketConn, u bool) {
if c.conf.ReverseReconnectInterval != 0 { if c.conf.ReverseReconnectInterval != 0 {
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval)) time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
if !u { if !u {
go c.connectApi() go c.connectAPI()
} }
} }
} }
func (c *websocketClient) onBotPushEvent(m coolq.MSG) { func (c *WebSocketClient) onBotPushEvent(m coolq.MSG) {
if c.eventConn != nil { if c.eventConn != nil {
log.Debugf("向WS服务器 %v 推送Event: %v", c.eventConn.RemoteAddr().String(), m.ToJson()) log.Debugf("向WS服务器 %v 推送Event: %v", c.eventConn.RemoteAddr().String(), m.ToJson())
conn := c.eventConn conn := c.eventConn
@ -222,7 +226,7 @@ func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
} }
} }
func (s *websocketServer) event(w http.ResponseWriter, r *http.Request) { func (s *webSocketServer) event(w http.ResponseWriter, r *http.Request) {
if s.token != "" { if s.token != "" {
if auth := r.URL.Query().Get("access_token"); auth != s.token { if auth := r.URL.Query().Get("access_token"); auth != s.token {
if auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(auth) != 2 || auth[1] != s.token { if auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(auth) != 2 || auth[1] != s.token {
@ -246,14 +250,14 @@ func (s *websocketServer) event(w http.ResponseWriter, r *http.Request) {
log.Infof("接受 Websocket 连接: %v (/event)", r.RemoteAddr) log.Infof("接受 Websocket 连接: %v (/event)", r.RemoteAddr)
conn := &websocketConn{Conn: c} conn := &webSocketConn{Conn: c}
s.eventConnMutex.Lock() s.eventConnMutex.Lock()
s.eventConn = append(s.eventConn, conn) s.eventConn = append(s.eventConn, conn)
s.eventConnMutex.Unlock() s.eventConnMutex.Unlock()
} }
func (s *websocketServer) api(w http.ResponseWriter, r *http.Request) { func (s *webSocketServer) api(w http.ResponseWriter, r *http.Request) {
if s.token != "" { if s.token != "" {
if auth := r.URL.Query().Get("access_token"); auth != s.token { if auth := r.URL.Query().Get("access_token"); auth != s.token {
if auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(auth) != 2 || auth[1] != s.token { if auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(auth) != 2 || auth[1] != s.token {
@ -269,11 +273,11 @@ func (s *websocketServer) api(w http.ResponseWriter, r *http.Request) {
return return
} }
log.Infof("接受 Websocket 连接: %v (/api)", r.RemoteAddr) log.Infof("接受 Websocket 连接: %v (/api)", r.RemoteAddr)
conn := &websocketConn{Conn: c} conn := &webSocketConn{Conn: c}
go s.listenApi(conn) go s.listenAPI(conn)
} }
func (s *websocketServer) any(w http.ResponseWriter, r *http.Request) { func (s *webSocketServer) any(w http.ResponseWriter, r *http.Request) {
if s.token != "" { if s.token != "" {
if auth := r.URL.Query().Get("access_token"); auth != s.token { if auth := r.URL.Query().Get("access_token"); auth != s.token {
if auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(auth) != 2 || auth[1] != s.token { if auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2); len(auth) != 2 || auth[1] != s.token {
@ -295,12 +299,12 @@ func (s *websocketServer) any(w http.ResponseWriter, r *http.Request) {
return return
} }
log.Infof("接受 Websocket 连接: %v (/)", r.RemoteAddr) log.Infof("接受 Websocket 连接: %v (/)", r.RemoteAddr)
conn := &websocketConn{Conn: c} conn := &webSocketConn{Conn: c}
s.eventConn = append(s.eventConn, conn) s.eventConn = append(s.eventConn, conn)
s.listenApi(conn) s.listenAPI(conn)
} }
func (s *websocketServer) listenApi(c *websocketConn) { func (s *webSocketServer) listenAPI(c *webSocketConn) {
defer c.Close() defer c.Close()
for { for {
t, payload, err := c.ReadMessage() t, payload, err := c.ReadMessage()
@ -314,7 +318,7 @@ func (s *websocketServer) listenApi(c *websocketConn) {
} }
} }
func (c *websocketConn) handleRequest(bot *coolq.CQBot, payload []byte) { func (c *webSocketConn) handleRequest(bot *coolq.CQBot, payload []byte) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Printf("处置WS命令时发生无法恢复的异常%v\n%s", err, debug.Stack()) log.Printf("处置WS命令时发生无法恢复的异常%v\n%s", err, debug.Stack())
@ -325,7 +329,7 @@ func (c *websocketConn) handleRequest(bot *coolq.CQBot, payload []byte) {
j := gjson.ParseBytes(payload) j := gjson.ParseBytes(payload)
t := strings.ReplaceAll(j.Get("action").Str, "_async", "") t := strings.ReplaceAll(j.Get("action").Str, "_async", "")
log.Debugf("WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw) log.Debugf("WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw)
if f, ok := wsApi[t]; ok { if f, ok := wsAPI[t]; ok {
ret := f(bot, j.Get("params")) ret := f(bot, j.Get("params"))
if j.Get("echo").Exists() { if j.Get("echo").Exists() {
ret["echo"] = j.Get("echo").Value() ret["echo"] = j.Get("echo").Value()
@ -344,7 +348,7 @@ func (c *websocketConn) handleRequest(bot *coolq.CQBot, payload []byte) {
} }
} }
func (s *websocketServer) onBotPushEvent(m coolq.MSG) { func (s *webSocketServer) onBotPushEvent(m coolq.MSG) {
s.eventConnMutex.Lock() s.eventConnMutex.Lock()
defer s.eventConnMutex.Unlock() defer s.eventConnMutex.Unlock()
for i, l := 0, len(s.eventConn); i < l; i++ { for i, l := 0, len(s.eventConn); i < l; i++ {
@ -368,7 +372,7 @@ func (s *websocketServer) onBotPushEvent(m coolq.MSG) {
} }
} }
var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{ var wsAPI = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
"get_login_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "get_login_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetLoginInfo() return bot.CQGetLoginInfo()
}, },
@ -516,7 +520,7 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
return bot.CQGetGroupHonorInfo(p.Get("group_id").Int(), p.Get("type").Str) return bot.CQGetGroupHonorInfo(p.Get("group_id").Int(), p.Get("type").Str)
}, },
"set_restart": func(c *coolq.CQBot, p gjson.Result) coolq.MSG { "set_restart": func(c *coolq.CQBot, p gjson.Result) coolq.MSG {
var delay int64 = 0 var delay int64
delay = p.Get("delay").Int() delay = p.Get("delay").Int()
if delay < 0 { if delay < 0 {
delay = 0 delay = 0
@ -558,6 +562,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
"get_group_file_url": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "get_group_file_url": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetGroupFileUrl(p.Get("group_id").Int(), p.Get("file_id").Str, int32(p.Get("busid").Int())) return bot.CQGetGroupFileUrl(p.Get("group_id").Int(), p.Get("file_id").Str, int32(p.Get("busid").Int()))
}, },
"get_group_msg_history": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetGroupMessageHistory(p.Get("group_id").Int(), p.Get("message_seq").Int())
},
"_get_vip_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "_get_vip_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetVipInfo(p.Get("user_id").Int()) return bot.CQGetVipInfo(p.Get("user_id").Int())
}, },
@ -573,6 +580,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
"get_group_at_all_remain": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "get_group_at_all_remain": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetAtAllRemain(p.Get("group_id").Int()) return bot.CQGetAtAllRemain(p.Get("group_id").Int())
}, },
"get_online_clients": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetOnlineClients(p.Get("no_cache").Bool())
},
".get_word_slices": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { ".get_word_slices": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetWordSlices(p.Get("content").Str) return bot.CQGetWordSlices(p.Get("content").Str)
}, },