mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-06-30 11:53:25 +00:00
Compare commits
56 Commits
Author | SHA1 | Date | |
---|---|---|---|
470efa07ab | |||
b7572f8d2c | |||
c9a914b5d5 | |||
51696e8054 | |||
a6bcd96415 | |||
08694f5ae8 | |||
8c71dbff68 | |||
568b0e479f | |||
d1da08a376 | |||
5768c61bc7 | |||
864120d3fe | |||
99b414530a | |||
4820eb2fec | |||
00d80d5dfc | |||
22d9ddb4ea | |||
fa33dbdd4e | |||
be8b68c8e9 | |||
cef129eff7 | |||
1fac06f58a | |||
1a1f860dbe | |||
f325b26e1a | |||
b545e05a9d | |||
b290a0a596 | |||
9af61a336b | |||
774a1e32da | |||
50eee15a67 | |||
edf6180e1c | |||
3c04573e82 | |||
5e49820319 | |||
1418e36bab | |||
611d16d79e | |||
c6f701e8c8 | |||
a49a57b964 | |||
bb03315930 | |||
5683db6324 | |||
f1ffa17d20 | |||
69675aa0f9 | |||
782a3e2a39 | |||
1967e687af | |||
23d436972c | |||
129622dd24 | |||
19104d00a3 | |||
6c0e78b60c | |||
a075f41a51 | |||
cdbf903361 | |||
906247fcf0 | |||
72a7430841 | |||
5c4f586c36 | |||
5d2e202b0c | |||
c27ebadbc4 | |||
d5a8f3ead2 | |||
03bfd9dd3d | |||
0b02178402 | |||
3819f2c2da | |||
fdd517ee8c | |||
2d010326c7 |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
.gitlab-ci.yml
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
README.md
|
||||
LICENSE
|
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
FROM golang:1.14.2-alpine AS builder
|
||||
|
||||
RUN go env -w GO111MODULE=auto \
|
||||
&& go env -w CGO_ENABLED=0 \
|
||||
&& mkdir /build
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY ./ .
|
||||
|
||||
RUN cd /build \
|
||||
&& go build -ldflags "-s -w -extldflags '-static'" -o cqhttp
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=builder /build/cqhttp /usr/bin/cqhttp
|
||||
RUN chmod +x /usr/bin/cqhttp
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/cqhttp" ]
|
11
README.md
11
README.md
@ -28,6 +28,7 @@
|
||||
|
||||
- [CQ:image]
|
||||
- [CQ:record]
|
||||
- [CQ:video]
|
||||
- [CQ:face]
|
||||
- [CQ:at]
|
||||
- [CQ:share]
|
||||
@ -91,6 +92,16 @@
|
||||
|
||||
</details>
|
||||
|
||||
# 关于ISSUE
|
||||
|
||||
以下ISSUE会被直接关闭
|
||||
- 提交BUG不使用Template
|
||||
- 询问已知问题
|
||||
- 提问找不到重点
|
||||
- 重复提问
|
||||
|
||||
> 请注意, 开发者并没有义务回复您的问题. 您应该具备基本的提问技巧。
|
||||
|
||||
# 性能
|
||||
|
||||
在关闭数据库的情况下, 加载25个好友128个群运行24小时后内存使用为10MB左右. 开启数据库后内存使用将根据消息量增加10-20MB, 如果系统内存小于128M建议关闭数据库使用.
|
||||
|
118
coolq/api.go
118
coolq/api.go
@ -34,8 +34,11 @@ func (bot *CQBot) CQGetFriendList() MSG {
|
||||
}
|
||||
|
||||
// https://cqhttp.cc/docs/4.15/#/API?id=get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
|
||||
func (bot *CQBot) CQGetGroupList() MSG {
|
||||
func (bot *CQBot) CQGetGroupList(noCache bool) MSG {
|
||||
var gs []MSG
|
||||
if noCache {
|
||||
_ = bot.Client.ReloadGroupList()
|
||||
}
|
||||
for _, g := range bot.Client.GroupList {
|
||||
gs = append(gs, MSG{
|
||||
"group_id": g.Code,
|
||||
@ -96,11 +99,25 @@ func (bot *CQBot) CQGetGroupMemberInfo(groupId, userId int64, noCache bool) MSG
|
||||
}
|
||||
|
||||
// https://cqhttp.cc/docs/4.15/#/API?id=send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF
|
||||
func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}) MSG {
|
||||
func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}, autoEscape bool) MSG {
|
||||
var str string
|
||||
fixAt := func(elem []message.IMessageElement) {
|
||||
for _, e := range elem {
|
||||
if at, ok := e.(*message.AtElement); ok && at.Target != 0 {
|
||||
at.Display = "@" + func() string {
|
||||
mem := bot.Client.FindGroup(groupId).FindMember(at.Target)
|
||||
if mem != nil {
|
||||
return mem.DisplayName()
|
||||
}
|
||||
return strconv.FormatInt(at.Target, 10)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
if m, ok := i.(gjson.Result); ok {
|
||||
if m.Type == gjson.JSON {
|
||||
elem := bot.ConvertObjectMessage(m, true)
|
||||
fixAt(elem)
|
||||
mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100)
|
||||
@ -113,20 +130,19 @@ func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}) MSG {
|
||||
}
|
||||
return m.Raw
|
||||
}()
|
||||
}
|
||||
if s, ok := i.(string); ok {
|
||||
} else if s, ok := i.(string); ok {
|
||||
str = s
|
||||
}
|
||||
if str == "" {
|
||||
return Failed(100)
|
||||
}
|
||||
elem := bot.ConvertStringMessage(str, true)
|
||||
// fix at display
|
||||
for _, e := range elem {
|
||||
if at, ok := e.(*message.AtElement); ok && at.Target != 0 {
|
||||
at.Display = "@" + bot.Client.FindGroup(groupId).FindMember(at.Target).DisplayName()
|
||||
}
|
||||
var elem []message.IMessageElement
|
||||
if autoEscape {
|
||||
elem = append(elem, message.NewText(str))
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, true)
|
||||
}
|
||||
fixAt(elem)
|
||||
mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100)
|
||||
@ -205,7 +221,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupId int64, m gjson.Result) MSG {
|
||||
}
|
||||
|
||||
// https://cqhttp.cc/docs/4.15/#/API?id=send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
|
||||
func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}) MSG {
|
||||
func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}, autoEscape bool) MSG {
|
||||
var str string
|
||||
if m, ok := i.(gjson.Result); ok {
|
||||
if m.Type == gjson.JSON {
|
||||
@ -222,14 +238,18 @@ func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}) MSG {
|
||||
}
|
||||
return m.Raw
|
||||
}()
|
||||
}
|
||||
if s, ok := i.(string); ok {
|
||||
} else if s, ok := i.(string); ok {
|
||||
str = s
|
||||
}
|
||||
if str == "" {
|
||||
return Failed(100)
|
||||
}
|
||||
elem := bot.ConvertStringMessage(str, false)
|
||||
var elem []message.IMessageElement
|
||||
if autoEscape {
|
||||
elem = append(elem, message.NewText(str))
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, false)
|
||||
}
|
||||
mid := bot.SendPrivateMessage(userId, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100)
|
||||
@ -359,6 +379,61 @@ func (bot *CQBot) CQDeleteMessage(messageId int32) MSG {
|
||||
return OK(nil)
|
||||
}
|
||||
|
||||
// https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
|
||||
func (bot *CQBot) CQGetGroupHonorInfo(groupId int64, t string) MSG {
|
||||
msg := MSG{"group_id": groupId}
|
||||
convertMem := func(memList []client.HonorMemberInfo) (ret []MSG) {
|
||||
for _, mem := range memList {
|
||||
ret = append(ret, MSG{
|
||||
"user_id": mem.Uin,
|
||||
"nickname": mem.Name,
|
||||
"avatar": mem.Avatar,
|
||||
"description": mem.Desc,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
if t == "talkative" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Talkative); err == nil {
|
||||
if honor.CurrentTalkative.Uin != 0 {
|
||||
msg["current_talkative"] = MSG{
|
||||
"user_id": honor.CurrentTalkative.Uin,
|
||||
"nickname": honor.CurrentTalkative.Name,
|
||||
"avatar": honor.CurrentTalkative.Avatar,
|
||||
"day_count": honor.CurrentTalkative.DayCount,
|
||||
}
|
||||
}
|
||||
msg["talkative_list"] = convertMem(honor.TalkativeList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "performer" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Performer); err == nil {
|
||||
msg["performer_lis"] = convertMem(honor.ActorList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "legend" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Legend); err == nil {
|
||||
msg["legend_list"] = convertMem(honor.LegendList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "strong_newbie" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.StrongNewbie); err == nil {
|
||||
msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "emotion" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Emotion); err == nil {
|
||||
msg["emotion_list"] = convertMem(honor.EmotionList)
|
||||
}
|
||||
}
|
||||
|
||||
return OK(msg)
|
||||
}
|
||||
|
||||
// https://cqhttp.cc/docs/4.15/#/API?id=-handle_quick_operation-%E5%AF%B9%E4%BA%8B%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%93%8D%E4%BD%9C
|
||||
// https://github.com/richardchien/coolq-http-api/blob/master/src/cqhttp/plugins/web/http.cpp#L376
|
||||
func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
@ -368,6 +443,7 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
msgType := context.Get("message_type").Str
|
||||
reply := operation.Get("reply")
|
||||
if reply.Exists() {
|
||||
autoEscape := global.EnsureBool(operation.Get("auto_escape"), false)
|
||||
/*
|
||||
at := true
|
||||
if operation.Get("at_sender").Exists() {
|
||||
@ -376,10 +452,10 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
*/
|
||||
// TODO: 处理at字段
|
||||
if msgType == "group" {
|
||||
bot.CQSendGroupMessage(context.Get("group_id").Int(), reply)
|
||||
bot.CQSendGroupMessage(context.Get("group_id").Int(), reply, autoEscape)
|
||||
}
|
||||
if msgType == "private" {
|
||||
bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply)
|
||||
bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply, autoEscape)
|
||||
}
|
||||
}
|
||||
if msgType == "group" {
|
||||
@ -404,12 +480,12 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
}
|
||||
case "request":
|
||||
reqType := context.Get("request_type").Str
|
||||
if context.Get("approve").Bool() {
|
||||
if operation.Get("approve").Exists() {
|
||||
if reqType == "friend" {
|
||||
bot.CQProcessFriendRequest(context.Get("flag").Str, true)
|
||||
bot.CQProcessFriendRequest(context.Get("flag").Str, operation.Get("approve").Bool())
|
||||
}
|
||||
if reqType == "group" {
|
||||
bot.CQProcessGroupRequest(context.Get("flag").Str, context.Get("sub_type").Str, true)
|
||||
bot.CQProcessGroupRequest(context.Get("flag").Str, context.Get("sub_type").Str, operation.Get("approve").Bool())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -439,14 +515,14 @@ func (bot *CQBot) CQGetForwardMessage(resId string) MSG {
|
||||
}
|
||||
var r []MSG
|
||||
for _, n := range m.Nodes {
|
||||
checkImage(n.Message)
|
||||
bot.checkMedia(n.Message)
|
||||
r = append(r, MSG{
|
||||
"sender": MSG{
|
||||
"user_id": n.SenderId,
|
||||
"nickname": n.SenderName,
|
||||
},
|
||||
"time": n.Time,
|
||||
"content": ToStringMessage(n.Message, 0, false),
|
||||
"content": ToFormattedMessage(n.Message, 0, false),
|
||||
})
|
||||
}
|
||||
return OK(MSG{
|
||||
|
22
coolq/bot.go
22
coolq/bot.go
@ -12,6 +12,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/xujiajun/nutsdb"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
@ -63,6 +64,19 @@ func NewQQBot(cli *client.QQClient, conf *global.JsonConfig) *CQBot {
|
||||
bot.Client.OnNewFriendAdded(bot.friendAddedEvent)
|
||||
bot.Client.OnGroupInvited(bot.groupInvitedEvent)
|
||||
bot.Client.OnUserWantJoinGroup(bot.groupJoinReqEvent)
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Second * 5)
|
||||
bot.dispatchEventMessage(MSG{
|
||||
"time": time.Now().Unix(),
|
||||
"self_id": bot.Client.Uin,
|
||||
"post_type": "meta_event",
|
||||
"meta_event_type": "heartbeat",
|
||||
"status": nil,
|
||||
"interval": 5000,
|
||||
})
|
||||
}
|
||||
}()
|
||||
return bot
|
||||
}
|
||||
|
||||
@ -85,7 +99,7 @@ func (bot *CQBot) GetGroupMessage(mid int32) MSG {
|
||||
if err == nil {
|
||||
return m
|
||||
}
|
||||
log.Warnf("获取信息时出现错误: %v", err)
|
||||
log.Warnf("获取信息时出现错误: %v id: %v", err, mid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -94,6 +108,7 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
|
||||
var newElem []message.IMessageElement
|
||||
for _, elem := range m.Elements {
|
||||
if i, ok := elem.(*message.ImageElement); ok {
|
||||
_, _ = bot.Client.UploadGroupImage(int64(rand.Intn(11451419)), i.Data)
|
||||
gm, err := bot.Client.UploadGroupImage(groupId, i.Data)
|
||||
if err != nil {
|
||||
log.Warnf("警告: 群 %v 消息图片上传失败: %v", groupId, err)
|
||||
@ -115,6 +130,9 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
|
||||
}
|
||||
m.Elements = newElem
|
||||
ret := bot.Client.SendGroupMessage(groupId, m)
|
||||
if ret == nil || ret.Id == -1 {
|
||||
return -1
|
||||
}
|
||||
return bot.InsertGroupMessage(ret)
|
||||
}
|
||||
|
||||
@ -191,7 +209,7 @@ func (bot *CQBot) dispatchEventMessage(m MSG) {
|
||||
fn(m)
|
||||
end := time.Now()
|
||||
if end.Sub(start) > time.Second*5 {
|
||||
log.Debugf("警告: 事件处理耗时超过 5 秒 (%v秒), 请检查应用是否有堵塞.", end.Sub(start)/time.Second)
|
||||
log.Debugf("警告: 事件处理耗时超过 5 秒 (%v), 请检查应用是否有堵塞.", end.Sub(start))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
135
coolq/cqcode.go
135
coolq/cqcode.go
@ -1,6 +1,7 @@
|
||||
package coolq
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
@ -23,6 +24,88 @@ var matchReg = regexp.MustCompile(`\[CQ:\w+?.*?]`)
|
||||
var typeReg = regexp.MustCompile(`\[CQ:(\w+)`)
|
||||
var paramReg = regexp.MustCompile(`,([\w\-.]+?)=([^,\]]+)`)
|
||||
|
||||
func ToArrayMessage(e []message.IMessageElement, code int64, raw ...bool) (r []MSG) {
|
||||
ur := false
|
||||
if len(raw) != 0 {
|
||||
ur = raw[0]
|
||||
}
|
||||
for _, elem := range e {
|
||||
m := MSG{}
|
||||
switch o := elem.(type) {
|
||||
case *message.TextElement:
|
||||
m = MSG{
|
||||
"type": "text",
|
||||
"data": map[string]string{"text": o.Content},
|
||||
}
|
||||
case *message.AtElement:
|
||||
if o.Target == 0 {
|
||||
m = MSG{
|
||||
"type": "at",
|
||||
"data": map[string]string{"qq": "all"},
|
||||
}
|
||||
} else {
|
||||
m = MSG{
|
||||
"type": "at",
|
||||
"data": map[string]string{"qq": fmt.Sprint(o.Target)},
|
||||
}
|
||||
}
|
||||
case *message.ReplyElement:
|
||||
m = MSG{
|
||||
"type": "reply",
|
||||
"data": map[string]string{"id": fmt.Sprint(ToGlobalId(code, o.ReplySeq))},
|
||||
}
|
||||
case *message.ForwardElement:
|
||||
m = MSG{
|
||||
"type": "forward",
|
||||
"data": map[string]string{"id": o.ResId},
|
||||
}
|
||||
case *message.FaceElement:
|
||||
m = MSG{
|
||||
"type": "face",
|
||||
"data": map[string]string{"id": fmt.Sprint(o.Index)},
|
||||
}
|
||||
case *message.VoiceElement:
|
||||
if ur {
|
||||
m = MSG{
|
||||
"type": "record",
|
||||
"data": map[string]string{"file": o.Name},
|
||||
}
|
||||
} else {
|
||||
m = MSG{
|
||||
"type": "record",
|
||||
"data": map[string]string{"file": o.Name, "url": o.Url},
|
||||
}
|
||||
}
|
||||
case *message.ShortVideoElement:
|
||||
if ur {
|
||||
m = MSG{
|
||||
"type": "video",
|
||||
"data": map[string]string{"file": o.Name},
|
||||
}
|
||||
} else {
|
||||
m = MSG{
|
||||
"type": "video",
|
||||
"data": map[string]string{"file": o.Name, "url": o.Url},
|
||||
}
|
||||
}
|
||||
case *message.ImageElement:
|
||||
if ur {
|
||||
m = MSG{
|
||||
"type": "image",
|
||||
"data": map[string]string{"file": o.Filename},
|
||||
}
|
||||
} else {
|
||||
m = MSG{
|
||||
"type": "image",
|
||||
"data": map[string]string{"file": o.Filename, "url": o.Url},
|
||||
}
|
||||
}
|
||||
}
|
||||
r = append(r, m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r string) {
|
||||
ur := false
|
||||
if len(raw) != 0 {
|
||||
@ -45,12 +128,22 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
|
||||
case *message.FaceElement:
|
||||
r += fmt.Sprintf(`[CQ:face,id=%d]`, o.Index)
|
||||
case *message.VoiceElement:
|
||||
r += fmt.Sprintf(`[CQ:record,file=%s]`, o.Name)
|
||||
if ur {
|
||||
r += fmt.Sprintf(`[CQ:record,file=%s]`, o.Name)
|
||||
} else {
|
||||
r += fmt.Sprintf(`[CQ:record,file=%s,url=%s]`, o.Name, CQCodeEscapeValue(o.Url))
|
||||
}
|
||||
case *message.ShortVideoElement:
|
||||
if ur {
|
||||
r += fmt.Sprintf(`[CQ:video,file=%s]`, o.Name)
|
||||
} else {
|
||||
r += fmt.Sprintf(`[CQ:video,file=%s,url=%s]`, o.Name, CQCodeEscapeValue(o.Url))
|
||||
}
|
||||
case *message.ImageElement:
|
||||
if ur {
|
||||
r += fmt.Sprintf(`[CQ:image,file=%s]`, o.Filename)
|
||||
} else {
|
||||
r += fmt.Sprintf(`[CQ:image,file=%s,url=%s]`, o.Filename, o.Url)
|
||||
r += fmt.Sprintf(`[CQ:image,file=%s,url=%s]`, o.Filename, CQCodeEscapeValue(o.Url))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,7 +164,7 @@ func (bot *CQBot) ConvertStringMessage(m string, group bool) (r []message.IMessa
|
||||
ps := paramReg.FindAllStringSubmatch(code, -1)
|
||||
d := make(map[string]string)
|
||||
for _, p := range ps {
|
||||
d[p[1]] = p[2]
|
||||
d[p[1]] = CQCodeUnescapeValue(p[2])
|
||||
}
|
||||
if t == "reply" && group {
|
||||
if len(r) > 0 {
|
||||
@ -169,10 +262,23 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
case "image":
|
||||
f := d["file"]
|
||||
if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") {
|
||||
cache := d["cache"]
|
||||
if cache == "" {
|
||||
cache = "1"
|
||||
}
|
||||
hash := md5.Sum([]byte(f))
|
||||
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache")
|
||||
if global.PathExists(cacheFile) && cache == "1" {
|
||||
b, err := ioutil.ReadFile(cacheFile)
|
||||
if err == nil {
|
||||
return message.NewImage(b), nil
|
||||
}
|
||||
}
|
||||
b, err := global.GetBytes(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = ioutil.WriteFile(cacheFile, b, 0644)
|
||||
return message.NewImage(b), nil
|
||||
}
|
||||
if strings.HasPrefix(f, "base64") {
|
||||
@ -286,7 +392,14 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
}
|
||||
data = b
|
||||
}
|
||||
if !global.IsAMR(data) {
|
||||
if global.PathExists(path.Join(global.VOICE_PATH, f)) {
|
||||
b, err := ioutil.ReadFile(path.Join(global.VOICE_PATH, f))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = b
|
||||
}
|
||||
if !global.IsAMRorSILK(data) {
|
||||
return nil, errors.New("unsupported voice file format (please use AMR file for now)")
|
||||
}
|
||||
return &message.VoiceElement{Data: data}, nil
|
||||
@ -312,9 +425,15 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
|
||||
func CQCodeEscapeText(raw string) string {
|
||||
ret := raw
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
ret = strings.ReplaceAll(ret, "[", "[")
|
||||
ret = strings.ReplaceAll(ret, "]", "]")
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
return ret
|
||||
}
|
||||
|
||||
func CQCodeEscapeValue(value string) string {
|
||||
ret := CQCodeEscapeText(value)
|
||||
ret = strings.ReplaceAll(ret, ",", ",")
|
||||
return ret
|
||||
}
|
||||
|
||||
@ -325,3 +444,9 @@ func CQCodeUnescapeText(content string) string {
|
||||
ret = strings.ReplaceAll(ret, "&", "&")
|
||||
return ret
|
||||
}
|
||||
|
||||
func CQCodeUnescapeValue(content string) string {
|
||||
ret := strings.ReplaceAll(content, ",", ",")
|
||||
ret = CQCodeUnescapeText(ret)
|
||||
return ret
|
||||
}
|
||||
|
@ -10,11 +10,27 @@ import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var format = "string"
|
||||
|
||||
func SetMessageFormat(f string) {
|
||||
format = f
|
||||
}
|
||||
|
||||
func ToFormattedMessage(e []message.IMessageElement, code int64, raw ...bool) (r interface{}) {
|
||||
if format == "string" {
|
||||
r = ToStringMessage(e, code, raw...)
|
||||
} else if format == "array" {
|
||||
r = ToArrayMessage(e, code, raw...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMessage) {
|
||||
checkImage(m.Elements)
|
||||
bot.checkMedia(m.Elements)
|
||||
cqm := ToStringMessage(m.Elements, 0, true)
|
||||
log.Infof("收到好友 %v(%v) 的消息: %v", m.Sender.DisplayName(), m.Sender.Uin, cqm)
|
||||
fm := MSG{
|
||||
@ -23,7 +39,7 @@ func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMess
|
||||
"sub_type": "friend",
|
||||
"message_id": ToGlobalId(m.Sender.Uin, m.Id),
|
||||
"user_id": m.Sender.Uin,
|
||||
"message": ToStringMessage(m.Elements, 0, false),
|
||||
"message": ToFormattedMessage(m.Elements, 0, false),
|
||||
"raw_message": cqm,
|
||||
"font": 0,
|
||||
"self_id": c.Uin,
|
||||
@ -39,7 +55,7 @@ func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMess
|
||||
}
|
||||
|
||||
func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage) {
|
||||
checkImage(m.Elements)
|
||||
bot.checkMedia(m.Elements)
|
||||
for _, elem := range m.Elements {
|
||||
if file, ok := elem.(*message.GroupFileElement); ok {
|
||||
log.Infof("群 %v(%v) 内 %v(%v) 上传了文件: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, file.Name)
|
||||
@ -71,7 +87,7 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
|
||||
"anonymous": nil,
|
||||
"font": 0,
|
||||
"group_id": m.GroupCode,
|
||||
"message": ToStringMessage(m.Elements, m.GroupCode, false),
|
||||
"message": ToFormattedMessage(m.Elements, m.GroupCode, false),
|
||||
"message_id": id,
|
||||
"message_type": "group",
|
||||
"post_type": "message",
|
||||
@ -117,7 +133,7 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
|
||||
}
|
||||
|
||||
func (bot *CQBot) tempMessageEvent(c *client.QQClient, m *message.TempMessage) {
|
||||
checkImage(m.Elements)
|
||||
bot.checkMedia(m.Elements)
|
||||
cqm := ToStringMessage(m.Elements, 0, true)
|
||||
bot.tempMsgCache.Store(m.Sender.Uin, m.GroupCode)
|
||||
log.Infof("收到来自群 %v(%v) 内 %v(%v) 的临时会话消息: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm)
|
||||
@ -127,7 +143,7 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, m *message.TempMessage) {
|
||||
"sub_type": "group",
|
||||
"message_id": m.Id,
|
||||
"user_id": m.Sender.Uin,
|
||||
"message": ToStringMessage(m.Elements, 0, false),
|
||||
"message": ToFormattedMessage(m.Elements, 0, false),
|
||||
"raw_message": cqm,
|
||||
"font": 0,
|
||||
"self_id": c.Uin,
|
||||
@ -300,7 +316,7 @@ func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupR
|
||||
"sub_type": "add",
|
||||
"group_id": e.GroupCode,
|
||||
"user_id": e.RequesterUin,
|
||||
"comment": "",
|
||||
"comment": e.Message,
|
||||
"flag": flag,
|
||||
"time": time.Now().Unix(),
|
||||
"self_id": c.Uin,
|
||||
@ -346,9 +362,10 @@ func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.Group
|
||||
}
|
||||
}
|
||||
|
||||
func checkImage(e []message.IMessageElement) {
|
||||
func (bot *CQBot) checkMedia(e []message.IMessageElement) {
|
||||
for _, elem := range e {
|
||||
if i, ok := elem.(*message.ImageElement); ok {
|
||||
switch i := elem.(type) {
|
||||
case *message.ImageElement:
|
||||
filename := hex.EncodeToString(i.Md5) + ".image"
|
||||
if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) {
|
||||
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) {
|
||||
@ -356,9 +373,32 @@ func checkImage(e []message.IMessageElement) {
|
||||
w.WriteUInt32(uint32(i.Size))
|
||||
w.WriteString(i.Filename)
|
||||
w.WriteString(i.Url)
|
||||
}), 0777)
|
||||
}), 0644)
|
||||
}
|
||||
i.Filename = filename
|
||||
case *message.VoiceElement:
|
||||
i.Name = strings.ReplaceAll(i.Name, "{", "")
|
||||
i.Name = strings.ReplaceAll(i.Name, "}", "")
|
||||
if !global.PathExists(path.Join(global.VOICE_PATH, i.Name)) {
|
||||
b, err := global.GetBytes(i.Url)
|
||||
if err != nil {
|
||||
log.Warnf("语音文件 %v 下载失败: %v", i.Name, err)
|
||||
continue
|
||||
}
|
||||
_ = ioutil.WriteFile(path.Join(global.VOICE_PATH, i.Name), b, 0644)
|
||||
}
|
||||
case *message.ShortVideoElement:
|
||||
filename := hex.EncodeToString(i.Md5) + ".video"
|
||||
if !global.PathExists(path.Join(global.VIDEO_PATH, filename)) {
|
||||
_ = ioutil.WriteFile(path.Join(global.VIDEO_PATH, filename), binary.NewWriterF(func(w *binary.Writer) {
|
||||
w.Write(i.Md5)
|
||||
w.WriteUInt32(uint32(i.Size))
|
||||
w.WriteString(i.Name)
|
||||
w.Write(i.Uuid)
|
||||
}), 0644)
|
||||
}
|
||||
i.Name = filename
|
||||
i.Url = bot.Client.GetShortVideoUrl(i.Uuid, i.Md5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ go-cqhttp 支持导入CQHTTP的配置文件, 具体步骤为:
|
||||
"access_token": "",
|
||||
"relogin": false,
|
||||
"relogin_delay": 0,
|
||||
"post_message_format": "string",
|
||||
"http_config": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 5700,
|
||||
"post_urls": {"url:port": "secret"}
|
||||
"timeout": 5,
|
||||
"post_urls": {"url:port": "secret"},
|
||||
},
|
||||
"ws_config": {
|
||||
"enabled": true,
|
||||
|
@ -14,9 +14,11 @@ type JsonConfig struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ReLogin bool `json:"relogin"`
|
||||
ReLoginDelay int `json:"relogin_delay"`
|
||||
AsyncLoad bool `json:"async_load"`
|
||||
HttpConfig *GoCQHttpConfig `json:"http_config"`
|
||||
WSConfig *GoCQWebsocketConfig `json:"ws_config"`
|
||||
ReverseServers []*GoCQReverseWebsocketConfig `json:"ws_reverse_servers"`
|
||||
PostMessageFormat string `json:"post_message_format"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
|
||||
@ -43,6 +45,7 @@ type GoCQHttpConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Timeout int32 `json:"timeout"`
|
||||
PostUrls map[string]string `json:"post_urls"`
|
||||
}
|
||||
|
||||
@ -62,9 +65,10 @@ type GoCQReverseWebsocketConfig struct {
|
||||
|
||||
func DefaultConfig() *JsonConfig {
|
||||
return &JsonConfig{
|
||||
EnableDB: true,
|
||||
ReLogin: true,
|
||||
ReLoginDelay: 3,
|
||||
EnableDB: true,
|
||||
ReLogin: true,
|
||||
ReLoginDelay: 3,
|
||||
PostMessageFormat: "string",
|
||||
HttpConfig: &GoCQHttpConfig{
|
||||
Enabled: true,
|
||||
Host: "0.0.0.0",
|
||||
|
23
global/fs.go
23
global/fs.go
@ -1,13 +1,23 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var IMAGE_PATH = path.Join("data", "images")
|
||||
var (
|
||||
IMAGE_PATH = path.Join("data", "images")
|
||||
VOICE_PATH = path.Join("data", "voices")
|
||||
VIDEO_PATH = path.Join("data", "videos")
|
||||
CACHE_PATH = path.Join("data", "cache")
|
||||
|
||||
HEADER_AMR = []byte("#!AMR")
|
||||
HEADER_SILK = []byte("\x02#!SILK_V3")
|
||||
)
|
||||
|
||||
func PathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
@ -23,7 +33,7 @@ func ReadAllText(path string) string {
|
||||
}
|
||||
|
||||
func WriteAllText(path, text string) {
|
||||
_ = ioutil.WriteFile(path, []byte(text), 0777)
|
||||
_ = ioutil.WriteFile(path, []byte(text), 0644)
|
||||
}
|
||||
|
||||
func Check(err error) {
|
||||
@ -32,9 +42,6 @@ func Check(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func IsAMR(b []byte) bool {
|
||||
if len(b) <= 6 {
|
||||
return false
|
||||
}
|
||||
return b[0] == 0x23 && b[1] == 0x21 && b[2] == 0x41 && b[3] == 0x4D && b[4] == 0x52 // amr file header
|
||||
func IsAMRorSILK(b []byte) bool {
|
||||
return bytes.HasPrefix(b, HEADER_AMR) || bytes.HasPrefix(b, HEADER_SILK)
|
||||
}
|
||||
|
50
global/param.go
Normal file
50
global/param.go
Normal file
@ -0,0 +1,50 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/tidwall/gjson"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var trueSet = map[string]struct{}{
|
||||
"true": {},
|
||||
"yes": {},
|
||||
"1": {},
|
||||
}
|
||||
|
||||
var falseSet = map[string]struct{}{
|
||||
"false": {},
|
||||
"no": {},
|
||||
"0": {},
|
||||
}
|
||||
|
||||
func EnsureBool(p interface{}, defaultVal bool) bool {
|
||||
var str string
|
||||
if b, ok := p.(bool); ok {
|
||||
return b
|
||||
}
|
||||
if j, ok := p.(gjson.Result); ok {
|
||||
if !j.Exists() {
|
||||
return defaultVal
|
||||
}
|
||||
if j.Type == gjson.True {
|
||||
return true
|
||||
}
|
||||
if j.Type == gjson.False {
|
||||
return false
|
||||
}
|
||||
if j.Type != gjson.String {
|
||||
return defaultVal
|
||||
}
|
||||
str = j.Str
|
||||
} else if s, ok := p.(string); ok {
|
||||
str = s
|
||||
}
|
||||
str = strings.ToLower(str)
|
||||
if _, ok := trueSet[str]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := falseSet[str]; ok {
|
||||
return false
|
||||
}
|
||||
return defaultVal
|
||||
}
|
2
go.mod
2
go.mod
@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200809221224-7a84cfae6795
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/guonaihong/gout v0.1.1
|
||||
|
4
go.sum
4
go.sum
@ -1,7 +1,7 @@
|
||||
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/Mrs4s/MiraiGo v0.0.0-20200809221224-7a84cfae6795 h1:Bu4k9ZS/IIy9Shwd9lS/C2P/2I8fYUwg1OpRF91hr1w=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200809221224-7a84cfae6795/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17 h1:sY4lwW34serGKnD26hP7ZLpnJ4NfVYKdS2SJIW1Mezs=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
40
main.go
40
main.go
@ -36,14 +36,26 @@ func init() {
|
||||
if err == nil {
|
||||
log.SetOutput(io.MultiWriter(os.Stderr, w))
|
||||
}
|
||||
if !global.PathExists("data") {
|
||||
if err := os.Mkdir("data", 0777); err != nil {
|
||||
log.Fatalf("创建数据文件夹失败: %v", err)
|
||||
}
|
||||
if err := os.Mkdir(path.Join("data", "images"), 0777); err != nil {
|
||||
if !global.PathExists(global.IMAGE_PATH) {
|
||||
if err := os.MkdirAll(global.IMAGE_PATH, 0755); err != nil {
|
||||
log.Fatalf("创建图片缓存文件夹失败: %v", err)
|
||||
}
|
||||
}
|
||||
if !global.PathExists(global.VOICE_PATH) {
|
||||
if err := os.MkdirAll(global.VOICE_PATH, 0755); err != nil {
|
||||
log.Fatalf("创建语音缓存文件夹失败: %v", err)
|
||||
}
|
||||
}
|
||||
if !global.PathExists(global.VIDEO_PATH) {
|
||||
if err := os.MkdirAll(global.VIDEO_PATH, 0755); err != nil {
|
||||
log.Fatalf("创建视频缓存文件夹失败: %v", err)
|
||||
}
|
||||
}
|
||||
if !global.PathExists(global.CACHE_PATH) {
|
||||
if err := os.MkdirAll(global.CACHE_PATH, 0755); err != nil {
|
||||
log.Fatalf("创建发送图片缓存文件夹失败: %v", err)
|
||||
}
|
||||
}
|
||||
if global.PathExists("cqhttp.json") {
|
||||
log.Info("发现 cqhttp.json 将在五秒后尝试导入配置,按 Ctrl+C 取消.")
|
||||
log.Warn("警告: 该操作会删除 cqhttp.json 并覆盖 config.json 文件.")
|
||||
@ -99,7 +111,8 @@ func main() {
|
||||
Host: "0.0.0.0",
|
||||
Port: 6700,
|
||||
},
|
||||
Debug: os.Getenv("DEBUG") == "true",
|
||||
PostMessageFormat: "string",
|
||||
Debug: os.Getenv("DEBUG") == "true",
|
||||
}
|
||||
if post != "" {
|
||||
conf.HttpConfig.PostUrls[post] = os.Getenv("HTTP_SECRET")
|
||||
@ -127,7 +140,7 @@ func main() {
|
||||
if !global.PathExists("device.json") {
|
||||
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
|
||||
client.GenRandomDevice()
|
||||
_ = ioutil.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0777)
|
||||
_ = ioutil.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0644)
|
||||
log.Info("已生成设备信息并保存到 device.json 文件.")
|
||||
} else {
|
||||
log.Info("将使用 device.json 内的设备信息运行Bot.")
|
||||
@ -163,9 +176,10 @@ func main() {
|
||||
if !rsp.Success {
|
||||
switch rsp.Error {
|
||||
case client.NeedCaptcha:
|
||||
_ = ioutil.WriteFile("captcha.jpg", rsp.CaptchaImage, 0644)
|
||||
img, _, _ := image.Decode(bytes.NewReader(rsp.CaptchaImage))
|
||||
fmt.Println(asciiart.New("image", img).Art)
|
||||
log.Warn("请输入验证码: (Enter 提交)")
|
||||
log.Warn("请输入验证码 (captcha.jpg): (Enter 提交)")
|
||||
text, _ := console.ReadString('\n')
|
||||
rsp, err = cli.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), rsp.CaptchaSign)
|
||||
continue
|
||||
@ -186,13 +200,19 @@ func main() {
|
||||
global.Check(cli.ReloadFriendList())
|
||||
log.Infof("共加载 %v 个好友.", len(cli.FriendList))
|
||||
log.Infof("开始加载群列表...")
|
||||
global.Check(cli.ReloadGroupList())
|
||||
global.Check(cli.ReloadGroupList(conf.AsyncLoad))
|
||||
log.Infof("共加载 %v 个群.", len(cli.GroupList))
|
||||
b := coolq.NewQQBot(cli, conf)
|
||||
if conf.PostMessageFormat != "string" && conf.PostMessageFormat != "array" {
|
||||
log.Warnf("post_message_format 配置错误, 将自动使用 string")
|
||||
coolq.SetMessageFormat("string")
|
||||
} else {
|
||||
coolq.SetMessageFormat(conf.PostMessageFormat)
|
||||
}
|
||||
if conf.HttpConfig != nil && conf.HttpConfig.Enabled {
|
||||
server.HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, b)
|
||||
for k, v := range conf.HttpConfig.PostUrls {
|
||||
server.NewHttpClient().Run(k, v, b)
|
||||
server.NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, b)
|
||||
}
|
||||
}
|
||||
if conf.WSConfig != nil && conf.WSConfig.Enabled {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/coolq"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/guonaihong/gout"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -21,9 +22,10 @@ type httpServer struct {
|
||||
}
|
||||
|
||||
type httpClient struct {
|
||||
bot *coolq.CQBot
|
||||
secret string
|
||||
addr string
|
||||
bot *coolq.CQBot
|
||||
secret string
|
||||
addr string
|
||||
timeout int32
|
||||
}
|
||||
|
||||
var HttpServer = &httpServer{}
|
||||
@ -132,12 +134,12 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) {
|
||||
s.engine.Any("/set_group_leave_async", s.SetGroupLeave)
|
||||
|
||||
s.engine.Any("/get_image", s.GetImage)
|
||||
s.engine.Any("/get_image_async", s.GetImage)
|
||||
|
||||
s.engine.Any("/get_forward_msg", s.GetForwardMessage)
|
||||
|
||||
s.engine.Any("/get_group_msg", s.GetGroupMessage)
|
||||
s.engine.Any("/get_group_msg_async", s.GetGroupMessage)
|
||||
|
||||
s.engine.Any("/get_group_honor_info", s.GetGroupHonorInfo)
|
||||
|
||||
s.engine.Any("/can_send_image", s.CanSendImage)
|
||||
s.engine.Any("/can_send_image_async", s.CanSendImage)
|
||||
@ -163,10 +165,14 @@ func NewHttpClient() *httpClient {
|
||||
return &httpClient{}
|
||||
}
|
||||
|
||||
func (c *httpClient) Run(addr, secret string, bot *coolq.CQBot) {
|
||||
func (c *httpClient) Run(addr, secret string, timeout int32, bot *coolq.CQBot) {
|
||||
c.bot = bot
|
||||
c.secret = secret
|
||||
c.addr = addr
|
||||
c.timeout = timeout
|
||||
if c.timeout < 5 {
|
||||
c.timeout = 5
|
||||
}
|
||||
bot.OnEventPush(c.onBotPushEvent)
|
||||
log.Infof("HTTP POST上报器已启动: %v", addr)
|
||||
}
|
||||
@ -184,7 +190,7 @@ func (c *httpClient) onBotPushEvent(m coolq.MSG) {
|
||||
h["X-Signature"] = "sha1=" + hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
return h
|
||||
}()).SetTimeout(time.Second * 5).Do()
|
||||
}()).SetTimeout(time.Second * time.Duration(c.timeout)).Do()
|
||||
if err != nil {
|
||||
log.Warnf("上报Event数据到 %v 失败: %v", c.addr, err)
|
||||
return
|
||||
@ -203,7 +209,8 @@ func (s *httpServer) GetFriendList(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *httpServer) GetGroupList(c *gin.Context) {
|
||||
c.JSON(200, s.bot.CQGetGroupList())
|
||||
nc := getParamOrDefault(c, "no_cache", "false")
|
||||
c.JSON(200, s.bot.CQGetGroupList(nc == "true"))
|
||||
}
|
||||
|
||||
func (s *httpServer) GetGroupInfo(c *gin.Context) {
|
||||
@ -224,6 +231,14 @@ func (s *httpServer) GetGroupMemberInfo(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *httpServer) SendMessage(c *gin.Context) {
|
||||
if getParam(c, "message_type") == "private" {
|
||||
s.SendPrivateMessage(c)
|
||||
return
|
||||
}
|
||||
if getParam(c, "message_type") == "group" {
|
||||
s.SendGroupMessage(c)
|
||||
return
|
||||
}
|
||||
if getParam(c, "group_id") != "" {
|
||||
s.SendGroupMessage(c)
|
||||
return
|
||||
@ -235,22 +250,24 @@ func (s *httpServer) SendMessage(c *gin.Context) {
|
||||
|
||||
func (s *httpServer) SendPrivateMessage(c *gin.Context) {
|
||||
uid, _ := strconv.ParseInt(getParam(c, "user_id"), 10, 64)
|
||||
msg := getParam(c, "message")
|
||||
if gjson.Valid(msg) {
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg)))
|
||||
msg, t := getParamWithType(c, "message")
|
||||
autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
|
||||
if t == gjson.JSON {
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg), autoEscape))
|
||||
return
|
||||
}
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, msg))
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, msg, autoEscape))
|
||||
}
|
||||
|
||||
func (s *httpServer) SendGroupMessage(c *gin.Context) {
|
||||
gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64)
|
||||
msg := getParam(c, "message")
|
||||
if gjson.Valid(msg) {
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg)))
|
||||
msg, t := getParamWithType(c, "message")
|
||||
autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
|
||||
if t == gjson.JSON {
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg), autoEscape))
|
||||
return
|
||||
}
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, msg))
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, msg, autoEscape))
|
||||
}
|
||||
|
||||
func (s *httpServer) SendGroupForwardMessage(c *gin.Context) {
|
||||
@ -269,6 +286,11 @@ func (s *httpServer) GetGroupMessage(c *gin.Context) {
|
||||
c.JSON(200, s.bot.CQGetGroupMessage(int32(mid)))
|
||||
}
|
||||
|
||||
func (s *httpServer) GetGroupHonorInfo(c *gin.Context) {
|
||||
gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64)
|
||||
c.JSON(200, s.bot.CQGetGroupHonorInfo(gid, getParam(c, "type")))
|
||||
}
|
||||
|
||||
func (s *httpServer) ProcessFriendRequest(c *gin.Context) {
|
||||
flag := getParam(c, "flag")
|
||||
approve := getParamOrDefault(c, "approve", "true")
|
||||
@ -372,14 +394,19 @@ func getParamOrDefault(c *gin.Context, k, def string) string {
|
||||
}
|
||||
|
||||
func getParam(c *gin.Context, k string) string {
|
||||
p, _ := getParamWithType(c, k)
|
||||
return p
|
||||
}
|
||||
|
||||
func getParamWithType(c *gin.Context, k string) (string, gjson.Type) {
|
||||
if q := c.Query(k); q != "" {
|
||||
return q
|
||||
return q, gjson.Null
|
||||
}
|
||||
if c.Request.Method == "POST" {
|
||||
if h := c.Request.Header.Get("Content-Type"); h != "" {
|
||||
if h == "application/x-www-form-urlencoded" {
|
||||
if p, ok := c.GetPostForm(k); ok {
|
||||
return p
|
||||
return p, gjson.Null
|
||||
}
|
||||
}
|
||||
if h == "application/json" {
|
||||
@ -388,20 +415,20 @@ func getParam(c *gin.Context, k string) string {
|
||||
if res.Exists() {
|
||||
switch res.Type {
|
||||
case gjson.JSON:
|
||||
return res.Raw
|
||||
return res.Raw, gjson.JSON
|
||||
case gjson.String:
|
||||
return res.Str
|
||||
return res.Str, gjson.String
|
||||
case gjson.Number:
|
||||
return strconv.FormatInt(res.Int(), 10) // 似乎没有需要接受 float 类型的api
|
||||
return strconv.FormatInt(res.Int(), 10), gjson.Number // 似乎没有需要接受 float 类型的api
|
||||
case gjson.True:
|
||||
return "true"
|
||||
return "true", gjson.True
|
||||
case gjson.False:
|
||||
return "false"
|
||||
return "false", gjson.False
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "", gjson.Null
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
wsc "golang.org/x/net/websocket"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -29,8 +28,8 @@ type websocketClient struct {
|
||||
bot *coolq.CQBot
|
||||
|
||||
pushLock *sync.Mutex
|
||||
universalConn *wsc.Conn
|
||||
eventConn *wsc.Conn
|
||||
universalConn *websocket.Conn
|
||||
eventConn *websocket.Conn
|
||||
}
|
||||
|
||||
var WebsocketServer = &websocketServer{}
|
||||
@ -79,18 +78,15 @@ func (c *websocketClient) Run() {
|
||||
|
||||
func (c *websocketClient) connectApi() {
|
||||
log.Infof("开始尝试连接到反向Websocket API服务器: %v", c.conf.ReverseApiUrl)
|
||||
wsConf, err := wsc.NewConfig(c.conf.ReverseApiUrl, c.conf.ReverseApiUrl)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket API服务器 %v 时出现致命错误: %v", c.conf.ReverseApiUrl, err)
|
||||
return
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{"API"},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
wsConf.Header["X-Client-Role"] = []string{"API"}
|
||||
wsConf.Header["X-Self-ID"] = []string{strconv.FormatInt(c.bot.Client.Uin, 10)}
|
||||
wsConf.Header["User-Agent"] = []string{"CQHttp/4.15.0"}
|
||||
if c.token != "" {
|
||||
wsConf.Header["Authorization"] = []string{"Token " + c.token}
|
||||
header["Authorization"] = []string{"Token " + c.token}
|
||||
}
|
||||
conn, err := wsc.DialConfig(wsConf)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseApiUrl, header)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket API服务器 %v 时出现错误: %v", c.conf.ReverseApiUrl, err)
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
@ -105,23 +101,20 @@ func (c *websocketClient) connectApi() {
|
||||
|
||||
func (c *websocketClient) connectEvent() {
|
||||
log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl)
|
||||
wsConf, err := wsc.NewConfig(c.conf.ReverseEventUrl, c.conf.ReverseEventUrl)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket Event服务器 %v 时出现致命错误: %v", c.conf.ReverseApiUrl, err)
|
||||
return
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{"Event"},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
wsConf.Header["X-Client-Role"] = []string{"Event"}
|
||||
wsConf.Header["X-Self-ID"] = []string{strconv.FormatInt(c.bot.Client.Uin, 10)}
|
||||
wsConf.Header["User-Agent"] = []string{"CQHttp/4.15.0"}
|
||||
if c.token != "" {
|
||||
wsConf.Header["Authorization"] = []string{"Token " + c.token}
|
||||
header["Authorization"] = []string{"Token " + c.token}
|
||||
}
|
||||
conn, err := wsc.DialConfig(wsConf)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseEventUrl, header)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket API服务器 %v 时出现错误: %v", c.conf.ReverseApiUrl, err)
|
||||
log.Warnf("连接到反向Websocket Event服务器 %v 时出现错误: %v", c.conf.ReverseEventUrl, err)
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
c.connectApi()
|
||||
c.connectEvent()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -131,19 +124,15 @@ func (c *websocketClient) connectEvent() {
|
||||
|
||||
func (c *websocketClient) connectUniversal() {
|
||||
log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl)
|
||||
wsConf, err := wsc.NewConfig(c.conf.ReverseUrl, c.conf.ReverseUrl)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket Universal服务器 %v 时出现致命错误: %v", c.conf.ReverseUrl, err)
|
||||
return
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{"Universal"},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
wsConf.Dialer.Timeout = time.Second * 5
|
||||
wsConf.Header["X-Client-Role"] = []string{"Universal"}
|
||||
wsConf.Header["X-Self-ID"] = []string{strconv.FormatInt(c.bot.Client.Uin, 10)}
|
||||
wsConf.Header["User-Agent"] = []string{"CQHttp/4.15.0"}
|
||||
if c.token != "" {
|
||||
wsConf.Header["Authorization"] = []string{"Token " + c.token}
|
||||
header["Authorization"] = []string{"Token " + c.token}
|
||||
}
|
||||
conn, err := wsc.DialConfig(wsConf)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseUrl, header)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket Universal服务器 %v 时出现错误: %v", c.conf.ReverseUrl, err)
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
@ -156,12 +145,12 @@ func (c *websocketClient) connectUniversal() {
|
||||
c.universalConn = conn
|
||||
}
|
||||
|
||||
func (c *websocketClient) listenApi(conn *wsc.Conn, u bool) {
|
||||
func (c *websocketClient) listenApi(conn *websocket.Conn, u bool) {
|
||||
defer conn.Close()
|
||||
for {
|
||||
var buf []byte
|
||||
err := wsc.Message.Receive(conn, &buf)
|
||||
_, buf, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Warnf("监听反向WS API时出现错误: %v", err)
|
||||
break
|
||||
}
|
||||
j := gjson.ParseBytes(buf)
|
||||
@ -173,17 +162,16 @@ func (c *websocketClient) listenApi(conn *wsc.Conn, u bool) {
|
||||
ret["echo"] = j.Get("echo").Value()
|
||||
}
|
||||
c.pushLock.Lock()
|
||||
_, _ = conn.Write([]byte(ret.ToJson()))
|
||||
log.Debugf("准备发送API %v 处理结果: %v", t, ret.ToJson())
|
||||
_ = conn.WriteJSON(ret)
|
||||
c.pushLock.Unlock()
|
||||
}
|
||||
}
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
if u {
|
||||
c.connectUniversal()
|
||||
return
|
||||
if !u {
|
||||
c.connectApi()
|
||||
}
|
||||
c.connectApi()
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,8 +180,8 @@ func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
|
||||
defer c.pushLock.Unlock()
|
||||
if c.eventConn != nil {
|
||||
log.Debugf("向WS服务器 %v 推送Event: %v", c.eventConn.RemoteAddr().String(), m.ToJson())
|
||||
_ = c.eventConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
||||
if _, err := c.eventConn.Write([]byte(m.ToJson())); err != nil {
|
||||
if err := c.eventConn.WriteJSON(m); err != nil {
|
||||
log.Warnf("向WS服务器 %v 推送Event时出现错误: %v", c.eventConn.RemoteAddr().String(), err)
|
||||
_ = c.eventConn.Close()
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
go func() {
|
||||
@ -205,8 +193,16 @@ func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
|
||||
}
|
||||
if c.universalConn != nil {
|
||||
log.Debugf("向WS服务器 %v 推送Event: %v", c.universalConn.RemoteAddr().String(), m.ToJson())
|
||||
_ = c.universalConn.SetWriteDeadline(time.Now().Add(time.Second * 3))
|
||||
_, _ = c.universalConn.Write([]byte(m.ToJson()))
|
||||
if err := c.universalConn.WriteJSON(m); err != nil {
|
||||
log.Warnf("向WS服务器 %v 推送Event时出现错误: %v", c.universalConn.RemoteAddr().String(), err)
|
||||
_ = c.universalConn.Close()
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
c.connectUniversal()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,7 +314,7 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
return bot.CQGetFriendList()
|
||||
},
|
||||
"get_group_list": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupList()
|
||||
return bot.CQGetGroupList(p.Get("no_cache").Bool())
|
||||
},
|
||||
"get_group_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupInfo(p.Get("group_id").Int())
|
||||
@ -333,22 +329,29 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
)
|
||||
},
|
||||
"send_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
autoEscape := global.EnsureBool(p.Get("auto_escape"), false)
|
||||
if p.Get("message_type").Str == "private" {
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
if p.Get("message_type").Str == "group" {
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
if p.Get("group_id").Int() != 0 {
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"))
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
if p.Get("user_id").Int() != 0 {
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"))
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
return coolq.MSG{}
|
||||
},
|
||||
"send_group_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"))
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"), global.EnsureBool(p.Get("auto_escape"), false))
|
||||
},
|
||||
"send_group_forward_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQSendGroupForwardMessage(p.Get("group_id").Int(), p.Get("messages"))
|
||||
},
|
||||
"send_private_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"))
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"), global.EnsureBool(p.Get("auto_escape"), false))
|
||||
},
|
||||
"delete_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQDeleteMessage(int32(p.Get("message_id").Int()))
|
||||
@ -411,6 +414,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
"get_group_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupMessage(int32(p.Get("message_id").Int()))
|
||||
},
|
||||
"get_group_honor_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupHonorInfo(p.Get("group_id").Int(), p.Get("type").Str)
|
||||
},
|
||||
"can_send_image": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQCanSendImage()
|
||||
},
|
||||
|
Reference in New Issue
Block a user