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

Compare commits

...

66 Commits

Author SHA1 Message Date
4e2fb91d8f image download supported. 2020-08-23 20:13:01 +08:00
37ea92b928 fix #167. 2020-08-23 19:58:02 +08:00
fc1680aa2e update MiraiGo. fix #170 2020-08-23 15:58:29 +08:00
6b256c8b82 fix #168. 2020-08-23 13:52:07 +08:00
84a826815d fix #164. 2020-08-23 03:34:42 +08:00
8ae2c1c9be music supported. 2020-08-23 03:16:48 +08:00
8fa804a2c9 Merge branch 'master' of https://github.com/Mrs4s/go-cqhttp 2020-08-23 01:46:13 +08:00
d6961fc69b move reply pos. 2020-08-23 01:46:02 +08:00
b4229c5b0e Merge pull request #166 from F0ur/master
fix(ws): fix eventConn panic when ws connect close
2020-08-23 00:31:52 +08:00
8a8ce4049d perf: format code 2020-08-22 23:09:22 +08:00
7d1ed2577e fix(ws): fix eventConn panic when ws connect close 2020-08-22 23:06:53 +08:00
b3c706cb70 update MiraiGo. 2020-08-22 18:30:08 +08:00
382daf48a5 Merge pull request #162 from wdvxdr1123/master
反向WS添加事件过滤器
2020-08-22 16:29:12 +08:00
2b92035fdc remove unnecessary info 2020-08-22 16:24:17 +08:00
5868b68437 与原版event filter基本一致 2020-08-22 16:14:26 +08:00
2949f68532 反向WS事件过滤器 2020-08-22 13:24:32 +08:00
d7abe6f7e3 Merge pull request #1 from Mrs4s/master
同步
2020-08-22 10:56:30 +08:00
2259e7ccba deadline update. 2020-08-22 03:28:38 +08:00
5e6b3e09f7 kusa. 2020-08-22 03:21:43 +08:00
9f060ce2ed fix lock & http server error. 2020-08-22 03:02:19 +08:00
3709f5c7b9 clear. 2020-08-22 02:44:00 +08:00
fa0a2d80f8 update MiraiGo. 2020-08-22 02:27:23 +08:00
894a5d9641 fix Content-Type. 2020-08-22 01:21:04 +08:00
30ff055126 Merge pull request #148 from xuthus5/master
add:go-cqhttp_version
2020-08-21 22:30:18 +08:00
f8d74a6ac9 update debug. 2020-08-21 22:28:11 +08:00
d625c79f7c Merge pull request #157 from wfjsw/patches/reduce-websocket-lock-contention
reduce websocket lock contention
2020-08-21 21:15:11 +08:00
c500bfc55c reduce websocket conn lock contention
(cherry-picked from commit dac8933..83772e2)
2020-08-21 20:47:33 +08:00
c3da6f29e2 ignore go vendor 2020-08-21 20:33:55 +08:00
196cffb099 ws async supported. close #122 2020-08-21 19:27:07 +08:00
cb9599271a update MiraiGo. 2020-08-21 19:24:06 +08:00
5ec4e3331d fix:action 2020-08-21 00:50:45 +08:00
739d486b09 add:go-cqhttp_version 2020-08-20 22:14:31 +08:00
322b70c6b5 fix bug. 2020-08-20 03:33:55 +08:00
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
18 changed files with 842 additions and 230 deletions

View File

@ -17,6 +17,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set RELEASE_VERSION env
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF:10}
- uses: wangyoucao577/go-release-action@master - uses: wangyoucao577/go-release-action@master
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@ -24,4 +26,5 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }} goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }} goarch: ${{ matrix.goarch }}
ldflags: "-w -s" ldflags: -w -s -X "github.com/Mrs4s/go-cqhttp/coolq.version=${{ env.RELEASE_VERSION }}"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor/

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

