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

Compare commits

...

56 Commits

Author SHA1 Message Date
470efa07ab url cache supported. 2020-08-20 03:05:46 +08:00
b7572f8d2c fix log. 2020-08-20 02:31:51 +08:00
c9a914b5d5 fix #141. 2020-08-20 02:30:44 +08:00
51696e8054 /get_group_list supported no_cache param. 2020-08-20 02:12:54 +08:00
a6bcd96415 fix event push error. 2020-08-20 01:59:31 +08:00
08694f5ae8 heartbeat event supported. close #131 2020-08-19 18:37:12 +08:00
8c71dbff68 honor api supported. 2020-08-19 16:04:30 +08:00
568b0e479f update MiraiGo. 2020-08-18 17:06:59 +08:00
d1da08a376 fix at. 2020-08-18 13:22:11 +08:00
5768c61bc7 Merge pull request #99 from mirai-qq/master
support silk audio
2020-08-17 14:15:11 +08:00
864120d3fe Merge pull request #127 from richardchien/auto_escape
support "auto_escape" in api and quick operation
2020-08-17 14:12:11 +08:00
99b414530a remove todo comment 2020-08-17 14:10:33 +08:00
4820eb2fec support "auto_escape" in api and quick operation 2020-08-17 13:46:11 +08:00
00d80d5dfc fix #107. 2020-08-16 19:35:38 +08:00
22d9ddb4ea try to fix #115. 2020-08-16 19:31:40 +08:00
fa33dbdd4e Merge branch 'master' of https://github.com/Mrs4s/go-cqhttp 2020-08-16 19:28:34 +08:00
be8b68c8e9 update MiraiGo & async loading supported. close #111 2020-08-16 19:27:43 +08:00
cef129eff7 Merge pull request #109 from SJTU-Plus/master
fix #103
2020-08-16 02:26:42 +08:00
1fac06f58a fix #103 2020-08-15 23:47:29 +08:00
1a1f860dbe fix #108. 2020-08-15 16:07:56 +08:00
f325b26e1a doc update. close #102 2020-08-14 13:41:48 +08:00
b545e05a9d Merge branch 'master' of https://github.com/Mrs4s/go-cqhttp 2020-08-14 08:28:55 +08:00
b290a0a596 try to fix group image upload failed. #65 2020-08-14 08:28:37 +08:00
9af61a336b support silk audio 2020-08-13 23:36:16 +08:00
774a1e32da Merge pull request #96 from mirai-qq/master
remove os.ModePerm
2020-08-13 21:24:15 +08:00
50eee15a67 remove os.ModePerm 2020-08-13 20:53:42 +08:00
edf6180e1c fix ci failed. 2020-08-13 20:09:57 +08:00
3c04573e82 fix #89. 2020-08-13 20:07:21 +08:00
5e49820319 update README.md 2020-08-13 17:21:54 +08:00
1418e36bab cq code 'video' supported. 2020-08-13 17:18:16 +08:00
611d16d79e update README.md 2020-08-13 11:14:21 +08:00
c6f701e8c8 Merge pull request #84 from Shigma/master
adjust unescape order
2020-08-12 12:32:06 +08:00
a49a57b964 adjust unescape order 2020-08-12 12:23:48 +08:00
bb03315930 try to support escape again. #9 2020-08-12 09:52:32 +08:00
5683db6324 fix #80. 2020-08-12 09:33:25 +08:00
f1ffa17d20 update MiraiGo. 2020-08-12 09:19:00 +08:00
69675aa0f9 fix #75. 2020-08-12 05:01:16 +08:00
782a3e2a39 add default 'http_config.post_message_format' value. 2020-08-11 17:47:16 +08:00
1967e687af fix perm error. 2020-08-11 17:38:33 +08:00
23d436972c save captcha to local file. close #71 2020-08-11 17:35:03 +08:00
129622dd24 fix reverse push issue. 2020-08-11 17:32:09 +08:00
19104d00a3 Merge pull request #78 from yyuueexxiinngg/patch-1
移除上报消息为array时的文本转义
2020-08-11 15:32:45 +08:00
6c0e78b60c Remove content escape when using array post message format. 2020-08-11 15:22:26 +08:00
a075f41a51 http post timeout setting supported. close #77 2020-08-11 15:19:54 +08:00
cdbf903361 Merge pull request #74 from Shigma/api-type-check
sendXXX api type check
2020-08-11 11:20:17 +08:00
906247fcf0 Merge pull request #70 from XYenon/master
Add support for post_message_format: array
2020-08-11 04:54:37 +08:00
72a7430841 add api type check 2020-08-10 20:43:31 +08:00
5c4f586c36 Use string format in db 2020-08-10 19:44:54 +08:00
5d2e202b0c Merge pull request #47 from purerosefallen/patch-1
Add Dockerfile
2020-08-10 16:57:34 +08:00
c27ebadbc4 Add support for post_message_format: array 2020-08-10 13:59:46 +08:00
d5a8f3ead2 Merge pull request #69 from Shigma/master
& escape should come first
2020-08-10 12:51:26 +08:00
03bfd9dd3d & escape should come first 2020-08-10 12:48:50 +08:00
0b02178402 supported voice download. 2020-08-10 12:38:58 +08:00
3819f2c2da fix #66 2020-08-10 10:31:50 +08:00
fdd517ee8c dockerignore 2020-08-07 14:36:00 +08:00
2d010326c7 Add a Dockerfile and more configurable environment variables 2020-08-07 14:34:47 +08:00
16 changed files with 549 additions and 137 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.gitlab-ci.yml
.dockerignore
Dockerfile
README.md
LICENSE