@ -1,20 +1,23 @@
package coolq package coolq
import ( import (
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/go-cqhttp/global"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"runtime" "runtime"
"strconv" "strconv"
"time" "time"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/go-cqhttp/global"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
) )
var version = "unknown"
// https://cqhttp.cc/docs/4.15/#/API?id=get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF // https://cqhttp.cc/docs/4.15/#/API?id=get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
func (bot *CQBot) CQGetLoginInfo() MSG { func (bot *CQBot) CQGetLoginInfo() MSG {
return OK(MSG{"user_id": bot.Client.Uin, "nickname": bot.Client.Nickname}) return OK(MSG{"user_id": bot.Client.Uin, "nickname": bot.Client.Nickname})
@ -34,8 +37,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 +102,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 +133,20 @@ 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 == "" {
log.Warnf("群消息发送失败: 信息为空. MSG: %v", i)
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 +225,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 +242,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)
@ -322,7 +346,7 @@ func (bot *CQBot) CQProcessFriendRequest(flag string, approve bool) MSG {
} }
// https://cqhttp.cc/docs/4.15/#/API?id=set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%EF%BC%8F%E9%82%80%E8%AF%B7 // https://cqhttp.cc/docs/4.15/#/API?id=set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%EF%BC%8F%E9%82%80%E8%AF%B7
func (bot *CQBot) CQProcessGroupRequest(flag, subType string, approve bool) MSG { func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bool) MSG {
if subType == "add" { if subType == "add" {
req, ok := bot.joinReqCache.Load(flag) req, ok := bot.joinReqCache.Load(flag)
if !ok { if !ok {
@ -332,7 +356,7 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType string, approve bool) MSG
if approve { if approve {
req.(*client.UserJoinGroupRequest).Accept() req.(*client.UserJoinGroupRequest).Accept()
} else { } else {
req.(*client.UserJoinGroupRequest).Reject() req.(*client.UserJoinGroupRequest).Reject(false, reason)
} }
return OK(nil) return OK(nil)
} }
@ -342,7 +366,7 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType string, approve bool) MSG
if approve { if approve {
req.(*client.GroupInvitedRequest).Accept() req.(*client.GroupInvitedRequest).Accept()
} else { } else {
req.(*client.GroupInvitedRequest).Reject() req.(*client.GroupInvitedRequest).Reject(false, reason)
} }
return OK(nil) return OK(nil)
} }
@ -359,6 +383,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 +447,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 +456,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 +484,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("reason").Str, operation.Get("approve").Bool())
} }
} }
} }
@ -423,11 +503,19 @@ func (bot *CQBot) CQGetImage(file string) MSG {
if b, err := ioutil.ReadFile(path.Join(global.IMAGE_PATH, file)); err == nil { if b, err := ioutil.ReadFile(path.Join(global.IMAGE_PATH, file)); err == nil {
r := binary.NewReader(b) r := binary.NewReader(b)
r.ReadBytes(16) r.ReadBytes(16)
return OK(MSG{ msg := MSG{
"size": r.ReadInt32(), "size": r.ReadInt32(),
"filename": r.ReadString(), "filename": r.ReadString(),
"url": r.ReadString(), "url": r.ReadString(),
}) }
local := path.Join(global.CACHE_PATH, file+"."+path.Ext(msg["filename"].(string)))
if !global.PathExists(local) {
if data, err := global.GetBytes(msg["url"].(string)); err == nil {
_ = ioutil.WriteFile(local, data, 0644)
}
}
msg["file"] = local
return OK(msg)
} }
return Failed(100) return Failed(100)
} }
@ -439,7 +527,7 @@ func (bot *CQBot) CQGetForwardMessage(resId string) MSG {
} }
var r []MSG var r []MSG
for _, n := range m.Nodes { for _, n := range m.Nodes {
checkMedia(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,
@ -502,6 +590,7 @@ func (bot *CQBot) CQGetVersionInfo() MSG {
"plugin_build_configuration": "release", "plugin_build_configuration": "release",
"runtime_version": runtime.Version(), "runtime_version": runtime.Version(),
"runtime_os": runtime.GOOS, "runtime_os": runtime.GOOS,
"version": version,
}) })
} }

View File

@ -63,6 +63,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 +98,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
} }
@ -115,6 +128,10 @@ 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 {
log.Warnf("群消息发送失败: 账号可能被风控.")
return -1
}
return bot.InsertGroupMessage(ret) return bot.InsertGroupMessage(ret)
} }
@ -133,16 +150,23 @@ func (bot *CQBot) SendPrivateMessage(target int64, m *message.SendingMessage) in
newElem = append(newElem, elem) newElem = append(newElem, elem)
} }
m.Elements = newElem m.Elements = newElem
var id int32 var id int32 = -1
if bot.Client.FindFriend(target) != nil { if bot.Client.FindFriend(target) != nil {
id = bot.Client.SendPrivateMessage(target, m).Id msg := bot.Client.SendPrivateMessage(target, m)
if msg != nil {
id = msg.Id
}
} else { } else {
if code, ok := bot.tempMsgCache.Load(target); ok { if code, ok := bot.tempMsgCache.Load(target); ok {
id = bot.Client.SendTempMessage(code.(int64), target, m).Id msg := bot.Client.SendTempMessage(code.(int64), target, m)
} else { if msg != nil {
return -1 id = msg.Id
}
} }
} }
if id == -1 {
return -1
}
return ToGlobalId(target, id) return ToGlobalId(target, id)
} }
@ -191,7 +215,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))
} }
}() }()
} }
@ -202,6 +226,9 @@ func formatGroupName(group *client.GroupInfo) string {
} }
func formatMemberName(mem *client.GroupMemberInfo) string { func formatMemberName(mem *client.GroupMemberInfo) string {
if mem == nil {
return "未知"
}
return fmt.Sprintf("%s(%d)", mem.DisplayName(), mem.Uin) return fmt.Sprintf("%s(%d)", mem.DisplayName(), mem.Uin)
} }

View File

@ -1,6 +1,7 @@
package coolq package coolq
import ( import (
"crypto/md5"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"errors" "errors"
@ -75,6 +76,18 @@ func ToArrayMessage(e []message.IMessageElement, code int64, raw ...bool) (r []M
"data": map[string]string{"file": o.Name, "url": o.Url}, "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: case *message.ImageElement:
if ur { if ur {
m = MSG{ m = MSG{
@ -98,6 +111,15 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
if len(raw) != 0 { if len(raw) != 0 {
ur = raw[0] ur = raw[0]
} }
// 方便
m := &message.SendingMessage{Elements: e}
reply := m.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
return ok
})
if reply != nil {
r += fmt.Sprintf("[CQ:reply,id=%d]", ToGlobalId(code, reply.(*message.ReplyElement).ReplySeq))
}
for _, elem := range e { for _, elem := range e {
switch o := elem.(type) { switch o := elem.(type) {
case *message.TextElement: case *message.TextElement:
@ -108,8 +130,6 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
continue continue
} }
r += fmt.Sprintf("[CQ:at,qq=%d]", o.Target) r += fmt.Sprintf("[CQ:at,qq=%d]", o.Target)
case *message.ReplyElement:
r += fmt.Sprintf("[CQ:reply,id=%d]", ToGlobalId(code, o.ReplySeq))
case *message.ForwardElement: case *message.ForwardElement:
r += fmt.Sprintf("[CQ:forward,id=%s]", o.ResId) r += fmt.Sprintf("[CQ:forward,id=%s]", o.ResId)
case *message.FaceElement: case *message.FaceElement:
@ -120,6 +140,12 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
} else { } else {
r += fmt.Sprintf(`[CQ:record,file=%s,url=%s]`, o.Name, CQCodeEscapeValue(o.Url)) 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)
@ -243,10 +269,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") {
@ -367,7 +406,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
} }
data = b data = b
} }
if !global.IsAMR(data) { 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
@ -386,6 +425,38 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
return message.NewAt(t), nil return message.NewAt(t), nil
case "share": case "share":
return message.NewUrlShare(d["url"], d["title"], d["content"], d["image"]), nil return message.NewUrlShare(d["url"], d["title"], d["content"], d["image"]), nil
case "music":
if d["type"] == "qq" {
info, err := global.QQMusicSongInfo(d["id"])
if err != nil {
return nil, err
}
if !info.Get("track_info").Exists() {
return nil, errors.New("song not found")
}
aid := strconv.FormatInt(info.Get("track_info.album.id").Int(), 10)
name := info.Get("track_info.name").Str
if len(aid) < 2 {
return nil, errors.New("song error")
}
xml := fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="2" templateID="1" action="web" brief="[分享] %s" sourceMsgId="0" url="https://i.y.qq.com/v8/playsong.html?_wv=1&songid=%s&souce=qqshare&source=qqshare&ADTAG=qqshare" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2"><audio cover="http://imgcache.qq.com/music/photo/album_500/%s/500_albumpic_%s_0.jpg" src="%s" /><title>%s</title><summary>%s</summary></item><source name="QQ音乐" icon="https://i.gtimg.cn/open/app_icon/01/07/98/56/1101079856_100_m.png" url="http://web.p.qq.com/qqmpmobile/aio/app.html?id=1101079856" action="app" a_actionData="com.tencent.qqmusic" i_actionData="tencent1101079856://" appid="1101079856" /></msg>`,
name, d["id"], aid[:len(aid)-2], aid, name, "", info.Get("track_info.singer.name").Str)
return &message.ServiceElement{
Id: 60,
Content: xml,
SubType: "music",
}, nil
}
if d["type"] == "custom" {
xml := fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="2" templateID="1" action="web" brief="[分享] %s" sourceMsgId="0" url="%s" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2"><audio cover="%s" src="%s"/><title>%s</title><summary>%s</summary></item><source name="音乐" icon="https://i.gtimg.cn/open/app_icon/01/07/98/56/1101079856_100_m.png" url="http://web.p.qq.com/qqmpmobile/aio/app.html?id=1101079856" action="app" a_actionData="com.tencent.qqmusic" i_actionData="tencent1101079856://" appid="1101079856" /></msg>`,
d["title"], d["url"], d["image"], d["audio"], d["title"], d["content"])
return &message.ServiceElement{
Id: 60,
Content: xml,
SubType: "music",
}, nil
}
return nil, errors.New("unsupported music type: " + d["type"])
default: default:
return nil, errors.New("unsupported cq code: " + t) return nil, errors.New("unsupported cq code: " + t)
} }
@ -414,7 +485,7 @@ func CQCodeUnescapeText(content string) string {
} }
func CQCodeUnescapeValue(content string) string { func CQCodeUnescapeValue(content string) string {
ret := CQCodeUnescapeText(content) ret := strings.ReplaceAll(content, "&#44;", ",")
ret = strings.ReplaceAll(ret, "&#44;", ",") ret = CQCodeUnescapeText(ret)
return ret return ret
} }

View File