21
Dockerfile Normal file
View 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" ]

View File

@ -28,6 +28,7 @@
- [CQ:image] - [CQ:image]
- [CQ:record] - [CQ:record]
- [CQ:video]
- [CQ:face] - [CQ:face]
- [CQ:at] - [CQ:at]
- [CQ:share] - [CQ:share]
@ -91,6 +92,16 @@
</details> </details>
# 关于ISSUE
以下ISSUE会被直接关闭
- 提交BUG不使用Template
- 询问已知问题
- 提问找不到重点
- 重复提问
> 请注意, 开发者并没有义务回复您的问题. 您应该具备基本的提问技巧。
# 性能 # 性能
在关闭数据库的情况下, 加载25个好友128个群运行24小时后内存使用为10MB左右. 开启数据库后内存使用将根据消息量增加10-20MB, 如果系统内存小于128M建议关闭数据库使用. 在关闭数据库的情况下, 加载25个好友128个群运行24小时后内存使用为10MB左右. 开启数据库后内存使用将根据消息量增加10-20MB, 如果系统内存小于128M建议关闭数据库使用.

View File

@ -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 // 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 var gs []MSG
if noCache {
_ = bot.Client.ReloadGroupList()
}
for _, g := range bot.Client.GroupList { for _, g := range bot.Client.GroupList {
gs = append(gs, MSG{ gs = append(gs, MSG{
"group_id": g.Code, "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 // 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 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, ok := i.(gjson.Result); ok {
if m.Type == gjson.JSON { if m.Type == gjson.JSON {
elem := bot.ConvertObjectMessage(m, true) elem := bot.ConvertObjectMessage(m, true)
fixAt(elem)
mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem}) mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem})
if mid == -1 { if mid == -1 {
return Failed(100) return Failed(100)
@ -113,20 +130,19 @@ func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}) MSG {
} }
return m.Raw return m.Raw
}() }()
} } else if s, ok := i.(string); ok {
if s, ok := i.(string); ok {
str = s str = s
} }
if str == "" { if str == "" {
return Failed(100) return Failed(100)
} }
elem := bot.ConvertStringMessage(str, true) var elem []message.IMessageElement
// fix at display if autoEscape {
for _, e := range elem { elem = append(elem, message.NewText(str))
if at, ok := e.(*message.AtElement); ok && at.Target != 0 { } else {
at.Display = "@" + bot.Client.FindGroup(groupId).FindMember(at.Target).DisplayName() elem = bot.ConvertStringMessage(str, true)
}
} }
fixAt(elem)
mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem}) mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem})
if mid == -1 { if mid == -1 {
return Failed(100) 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 // 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 var str string
if m, ok := i.(gjson.Result); ok { if m, ok := i.(gjson.Result); ok {
if m.Type == gjson.JSON { if m.Type == gjson.JSON {
@ -222,14 +238,18 @@ func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}) MSG {
} }
return m.Raw return m.Raw
}() }()
} } else if s, ok := i.(string); ok {
if s, ok := i.(string); ok {
str = s str = s
} }
if str == "" { if str == "" {
return Failed(100) 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}) mid := bot.SendPrivateMessage(userId, &message.SendingMessage{Elements: elem})
if mid == -1 { if mid == -1 {
return Failed(100) return Failed(100)
@ -359,6 +379,61 @@ func (bot *CQBot) CQDeleteMessage(messageId int32) MSG {
return OK(nil) 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://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 // 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 { 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 msgType := context.Get("message_type").Str
reply := operation.Get("reply") reply := operation.Get("reply")
if reply.Exists() { if reply.Exists() {
autoEscape := global.EnsureBool(operation.Get("auto_escape"), false)
/* /*
at := true at := true
if operation.Get("at_sender").Exists() { if operation.Get("at_sender").Exists() {
@ -376,10 +452,10 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
*/ */
// TODO: 处理at字段 // TODO: 处理at字段
if msgType == "group" { if msgType == "group" {
bot.CQSendGroupMessage(context.Get("group_id").Int(), reply) bot.CQSendGroupMessage(context.Get("group_id").Int(), reply, autoEscape)
} }
if msgType == "private" { if msgType == "private" {
bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply) bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply, autoEscape)
} }
} }
if msgType == "group" { if msgType == "group" {
@ -404,12 +480,12 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
} }
case "request": case "request":
reqType := context.Get("request_type").Str reqType := context.Get("request_type").Str
if context.Get("approve").Bool() { if operation.Get("approve").Exists() {
if reqType == "friend" { if reqType == "friend" {
bot.CQProcessFriendRequest(context.Get("flag").Str, true) bot.CQProcessFriendRequest(context.Get("flag").Str, operation.Get("approve").Bool())
} }
if reqType == "group" { 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 var r []MSG
for _, n := range m.Nodes { for _, n := range m.Nodes {
checkImage(n.Message) bot.checkMedia(n.Message)
r = append(r, MSG{ r = append(r, MSG{
"sender": MSG{ "sender": MSG{
"user_id": n.SenderId, "user_id": n.SenderId,
"nickname": n.SenderName, "nickname": n.SenderName,
}, },
"time": n.Time, "time": n.Time,
"content": ToStringMessage(n.Message, 0, false), "content": ToFormattedMessage(n.Message, 0, false),
}) })
} }
return OK(MSG{ return OK(MSG{

View File

@ -12,6 +12,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/xujiajun/nutsdb" "github.com/xujiajun/nutsdb"
"hash/crc32" "hash/crc32"
"math/rand"
"path" "path"
"sync" "sync"
"time" "time"
@ -63,6 +64,19 @@ 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)
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 return bot
} }
@ -85,7 +99,7 @@ func (bot *CQBot) GetGroupMessage(mid int32) MSG {
if err == nil { if err == nil {
return m return m
} }
log.Warnf("获取信息时出现错误: %v", err) log.Warnf("获取信息时出现错误: %v id: %v", err, mid)
} }
return nil return nil
} }
@ -94,6 +108,7 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
var newElem []message.IMessageElement var newElem []message.IMessageElement
for _, elem := range m.Elements { for _, elem := range m.Elements {
if i, ok := elem.(*message.ImageElement); ok { if i, ok := elem.(*message.ImageElement); ok {
_, _ = bot.Client.UploadGroupImage(int64(rand.Intn(11451419)), i.Data)
gm, err := bot.Client.UploadGroupImage(groupId, i.Data) gm, err := bot.Client.UploadGroupImage(groupId, i.Data)
if err != nil { if err != nil {
log.Warnf("警告: 群 %v 消息图片上传失败: %v", groupId, err) log.Warnf("警告: 群 %v 消息图片上传失败: %v", groupId, err)
@ -115,6 +130,9 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
} }
m.Elements = newElem m.Elements = newElem
ret := bot.Client.SendGroupMessage(groupId, m) ret := bot.Client.SendGroupMessage(groupId, m)
if ret == nil || ret.Id == -1 {
return -1
}
return bot.InsertGroupMessage(ret) return bot.InsertGroupMessage(ret)
} }
@ -191,7 +209,7 @@ func (bot *CQBot) dispatchEventMessage(m MSG) {
fn(m) fn(m)
end := time.Now() end := time.Now()
if end.Sub(start) > time.Second*5 { if end.Sub(start) > time.Second*5 {
log.Debugf("警告: 事件处理耗时超过 5 秒 (%v), 请检查应用是否有堵塞.", end.Sub(start)/time.Second) log.Debugf("警告: 事件处理耗时超过 5 秒 (%v), 请检查应用是否有堵塞.", end.Sub(start))
} }
}() }()
} }

View File

@ -1,6 +1,7 @@
package coolq package coolq
import ( import (
"crypto/md5"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
@ -23,6 +24,88 @@ var matchReg = regexp.MustCompile(`\[CQ:\w+?.*?]`)
var typeReg = regexp.MustCompile(`\[CQ:(\w+)`) var typeReg = regexp.MustCompile(`\[CQ:(\w+)`)
var paramReg = regexp.MustCompile(`,([\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) { func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r string) {
ur := false ur := false
if len(raw) != 0 { if len(raw) != 0 {
@ -45,12 +128,22 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
case *message.FaceElement: case *message.FaceElement:
r += fmt.Sprintf(`[CQ:face,id=%d]`, o.Index) r += fmt.Sprintf(`[CQ:face,id=%d]`, o.Index)
case *message.VoiceElement: case *message.VoiceElement:
if ur {
r += fmt.Sprintf(`[CQ:record,file=%s]`, o.Name) 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: case *message.ImageElement:
if ur { if ur {
r += fmt.Sprintf(`[CQ:image,file=%s]`, o.Filename) r += fmt.Sprintf(`[CQ:image,file=%s]`, o.Filename)
} else { } 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) ps := paramReg.FindAllStringSubmatch(code, -1)
d := make(map[string]string) d := make(map[string]string)
for _, p := range ps { for _, p := range ps {
d[p[1]] = p[2] d[p[1]] = CQCodeUnescapeValue(p[2])
} }
if t == "reply" && group { if t == "reply" && group {
if len(r) > 0 { if len(r) > 0 {
@ -169,10 +262,23 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
case "image": case "image":
f := d["file"] f := d["file"]
if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") { if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") {
cache := d["cache"]
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) b, err := global.GetBytes(f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_ = ioutil.WriteFile(cacheFile, b, 0644)
return message.NewImage(b), nil return message.NewImage(b), nil
} }
if strings.HasPrefix(f, "base64") { if strings.HasPrefix(f, "base64") {
@ -286,7 +392,14 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
} }
data = b 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 nil, errors.New("unsupported voice file format (please use AMR file for now)")
} }
return &message.VoiceElement{Data: data}, nil 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 { func CQCodeEscapeText(raw string) string {
ret := raw ret := raw
ret = strings.ReplaceAll(ret, "&", "&amp;")
ret = strings.ReplaceAll(ret, "[", "&#91;") ret = strings.ReplaceAll(ret, "[", "&#91;")
ret = strings.ReplaceAll(ret, "]", "&#93;") ret = strings.ReplaceAll(ret, "]", "&#93;")
ret = strings.ReplaceAll(ret, "&", "&amp;") return ret
}
func CQCodeEscapeValue(value string) string {
ret := CQCodeEscapeText(value)
ret = strings.ReplaceAll(ret, ",", "&#44;")
return ret return ret
} }
@ -325,3 +444,9 @@ func CQCodeUnescapeText(content string) string {
ret = strings.ReplaceAll(ret, "&amp;", "&") ret = strings.ReplaceAll(ret, "&amp;", "&")
return ret return ret
} }
func CQCodeUnescapeValue(content string) string {
ret := strings.ReplaceAll(content, "&#44;", ",")
ret = CQCodeUnescapeText(ret)
return ret
}

View File

@ -10,11 +10,27 @@ import (
"io/ioutil" "io/ioutil"
"path" "path"
"strconv" "strconv"
"strings"
"time" "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) { func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMessage) {
checkImage(m.Elements) bot.checkMedia(m.Elements)
cqm := ToStringMessage(m.Elements, 0, true) cqm := ToStringMessage(m.Elements, 0, true)
log.Infof("收到好友 %v(%v) 的消息: %v", m.Sender.DisplayName(), m.Sender.Uin, cqm) log.Infof("收到好友 %v(%v) 的消息: %v", m.Sender.DisplayName(), m.Sender.Uin, cqm)
fm := MSG{ fm := MSG{
@ -23,7 +39,7 @@ func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMess
"sub_type": "friend", "sub_type": "friend",
"message_id": ToGlobalId(m.Sender.Uin, m.Id), "message_id": ToGlobalId(m.Sender.Uin, m.Id),
"user_id": m.Sender.Uin, "user_id": m.Sender.Uin,
"message": ToStringMessage(m.Elements, 0, false), "message": ToFormattedMessage(m.Elements, 0, false),
"raw_message": cqm, "raw_message": cqm,
"font": 0, "font": 0,
"self_id": c.Uin, "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) { func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage) {
checkImage(m.Elements) bot.checkMedia(m.Elements)
for _, elem := range m.Elements { for _, elem := range m.Elements {
if file, ok := elem.(*message.GroupFileElement); ok { 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) 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, "anonymous": nil,
"font": 0, "font": 0,
"group_id": m.GroupCode, "group_id": m.GroupCode,
"message": ToStringMessage(m.Elements, m.GroupCode, false), "message": ToFormattedMessage(m.Elements, m.GroupCode, false),
"message_id": id, "message_id": id,
"message_type": "group", "message_type": "group",
"post_type": "message", "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) { func (bot *CQBot) tempMessageEvent(c *client.QQClient, m *message.TempMessage) {
checkImage(m.Elements) bot.checkMedia(m.Elements)
cqm := ToStringMessage(m.Elements, 0, true) cqm := ToStringMessage(m.Elements, 0, true)
bot.tempMsgCache.Store(m.Sender.Uin, m.GroupCode) 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) 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", "sub_type": "group",
"message_id": m.Id, "message_id": m.Id,
"user_id": m.Sender.Uin, "user_id": m.Sender.Uin,
"message": ToStringMessage(m.Elements, 0, false), "message": ToFormattedMessage(m.Elements, 0, false),
"raw_message": cqm, "raw_message": cqm,
"font": 0, "font": 0,
"self_id": c.Uin, "self_id": c.Uin,
@ -300,7 +316,7 @@ func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupR
"sub_type": "add", "sub_type": "add",
"group_id": e.GroupCode, "group_id": e.GroupCode,
"user_id": e.RequesterUin, "user_id": e.RequesterUin,
"comment": "", "comment": e.Message,
"flag": flag, "flag": flag,
"time": time.Now().Unix(), "time": time.Now().Unix(),
"self_id": c.Uin, "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 { for _, elem := range e {
if i, ok := elem.(*message.ImageElement); ok { switch i := elem.(type) {
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.IMAGE_PATH, filename)) {
_ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { _ = 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.WriteUInt32(uint32(i.Size))
w.WriteString(i.Filename) w.WriteString(i.Filename)
w.WriteString(i.Url) w.WriteString(i.Url)
}), 0777) }), 0644)
} }
i.Filename = filename 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)
} }
} }
} }

View File

@ -24,11 +24,13 @@ go-cqhttp 支持导入CQHTTP的配置文件, 具体步骤为:
"access_token": "", "access_token": "",
"relogin": false, "relogin": false,
"relogin_delay": 0, "relogin_delay": 0,
"post_message_format": "string",
"http_config": { "http_config": {
"enabled": true, "enabled": true,
"host": "0.0.0.0", "host": "0.0.0.0",
"port": 5700, "port": 5700,
"post_urls": {"url:port": "secret"} "timeout": 5,
"post_urls": {"url:port": "secret"},
}, },
"ws_config": { "ws_config": {
"enabled": true, "enabled": true,

View File

@ -14,9 +14,11 @@ type JsonConfig struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
ReLogin bool `json:"relogin"` ReLogin bool `json:"relogin"`
ReLoginDelay int `json:"relogin_delay"` ReLoginDelay int `json:"relogin_delay"`
AsyncLoad bool `json:"async_load"`
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"`
Debug bool `json:"debug"` Debug bool `json:"debug"`
} }
@ -43,6 +45,7 @@ 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"`
Timeout int32 `json:"timeout"`
PostUrls map[string]string `json:"post_urls"` PostUrls map[string]string `json:"post_urls"`
} }
@ -65,6 +68,7 @@ func DefaultConfig() *JsonConfig {
EnableDB: true, EnableDB: true,
ReLogin: true, ReLogin: true,
ReLoginDelay: 3, ReLoginDelay: 3,
PostMessageFormat: "string",
HttpConfig: &GoCQHttpConfig{ HttpConfig: &GoCQHttpConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",

View File

@ -1,13 +1,23 @@
package global package global
import ( import (
log "github.com/sirupsen/logrus" "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "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 { func PathExists(path string) bool {
_, err := os.Stat(path) _, err := os.Stat(path)
@ -23,7 +33,7 @@ func ReadAllText(path string) string {
} }
func WriteAllText(path, text string) { func WriteAllText(path, text string) {
_ = ioutil.WriteFile(path, []byte(text), 0777) _ = ioutil.WriteFile(path, []byte(text), 0644)
} }
func Check(err error) { func Check(err error) {
@ -32,9 +42,6 @@ func Check(err error) {
} }
} }
func IsAMR(b []byte) bool { func IsAMRorSILK(b []byte) bool {
if len(b) <= 6 { return bytes.HasPrefix(b, HEADER_AMR) || bytes.HasPrefix(b, HEADER_SILK)
return false
}
return b[0] == 0x23 && b[1] == 0x21 && b[2] == 0x41 && b[3] == 0x4D && b[4] == 0x52 // amr file header
} }

50
global/param.go Normal file
View 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
View File

@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp
go 1.14 go 1.14
require ( 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/gin-gonic/gin v1.6.3
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/guonaihong/gout v0.1.1 github.com/guonaihong/gout v0.1.1

4
go.sum
View File

@ -1,7 +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-20200809221224-7a84cfae6795 h1:Bu4k9ZS/IIy9Shwd9lS/C2P/2I8fYUwg1OpRF91hr1w= github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17 h1:sY4lwW34serGKnD26hP7ZLpnJ4NfVYKdS2SJIW1Mezs=
github.com/Mrs4s/MiraiGo v0.0.0-20200809221224-7a84cfae6795/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM= 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 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= 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= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

38
main.go
View File

@ -36,14 +36,26 @@ func init() {
if err == nil { if err == nil {
log.SetOutput(io.MultiWriter(os.Stderr, w)) log.SetOutput(io.MultiWriter(os.Stderr, w))
} }
if !global.PathExists("data") { if !global.PathExists(global.IMAGE_PATH) {
if err := os.Mkdir("data", 0777); err != nil { if err := os.MkdirAll(global.IMAGE_PATH, 0755); err != nil {
log.Fatalf("创建数据文件夹失败: %v", err)
}
if err := os.Mkdir(path.Join("data", "images"), 0777); err != nil {
log.Fatalf("创建图片缓存文件夹失败: %v", err) 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") { if global.PathExists("cqhttp.json") {
log.Info("发现 cqhttp.json 将在五秒后尝试导入配置,按 Ctrl+C 取消.") log.Info("发现 cqhttp.json 将在五秒后尝试导入配置,按 Ctrl+C 取消.")
log.Warn("警告: 该操作会删除 cqhttp.json 并覆盖 config.json 文件.") log.Warn("警告: 该操作会删除 cqhttp.json 并覆盖 config.json 文件.")
@ -99,6 +111,7 @@ func main() {
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 6700, Port: 6700,
}, },
PostMessageFormat: "string",
Debug: os.Getenv("DEBUG") == "true", Debug: os.Getenv("DEBUG") == "true",
} }
if post != "" { if post != "" {
@ -127,7 +140,7 @@ func main() {
if !global.PathExists("device.json") { if !global.PathExists("device.json") {
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.") log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
client.GenRandomDevice() client.GenRandomDevice()
_ = ioutil.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0777) _ = ioutil.WriteFile("device.json", client.SystemDeviceInfo.ToJson(), 0644)
log.Info("已生成设备信息并保存到 device.json 文件.") log.Info("已生成设备信息并保存到 device.json 文件.")
} else { } else {
log.Info("将使用 device.json 内的设备信息运行Bot.") log.Info("将使用 device.json 内的设备信息运行Bot.")
@ -163,9 +176,10 @@ func main() {
if !rsp.Success { if !rsp.Success {
switch rsp.Error { switch rsp.Error {
case client.NeedCaptcha: case client.NeedCaptcha:
_ = 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)
log.Warn("请输入验证码: (Enter 提交)") log.Warn("请输入验证码 (captcha.jpg) (Enter 提交)")
text, _ := console.ReadString('\n') text, _ := console.ReadString('\n')
rsp, err = cli.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), rsp.CaptchaSign) rsp, err = cli.SubmitCaptcha(strings.ReplaceAll(text, "\n", ""), rsp.CaptchaSign)
continue continue
@ -186,13 +200,19 @@ func main() {
global.Check(cli.ReloadFriendList()) global.Check(cli.ReloadFriendList())
log.Infof("共加载 %v 个好友.", len(cli.FriendList)) log.Infof("共加载 %v 个好友.", len(cli.FriendList))
log.Infof("开始加载群列表...") log.Infof("开始加载群列表...")
global.Check(cli.ReloadGroupList()) global.Check(cli.ReloadGroupList(conf.AsyncLoad))
log.Infof("共加载 %v 个群.", len(cli.GroupList)) log.Infof("共加载 %v 个群.", len(cli.GroupList))
b := coolq.NewQQBot(cli, conf) 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 { if conf.HttpConfig != nil && conf.HttpConfig.Enabled {
server.HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, b) server.HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, b)
for k, v := range conf.HttpConfig.PostUrls { 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 { if conf.WSConfig != nil && conf.WSConfig.Enabled {

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/guonaihong/gout" "github.com/guonaihong/gout"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -24,6 +25,7 @@ type httpClient struct {
bot *coolq.CQBot bot *coolq.CQBot
secret string secret string
addr string addr string
timeout int32
} }
var HttpServer = &httpServer{} 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("/set_group_leave_async", s.SetGroupLeave)
s.engine.Any("/get_image", s.GetImage) 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_forward_msg", s.GetForwardMessage)
s.engine.Any("/get_group_msg", s.GetGroupMessage) 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", s.CanSendImage)
s.engine.Any("/can_send_image_async", s.CanSendImage) s.engine.Any("/can_send_image_async", s.CanSendImage)
@ -163,10 +165,14 @@ func NewHttpClient() *httpClient {
return &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.bot = bot
c.secret = secret c.secret = secret
c.addr = addr c.addr = addr
c.timeout = timeout
if c.timeout < 5 {
c.timeout = 5
}
bot.OnEventPush(c.onBotPushEvent) bot.OnEventPush(c.onBotPushEvent)
log.Infof("HTTP POST上报器已启动: %v", addr) 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)) h["X-Signature"] = "sha1=" + hex.EncodeToString(mac.Sum(nil))
} }
return h return h
}()).SetTimeout(time.Second * 5).Do() }()).SetTimeout(time.Second * time.Duration(c.timeout)).Do()
if err != nil { if err != nil {
log.Warnf("上报Event数据到 %v 失败: %v", c.addr, err) log.Warnf("上报Event数据到 %v 失败: %v", c.addr, err)
return return
@ -203,7 +209,8 @@ func (s *httpServer) GetFriendList(c *gin.Context) {
} }
func (s *httpServer) GetGroupList(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) { 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) { 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") != "" { if getParam(c, "group_id") != "" {
s.SendGroupMessage(c) s.SendGroupMessage(c)
return return
@ -235,22 +250,24 @@ func (s *httpServer) SendMessage(c *gin.Context) {
func (s *httpServer) SendPrivateMessage(c *gin.Context) { func (s *httpServer) SendPrivateMessage(c *gin.Context) {
uid, _ := strconv.ParseInt(getParam(c, "user_id"), 10, 64) uid, _ := strconv.ParseInt(getParam(c, "user_id"), 10, 64)
msg := getParam(c, "message") msg, t := getParamWithType(c, "message")
if gjson.Valid(msg) { autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg))) if t == gjson.JSON {
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg), autoEscape))
return 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) { func (s *httpServer) SendGroupMessage(c *gin.Context) {
gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64) gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64)
msg := getParam(c, "message") msg, t := getParamWithType(c, "message")
if gjson.Valid(msg) { autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg))) if t == gjson.JSON {
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg), autoEscape))
return 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) { 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))) 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) { func (s *httpServer) ProcessFriendRequest(c *gin.Context) {
flag := getParam(c, "flag") flag := getParam(c, "flag")
approve := getParamOrDefault(c, "approve", "true") 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 { 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 != "" { if q := c.Query(k); q != "" {
return q return q, gjson.Null
} }
if c.Request.Method == "POST" { if c.Request.Method == "POST" {
if h := c.Request.Header.Get("Content-Type"); h != "" { if h := c.Request.Header.Get("Content-Type"); h != "" {
if h == "application/x-www-form-urlencoded" { if h == "application/x-www-form-urlencoded" {
if p, ok := c.GetPostForm(k); ok { if p, ok := c.GetPostForm(k); ok {
return p return p, gjson.Null
} }
} }
if h == "application/json" { if h == "application/json" {
@ -388,20 +415,20 @@ func getParam(c *gin.Context, k string) string {
if res.Exists() { if res.Exists() {
switch res.Type { switch res.Type {
case gjson.JSON: case gjson.JSON:
return res.Raw return res.Raw, gjson.JSON
case gjson.String: case gjson.String:
return res.Str return res.Str, gjson.String
case gjson.Number: case gjson.Number:
return strconv.FormatInt(res.Int(), 10) // 似乎没有需要接受 float 类型的api return strconv.FormatInt(res.Int(), 10), gjson.Number // 似乎没有需要接受 float 类型的api
case gjson.True: case gjson.True:
return "true" return "true", gjson.True
case gjson.False: case gjson.False:
return "false" return "false", gjson.False
} }
} }
} }
} }
} }
} }
return "" return "", gjson.Null
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
wsc "golang.org/x/net/websocket"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -29,8 +28,8 @@ type websocketClient struct {
bot *coolq.CQBot bot *coolq.CQBot
pushLock *sync.Mutex pushLock *sync.Mutex
universalConn *wsc.Conn universalConn *websocket.Conn
eventConn *wsc.Conn eventConn *websocket.Conn
} }
var WebsocketServer = &websocketServer{} var WebsocketServer = &websocketServer{}
@ -79,18 +78,15 @@ func (c *websocketClient) Run() {
func (c *websocketClient) connectApi() { func (c *websocketClient) connectApi() {
log.Infof("开始尝试连接到反向Websocket API服务器: %v", c.conf.ReverseApiUrl) log.Infof("开始尝试连接到反向Websocket API服务器: %v", c.conf.ReverseApiUrl)
wsConf, err := wsc.NewConfig(c.conf.ReverseApiUrl, c.conf.ReverseApiUrl) header := http.Header{
if err != nil { "X-Client-Role": []string{"API"},
log.Warnf("连接到反向Websocket API服务器 %v 时出现致命错误: %v", c.conf.ReverseApiUrl, err) "X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
return "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 != "" { 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 { 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 {
@ -105,23 +101,20 @@ func (c *websocketClient) connectApi() {
func (c *websocketClient) connectEvent() { func (c *websocketClient) connectEvent() {
log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl) log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl)
wsConf, err := wsc.NewConfig(c.conf.ReverseEventUrl, c.conf.ReverseEventUrl) header := http.Header{
if err != nil { "X-Client-Role": []string{"Event"},
log.Warnf("连接到反向Websocket Event服务器 %v 时出现致命错误: %v", c.conf.ReverseApiUrl, err) "X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
return "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 != "" { 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 { 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 { 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.connectEvent()
} }
return return
} }
@ -131,19 +124,15 @@ func (c *websocketClient) connectEvent() {
func (c *websocketClient) connectUniversal() { func (c *websocketClient) connectUniversal() {
log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl) log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl)
wsConf, err := wsc.NewConfig(c.conf.ReverseUrl, c.conf.ReverseUrl) header := http.Header{
if err != nil { "X-Client-Role": []string{"Universal"},
log.Warnf("连接到反向Websocket Universal服务器 %v 时出现致命错误: %v", c.conf.ReverseUrl, err) "X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
return "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 != "" { 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 { 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 {
@ -156,12 +145,12 @@ func (c *websocketClient) connectUniversal() {
c.universalConn = conn c.universalConn = conn
} }
func (c *websocketClient) listenApi(conn *wsc.Conn, u bool) { func (c *websocketClient) listenApi(conn *websocket.Conn, u bool) {
defer conn.Close() defer conn.Close()
for { for {
var buf []byte _, buf, err := conn.ReadMessage()
err := wsc.Message.Receive(conn, &buf)
if err != nil { if err != nil {
log.Warnf("监听反向WS API时出现错误: %v", err)
break break
} }
j := gjson.ParseBytes(buf) j := gjson.ParseBytes(buf)
@ -173,27 +162,26 @@ func (c *websocketClient) listenApi(conn *wsc.Conn, u bool) {
ret["echo"] = j.Get("echo").Value() ret["echo"] = j.Get("echo").Value()
} }
c.pushLock.Lock() c.pushLock.Lock()
_, _ = conn.Write([]byte(ret.ToJson())) log.Debugf("准备发送API %v 处理结果: %v", t, ret.ToJson())
_ = conn.WriteJSON(ret)
c.pushLock.Unlock() c.pushLock.Unlock()
} }
} }
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 {
c.connectUniversal()
return
}
c.connectApi() c.connectApi()
} }
} }
}
func (c *websocketClient) onBotPushEvent(m coolq.MSG) { func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
c.pushLock.Lock() c.pushLock.Lock()
defer c.pushLock.Unlock() defer c.pushLock.Unlock()
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())
_ = c.eventConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) if err := c.eventConn.WriteJSON(m); err != nil {
if _, err := c.eventConn.Write([]byte(m.ToJson())); err != nil { log.Warnf("向WS服务器 %v 推送Event时出现错误: %v", c.eventConn.RemoteAddr().String(), err)
_ = c.eventConn.Close() _ = c.eventConn.Close()
if c.conf.ReverseReconnectInterval != 0 { if c.conf.ReverseReconnectInterval != 0 {
go func() { go func() {
@ -205,8 +193,16 @@ func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
} }
if c.universalConn != nil { if c.universalConn != nil {
log.Debugf("向WS服务器 %v 推送Event: %v", c.universalConn.RemoteAddr().String(), m.ToJson()) log.Debugf("向WS服务器 %v 推送Event: %v", c.universalConn.RemoteAddr().String(), m.ToJson())
_ = c.universalConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) if err := c.universalConn.WriteJSON(m); err != nil {
_, _ = c.universalConn.Write([]byte(m.ToJson())) 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() return bot.CQGetFriendList()
}, },
"get_group_list": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "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 { "get_group_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetGroupInfo(p.Get("group_id").Int()) 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 { "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 { 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 { 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{} return coolq.MSG{}
}, },
"send_group_msg": func(bot *coolq.CQBot, p gjson.Result) 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 { "send_group_forward_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQSendGroupForwardMessage(p.Get("group_id").Int(), p.Get("messages")) return bot.CQSendGroupForwardMessage(p.Get("group_id").Int(), p.Get("messages"))
}, },
"send_private_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "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 { "delete_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQDeleteMessage(int32(p.Get("message_id").Int())) 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 { "get_group_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQGetGroupMessage(int32(p.Get("message_id").Int())) 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 { "can_send_image": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQCanSendImage() return bot.CQCanSendImage()
}, },