@ -30,7 +30,7 @@ func ToFormattedMessage(e []message.IMessageElement, code int64, raw ...bool) (r
} }
func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMessage) { func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMessage) {
checkMedia(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{
@ -55,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) {
checkMedia(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)
@ -133,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) {
checkMedia(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)
@ -362,7 +362,7 @@ func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.Group
} }
} }
func checkMedia(e []message.IMessageElement) { func (bot *CQBot) checkMedia(e []message.IMessageElement) {
for _, elem := range e { for _, elem := range e {
switch i := elem.(type) { switch i := elem.(type) {
case *message.ImageElement: case *message.ImageElement:
@ -373,7 +373,7 @@ func checkMedia(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: case *message.VoiceElement:
@ -385,8 +385,20 @@ func checkMedia(e []message.IMessageElement) {
log.Warnf("语音文件 %v 下载失败: %v", i.Name, err) log.Warnf("语音文件 %v 下载失败: %v", i.Name, err)
continue continue
} }
_ = ioutil.WriteFile(path.Join(global.VOICE_PATH, i.Name), b, 0777) _ = 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,12 +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,
"timeout": 5,
"post_urls": {"url:port": "secret"}, "post_urls": {"url:port": "secret"},
"post_message_format": "string"
}, },
"ws_config": { "ws_config": {
"enabled": true, "enabled": true,

View File

@ -17,6 +17,7 @@ type JsonConfig struct {
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"`
} }
@ -40,12 +41,11 @@ type CQHttpApiConfig struct {
} }
type GoCQHttpConfig struct { 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"` Timeout int32 `json:"timeout"`
PostUrls map[string]string `json:"post_urls"` PostUrls map[string]string `json:"post_urls"`
PostMessageFormat string `json:"post_message_format"`
} }
type GoCQWebsocketConfig struct { type GoCQWebsocketConfig struct {
@ -64,15 +64,15 @@ type GoCQReverseWebsocketConfig struct {
func DefaultConfig() *JsonConfig { func DefaultConfig() *JsonConfig {
return &JsonConfig{ return &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",
Port: 5700, Port: 5700,
PostUrls: map[string]string{}, PostUrls: map[string]string{},
PostMessageFormat: "string",
}, },
WSConfig: &GoCQWebsocketConfig{ WSConfig: &GoCQWebsocketConfig{
Enabled: true, Enabled: true,

267
global/filter.go Normal file
View File

@ -0,0 +1,267 @@
package global
import (
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"io/ioutil"
"regexp"
"strings"
"sync"
)
type Filter interface {
Eval(payload gjson.Result) bool
}
type OperationNode struct {
key string
filter Filter
}
type NotOperator struct {
operand_ Filter
}
func notOperatorConstruct(argument gjson.Result) *NotOperator {
if !argument.IsObject() {
log.Error("the argument of 'not' operator must be an object")
}
op := new(NotOperator)
op.operand_ = GetOperatorFactory().Generate("and", argument)
return op
}
func (notOperator NotOperator) Eval(payload gjson.Result) bool {
log.Debug("not " + payload.Str)
return !(notOperator.operand_).Eval(payload)
}
type AndOperator struct {
operands []OperationNode
}
func andOperatorConstruct(argument gjson.Result) *AndOperator {
if !argument.IsObject() {
log.Error("the argument of 'and' operator must be an object")
}
op := new(AndOperator)
argument.ForEach(func(key, value gjson.Result) bool {
if key.Str[0] == '.' {
// is an operator
// ".foo": {
// "bar": "baz"
// }
opKey := key.Str[1:]
op.operands = append(op.operands, OperationNode{"", GetOperatorFactory().Generate(opKey, value)})
} else if value.IsObject() {
// is an normal key with an object as the value
// "foo": {
// ".bar": "baz"
// }
opKey := key.Str
op.operands = append(op.operands, OperationNode{opKey, GetOperatorFactory().Generate("and", value)})
} else {
// is an normal key with a non-object as the value
// "foo": "bar"
opKey := key.Str
op.operands = append(op.operands, OperationNode{opKey, GetOperatorFactory().Generate("eq", value)})
}
return true
})
return op
}
func (andOperator *AndOperator) Eval(payload gjson.Result) bool {
log.Debug("and " + payload.Str)
res := true
for _, operand := range andOperator.operands {
if len(operand.key) == 0 {
// is an operator
res = res && operand.filter.Eval(payload)
} else {
// is an normal key
val := payload.Get(operand.key)
res = res && operand.filter.Eval(val)
}
if res == false {
break
}
}
return res
}
type OrOperator struct {
operands []Filter
}
func orOperatorConstruct(argument gjson.Result) *OrOperator {
if !argument.IsArray() {
log.Error("the argument of 'or' operator must be an array")
}
op := new(OrOperator)
argument.ForEach(func(_, value gjson.Result) bool {
op.operands = append(op.operands, GetOperatorFactory().Generate("and", value))
return true
})
return op
}
func (orOperator OrOperator) Eval(payload gjson.Result) bool {
log.Debug("or "+ payload.Str)
res:= false
for _, operand := range orOperator.operands {
res = res || operand.Eval(payload)
if res == true {
break
}
}
return res
}
type EqualOperator struct {
value gjson.Result
}
func equalOperatorConstruct(argument gjson.Result) *EqualOperator {
op := new(EqualOperator)
op.value = argument
return op
}
func (equalOperator EqualOperator) Eval(payload gjson.Result) bool {
log.Debug("eq "+ payload.Str + "==" + equalOperator.value.Str)
return payload.Str == equalOperator.value.Str
}
type NotEqualOperator struct {
value gjson.Result
}
func notEqualOperatorConstruct(argument gjson.Result) *NotEqualOperator {
op := new(NotEqualOperator)
op.value = argument
return op
}
func (notEqualOperator NotEqualOperator) Eval(payload gjson.Result) bool {
log.Debug("neq " + payload.Str)
return !(payload.Str == notEqualOperator.value.Str)
}
type InOperator struct {
operand gjson.Result
}
func inOperatorConstruct(argument gjson.Result) *InOperator {
if argument.IsObject() {
log.Error("the argument of 'in' operator must be an array or a string")
}
op := new(InOperator)
op.operand = argument
return op
}
func (inOperator InOperator) Eval(payload gjson.Result) bool {
log.Debug("in " + payload.Str)
if inOperator.operand.IsArray() {
res := false
inOperator.operand.ForEach(func(key, value gjson.Result) bool {
res = res || value.Str == payload.Str
return true
})
return res
}
return strings.Contains(inOperator.operand.Str, payload.Str)
}
type ContainsOperator struct {
operand string
}
func containsOperatorConstruct(argument gjson.Result) *ContainsOperator {
if argument.IsArray() || argument.IsObject() {
log.Error("the argument of 'contains' operator must be a string")
}
op := new(ContainsOperator)
op.operand = argument.Str
return op
}
func (containsOperator ContainsOperator) Eval(payload gjson.Result) bool {
log.Debug("contains "+ payload.Str)
if payload.IsObject() || payload.IsArray() {
return false
}
return strings.Contains(payload.String(), containsOperator.operand)
}
type RegexOperator struct {
regex string
}
func regexOperatorConstruct(argument gjson.Result) *RegexOperator {
if argument.IsArray() || argument.IsObject() {
log.Error("the argument of 'regex' operator must be a string")
}
op := new(RegexOperator)
op.regex = argument.Str
return op
}
func (containsOperator RegexOperator) Eval(payload gjson.Result) bool {
log.Debug("regex " + payload.Str)
matched, _ := regexp.MatchString(containsOperator.regex, payload.Str)
return matched
}
// 单例工厂
type operatorFactory struct{
}
var instance *operatorFactory = &operatorFactory{}
func GetOperatorFactory() *operatorFactory {
return instance
}
func (o operatorFactory) Generate(opName string, argument gjson.Result) Filter {
switch opName {
case "not":
return notOperatorConstruct(argument)
case "and":
return andOperatorConstruct(argument)
case "or":
return orOperatorConstruct(argument)
case "neq":
return notEqualOperatorConstruct(argument)
case "eq":
return equalOperatorConstruct(argument)
case "in":
return inOperatorConstruct(argument)
case "contains":
return containsOperatorConstruct(argument)
case "regex":
return regexOperatorConstruct(argument)
default:
log.Warnf("the operator '%s' is not supported", opName)
return nil
}
}
var filter = new(Filter)
var once sync.Once // 过滤器单例模式
func GetFilter() *Filter {
once.Do(func() {
f, err := ioutil.ReadFile("filter.json")
if err != nil {
filter = nil
} else {
*filter = GetOperatorFactory().Generate("and", gjson.ParseBytes(f))
}
})
return filter
}

View File

@ -1,15 +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")
var VOICE_PATH = path.Join("data", "voices") 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)
@ -25,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) {
@ -34,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
} }

View File

@ -3,6 +3,7 @@ package global
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"github.com/tidwall/gjson"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings" "strings"
@ -32,3 +33,11 @@ func GetBytes(url string) ([]byte, error) {
} }
return body, nil return body, nil
} }
func QQMusicSongInfo(id string) (gjson.Result, error) {
d, err := GetBytes(`https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data={%22comm%22:{%22ct%22:24,%22cv%22:0},%22songinfo%22:{%22method%22:%22get_song_detail_yqq%22,%22param%22:{%22song_type%22:0,%22song_mid%22:%22%22,%22song_id%22:` + id + `},%22module%22:%22music.pf_song_detail_svr%22}}`)
if err != nil {
return gjson.Result{}, err
}
return gjson.ParseBytes(d).Get("songinfo.data"), nil
}

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
}

7
go.mod
View File

@ -3,18 +3,21 @@ module github.com/Mrs4s/go-cqhttp
go 1.14 go 1.14
require ( require (
github.com/Mrs4s/MiraiGo v0.0.0-20200812011522-ee1117893fad github.com/Mrs4s/MiraiGo v0.0.0-20200823075559-507fe33e842d
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
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
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
github.com/jonboulle/clockwork v0.2.0 // indirect
github.com/lestrrat-go/file-rotatelogs v2.3.0+incompatible github.com/lestrrat-go/file-rotatelogs v2.3.0+incompatible
github.com/lestrrat-go/strftime v1.0.1 // indirect github.com/lestrrat-go/strftime v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.6.0 github.com/sirupsen/logrus v1.6.0
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816
github.com/tebeka/strftime v0.1.5 // indirect
github.com/tidwall/gjson v1.6.0 github.com/tidwall/gjson v1.6.0
github.com/xujiajun/nutsdb v0.5.0 github.com/xujiajun/nutsdb v0.5.0
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
) )

12
go.sum
View File

@ -1,11 +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-20200823075559-507fe33e842d h1:F7ssNQDHqB7NZVwTeADRY+AxKT3eeSlBzfzeZYTUfxM=
github.com/Mrs4s/MiraiGo v0.0.0-20200809221224-7a84cfae6795/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM= github.com/Mrs4s/MiraiGo v0.0.0-20200823075559-507fe33e842d/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
github.com/Mrs4s/MiraiGo v0.0.0-20200810032556-a425f9d1b98e h1:5LYDouOL9ZgTL5PwZuuSlFYSfboRQjnXqRIlhviRcGE=
github.com/Mrs4s/MiraiGo v0.0.0-20200810032556-a425f9d1b98e/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
github.com/Mrs4s/MiraiGo v0.0.0-20200812011522-ee1117893fad h1:mOz8SozY2NEjXivlOrTwGPsbukcpLYpi/rv0/ASM/Hg=
github.com/Mrs4s/MiraiGo v0.0.0-20200812011522-ee1117893fad/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=
@ -14,6 +10,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
@ -52,6 +49,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/guonaihong/gout v0.1.1 h1:2i3eqQ1KUhTlj7AFeIHqVUFku5QwUhwE2wNgYTVpbxQ= github.com/guonaihong/gout v0.1.1 h1:2i3eqQ1KUhTlj7AFeIHqVUFku5QwUhwE2wNgYTVpbxQ=
github.com/guonaihong/gout v0.1.1/go.mod h1:vXvv5Kxr70eM5wrp4F0+t9lnLWmq+YPW2GByll2f/EA= github.com/guonaihong/gout v0.1.1/go.mod h1:vXvv5Kxr70eM5wrp4F0+t9lnLWmq+YPW2GByll2f/EA=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -87,6 +86,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk= github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk=
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA= github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA=
github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=

43
main.go
View File

@ -37,15 +37,25 @@ func init() {
log.SetOutput(io.MultiWriter(os.Stderr, w)) log.SetOutput(io.MultiWriter(os.Stderr, w))
} }
if !global.PathExists(global.IMAGE_PATH) { if !global.PathExists(global.IMAGE_PATH) {
if err := os.MkdirAll(global.IMAGE_PATH, os.ModePerm); err != nil { if err := os.MkdirAll(global.IMAGE_PATH, 0755); err != nil {
log.Fatalf("创建图片缓存文件夹失败: %v", err) log.Fatalf("创建图片缓存文件夹失败: %v", err)
} }
} }
if !global.PathExists(global.VOICE_PATH) { if !global.PathExists(global.VOICE_PATH) {
if err := os.MkdirAll(global.VOICE_PATH, os.ModePerm); err != nil { if err := os.MkdirAll(global.VOICE_PATH, 0755); err != nil {
log.Fatalf("创建语音缓存文件夹失败: %v", err) 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 文件.")
@ -91,18 +101,18 @@ func main() {
Uin: uin, Uin: uin,
Password: pwd, Password: pwd,
HttpConfig: &global.GoCQHttpConfig{ HttpConfig: &global.GoCQHttpConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 5700, Port: 5700,
PostUrls: map[string]string{}, PostUrls: map[string]string{},
PostMessageFormat: "string",
}, },
WSConfig: &global.GoCQWebsocketConfig{ WSConfig: &global.GoCQWebsocketConfig{
Enabled: true, Enabled: true,
Host: "0.0.0.0", Host: "0.0.0.0",
Port: 6700, Port: 6700,
}, },
Debug: os.Getenv("DEBUG") == "true", PostMessageFormat: "string",
Debug: os.Getenv("DEBUG") == "true",
} }
if post != "" { if post != "" {
conf.HttpConfig.PostUrls[post] = os.Getenv("HTTP_SECRET") conf.HttpConfig.PostUrls[post] = os.Getenv("HTTP_SECRET")
@ -126,11 +136,12 @@ func main() {
if conf.Debug { if conf.Debug {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.Warnf("已开启Debug模式.") log.Warnf("已开启Debug模式.")
log.Debugf("开发交流群: 192548878")
} }
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(), os.ModePerm) _ = 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.")
@ -166,7 +177,7 @@ 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, os.ModePerm) _ = 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("请输入验证码 (captcha.jpg) (Enter 提交)") log.Warn("请输入验证码 (captcha.jpg) (Enter 提交)")
@ -193,14 +204,14 @@ func main() {
global.Check(cli.ReloadGroupList()) global.Check(cli.ReloadGroupList())
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)
if conf.HttpConfig.PostMessageFormat != "string" && conf.HttpConfig.PostMessageFormat != "array" {
log.Warnf("http_config.post_message_format 配置错误, 将自动使用 string")
coolq.SetMessageFormat("string")
} else {
coolq.SetMessageFormat(conf.HttpConfig.PostMessageFormat)
}
for k, v := range conf.HttpConfig.PostUrls { for k, v := range conf.HttpConfig.PostUrls {
server.NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, b) server.NewHttpClient().Run(k, v, conf.HttpConfig.Timeout, b)
} }

View File

@ -4,11 +4,13 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"os"
"strconv" "strconv"
"strings" "strings"
"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"
@ -39,7 +41,7 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) {
c.Status(404) c.Status(404)
return return
} }
if c.Request.Method == "POST" && c.Request.Header.Get("Content-Type") == "application/json" { if c.Request.Method == "POST" && strings.Contains(c.Request.Header.Get("Content-Type"), "application/json") {
d, err := c.GetRawData() d, err := c.GetRawData()
if err != nil { if err != nil {
log.Warnf("获取请求 %v 的Body时出现错误: %v", c.Request.RequestURI, err) log.Warnf("获取请求 %v 的Body时出现错误: %v", c.Request.RequestURI, err)
@ -133,12 +135,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)
@ -156,7 +158,13 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) {
go func() { go func() {
log.Infof("CQ HTTP 服务器已启动: %v", addr) log.Infof("CQ HTTP 服务器已启动: %v", addr)
log.Fatal(s.engine.Run(addr)) err := s.engine.Run(addr)
if err != nil {
log.Error(err)
log.Infof("请检查端口是否被占用.")
time.Sleep(time.Second * 5)
os.Exit(1)
}
}() }()
} }
@ -208,7 +216,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) {
@ -249,21 +258,23 @@ 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, t := getParamWithType(c, "message") msg, t := getParamWithType(c, "message")
autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
if t == gjson.JSON { if t == gjson.JSON {
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg))) 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, t := getParamWithType(c, "message") msg, t := getParamWithType(c, "message")
autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
if t == gjson.JSON { if t == gjson.JSON {
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg))) 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) {
@ -282,6 +293,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")
@ -295,7 +311,7 @@ func (s *httpServer) ProcessGroupRequest(c *gin.Context) {
subType = getParam(c, "type") subType = getParam(c, "type")
} }
approve := getParamOrDefault(c, "approve", "true") approve := getParamOrDefault(c, "approve", "true")
c.JSON(200, s.bot.CQProcessGroupRequest(flag, subType, approve == "true")) c.JSON(200, s.bot.CQProcessGroupRequest(flag, subType, getParam(c, "reason"), approve == "true"))
} }
func (s *httpServer) SetGroupCard(c *gin.Context) { func (s *httpServer) SetGroupCard(c *gin.Context) {
@ -395,12 +411,12 @@ func getParamWithType(c *gin.Context, k string) (string, gjson.Type) {
} }
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 strings.Contains(h, "application/x-www-form-urlencoded") {
if p, ok := c.GetPostForm(k); ok { if p, ok := c.GetPostForm(k); ok {
return p, gjson.Null return p, gjson.Null
} }
} }
if h == "application/json" { if strings.Contains(h, "application/json") {
if obj, ok := c.Get("json_body"); ok { if obj, ok := c.Get("json_body"); ok {
res := obj.(gjson.Result).Get(k) res := obj.(gjson.Result).Get(k)
if res.Exists() { if res.Exists() {

View File

@ -2,25 +2,25 @@ package server
import ( import (
"fmt" "fmt"
"github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
wsc "golang.org/x/net/websocket"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/Mrs4s/go-cqhttp/coolq"
"github.com/Mrs4s/go-cqhttp/global"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
) )
type websocketServer struct { type websocketServer struct {
bot *coolq.CQBot bot *coolq.CQBot
token string token string
eventConn []*websocket.Conn eventConn []*websocketConn
pushLock *sync.Mutex eventConnMutex sync.Mutex
handshake string handshake string
} }
type websocketClient struct { type websocketClient struct {
@ -28,9 +28,13 @@ type websocketClient struct {
token string token string
bot *coolq.CQBot bot *coolq.CQBot
pushLock *sync.Mutex universalConn *websocketConn
universalConn *wsc.Conn eventConn *websocketConn
eventConn *wsc.Conn }
type websocketConn struct {
*websocket.Conn
sync.Mutex
} }
var WebsocketServer = &websocketServer{} var WebsocketServer = &websocketServer{}
@ -42,7 +46,6 @@ var upgrader = websocket.Upgrader{
func (s *websocketServer) Run(addr, authToken string, b *coolq.CQBot) { func (s *websocketServer) Run(addr, authToken string, b *coolq.CQBot) {
s.token = authToken s.token = authToken
s.pushLock = new(sync.Mutex)
s.bot = b s.bot = b
s.handshake = fmt.Sprintf(`{"_post_method":2,"meta_event_type":"lifecycle","post_type":"meta_event","self_id":%d,"sub_type":"connect","time":%d}`, s.handshake = fmt.Sprintf(`{"_post_method":2,"meta_event_type":"lifecycle","post_type":"meta_event","self_id":%d,"sub_type":"connect","time":%d}`,
s.bot.Client.Uin, time.Now().Unix()) s.bot.Client.Uin, time.Now().Unix())
@ -57,7 +60,7 @@ func (s *websocketServer) Run(addr, authToken string, b *coolq.CQBot) {
} }
func NewWebsocketClient(conf *global.GoCQReverseWebsocketConfig, authToken string, b *coolq.CQBot) *websocketClient { func NewWebsocketClient(conf *global.GoCQReverseWebsocketConfig, authToken string, b *coolq.CQBot) *websocketClient {
return &websocketClient{conf: conf, token: authToken, bot: b, pushLock: new(sync.Mutex)} return &websocketClient{conf: conf, token: authToken, bot: b}
} }
func (c *websocketClient) Run() { func (c *websocketClient) Run() {
@ -79,18 +82,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 {
@ -100,49 +100,44 @@ func (c *websocketClient) connectApi() {
return return
} }
log.Infof("已连接到反向Websocket API服务器 %v", c.conf.ReverseApiUrl) log.Infof("已连接到反向Websocket API服务器 %v", c.conf.ReverseApiUrl)
go c.listenApi(conn, false) wrappedConn := &websocketConn{Conn: conn}
go c.listenApi(wrappedConn, false)
} }
func (c *websocketClient) connectEvent() { func (c *websocketClient) connectEvent() {
log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl) log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl)
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
} }
log.Infof("已连接到反向Websocket Event服务器 %v", c.conf.ReverseEventUrl) log.Infof("已连接到反向Websocket Event服务器 %v", c.conf.ReverseEventUrl)
c.eventConn = conn c.eventConn = &websocketConn{Conn: conn}
} }
func (c *websocketClient) connectUniversal() { func (c *websocketClient) connectUniversal() {
log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl) log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl)
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.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 {
@ -151,60 +146,67 @@ func (c *websocketClient) connectUniversal() {
} }
return return
} }
go c.listenApi(conn, true) wrappedConn := &websocketConn{Conn: conn}
c.universalConn = conn go c.listenApi(wrappedConn, true)
c.universalConn = wrappedConn
} }
func (c *websocketClient) listenApi(conn *wsc.Conn, u bool) { func (c *websocketClient) listenApi(conn *websocketConn, 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)
t := strings.ReplaceAll(j.Get("action").Str, "_async", "") go conn.handleRequest(c.bot, buf)
log.Debugf("反向WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw)
if f, ok := wsApi[t]; ok {
ret := f(c.bot, j.Get("params"))
if j.Get("echo").Exists() {
ret["echo"] = j.Get("echo").Value()
}
c.pushLock.Lock()
log.Debugf("准备发送API %v 处理结果: %v", t, ret.ToJson())
_, _ = conn.Write([]byte(ret.ToJson()))
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() go c.connectApi()
return
} }
c.connectApi()
} }
} }
func (c *websocketClient) onBotPushEvent(m coolq.MSG) { func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
c.pushLock.Lock() payload := gjson.Parse(m.ToJson())
defer c.pushLock.Unlock() filter := global.GetFilter()
if filter != nil && (*filter).Eval(payload) == false {
log.Debug("Event filtered!")
return
}
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())
if _, err := c.eventConn.Write([]byte(m.ToJson())); err != nil { conn := c.eventConn
conn.Lock()
defer conn.Unlock()
_ = c.eventConn.SetWriteDeadline(time.Now().Add(time.Second * 15))
if err := c.eventConn.WriteJSON(m); 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() { time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval)) c.connectEvent()
c.connectEvent()
}()
} }
} }
} }
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.Write([]byte(m.ToJson())) conn := c.universalConn
conn.Lock()
defer conn.Unlock()
_ = c.universalConn.SetWriteDeadline(time.Now().Add(time.Second * 15))
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 {
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
c.connectUniversal()
}
}
} }
} }
@ -222,10 +224,19 @@ func (s *websocketServer) event(w http.ResponseWriter, r *http.Request) {
return return
} }
err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake)) err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake))
if err == nil { if err != nil {
log.Infof("接受 Websocket 连接: %v (/event)", r.RemoteAddr) log.Warnf("Websocket 握手时出现错误: %v", err)
s.eventConn = append(s.eventConn, c) c.Close()
return
} }
log.Infof("接受 Websocket 连接: %v (/event)", r.RemoteAddr)
conn := &websocketConn{Conn: c}
s.eventConnMutex.Lock()
s.eventConn = append(s.eventConn, conn)
s.eventConnMutex.Unlock()
} }
func (s *websocketServer) api(w http.ResponseWriter, r *http.Request) { func (s *websocketServer) api(w http.ResponseWriter, r *http.Request) {
@ -242,7 +253,8 @@ func (s *websocketServer) api(w http.ResponseWriter, r *http.Request) {
return return
} }
log.Infof("接受 Websocket 连接: %v (/api)", r.RemoteAddr) log.Infof("接受 Websocket 连接: %v (/api)", r.RemoteAddr)
go s.listenApi(c) conn := &websocketConn{Conn: c}
go s.listenApi(conn)
} }
func (s *websocketServer) any(w http.ResponseWriter, r *http.Request) { func (s *websocketServer) any(w http.ResponseWriter, r *http.Request) {
@ -259,52 +271,72 @@ func (s *websocketServer) any(w http.ResponseWriter, r *http.Request) {
return return
} }
err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake)) err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake))
if err == nil { if err != nil {
log.Infof("接受 Websocket 连接: %v (/)", r.RemoteAddr) log.Warnf("Websocket 握手时出现错误: %v", err)
s.eventConn = append(s.eventConn, c) c.Close()
s.listenApi(c) return
} }
log.Infof("接受 Websocket 连接: %v (/)", r.RemoteAddr)
conn := &websocketConn{Conn: c}
s.eventConn = append(s.eventConn, conn)
s.listenApi(conn)
} }
func (s *websocketServer) listenApi(c *websocket.Conn) { func (s *websocketServer) listenApi(c *websocketConn) {
defer c.Close() defer c.Close()
for { for {
t, payload, err := c.ReadMessage() t, payload, err := c.ReadMessage()
if err != nil { if err != nil {
break break
} }
if t == websocket.TextMessage { if t == websocket.TextMessage {
j := gjson.ParseBytes(payload) go c.handleRequest(s.bot, payload)
t := strings.ReplaceAll(j.Get("action").Str, "_async", "") //TODO: async support
log.Debugf("WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw)
if f, ok := wsApi[t]; ok {
ret := f(s.bot, j.Get("params"))
if j.Get("echo").Exists() {
ret["echo"] = j.Get("echo").Value()
}
s.pushLock.Lock()
_ = c.WriteJSON(ret)
s.pushLock.Unlock()
}
} }
} }
} }
func (s *websocketServer) onBotPushEvent(m coolq.MSG) { func (c *websocketConn) handleRequest(bot *coolq.CQBot, payload []byte) {
s.pushLock.Lock() defer func() {
defer s.pushLock.Unlock() if err := recover(); err != nil {
pos := 0 log.Printf("处置WS命令时发生无法恢复的异常%v", err)
for _, conn := range s.eventConn { c.Close()
log.Debugf("向WS客户端 %v 推送Event: %v", conn.RemoteAddr().String(), m.ToJson()) }
err := conn.WriteMessage(websocket.TextMessage, []byte(m.ToJson())) }()
if err != nil {
_ = conn.Close() j := gjson.ParseBytes(payload)
s.eventConn = append(s.eventConn[:pos], s.eventConn[pos+1:]...) t := strings.ReplaceAll(j.Get("action").Str, "_async", "")
if pos > 0 { log.Debugf("WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw)
pos++ if f, ok := wsApi[t]; ok {
} ret := f(bot, j.Get("params"))
if j.Get("echo").Exists() {
ret["echo"] = j.Get("echo").Value()
}
c.Lock()
defer c.Unlock()
_ = c.WriteJSON(ret)
}
}
func (s *websocketServer) onBotPushEvent(m coolq.MSG) {
s.eventConnMutex.Lock()
defer s.eventConnMutex.Unlock()
for i, l := 0, len(s.eventConn); i < l; i++ {
conn := s.eventConn[i]
log.Debugf("向WS客户端 %v 推送Event: %v", conn.RemoteAddr().String(), m.ToJson())
if err := conn.WriteMessage(websocket.TextMessage, []byte(m.ToJson())); err != nil {
_ = conn.Close()
next := i + 1
if next >= l {
next = l - 1
}
s.eventConn[i], s.eventConn[next] = s.eventConn[next], s.eventConn[i]
s.eventConn = append(s.eventConn[:next], s.eventConn[next+1:]...)
i--
l--
conn = nil
} }
pos++
} }
} }
@ -316,7 +348,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())
@ -331,28 +363,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" { if p.Get("message_type").Str == "private" {
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message")) return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"), autoEscape)
} }
if p.Get("message_type").Str == "group" { if p.Get("message_type").Str == "group" {
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("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()))
@ -373,7 +406,7 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
if p.Get("approve").Exists() { if p.Get("approve").Exists() {
apr = p.Get("approve").Bool() apr = p.Get("approve").Bool()
} }
return bot.CQProcessGroupRequest(p.Get("flag").Str, subType, apr) return bot.CQProcessGroupRequest(p.Get("flag").Str, subType, p.Get("reason").Str, apr)
}, },
"set_group_card": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { "set_group_card": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
return bot.CQSetGroupCard(p.Get("group_id").Int(), p.Get("user_id").Int(), p.Get("card").Str) return bot.CQSetGroupCard(p.Get("group_id").Int(), p.Get("user_id").Int(), p.Get("card").Str)
@ -415,6 +448,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()
}, },