mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-06-30 03:43:25 +00:00
Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
cefbfb64ce | |||
04bcd2e270 | |||
0e7c30e854 | |||
0894f2ac44 | |||
50191fcedf | |||
41614b9d16 | |||
11592c4483 | |||
a9d08e5d92 | |||
1c4cb96ce0 | |||
46747354c8 | |||
01988cd3d7 | |||
0e95977ff3 | |||
be17920559 | |||
fe7a1f5028 | |||
174ebfae9d | |||
02355db0f1 | |||
bda02d895a | |||
5fe15163f8 | |||
174bb0bbe1 | |||
e319f2645e | |||
52c911056e | |||
ac97c04cef | |||
801fa9a204 | |||
17f8232a1c | |||
cb9436601f | |||
03cc0dba95 | |||
248ba84a0c | |||
8874ed0392 | |||
9acf598209 | |||
15085d0a6b | |||
4e2fb91d8f | |||
37ea92b928 | |||
fc1680aa2e | |||
6b256c8b82 | |||
65acc888fd | |||
84a826815d | |||
8ae2c1c9be | |||
8fa804a2c9 | |||
d6961fc69b | |||
b4229c5b0e | |||
8a8ce4049d | |||
7d1ed2577e | |||
b3c706cb70 | |||
382daf48a5 | |||
2b92035fdc | |||
5868b68437 | |||
2949f68532 | |||
d7abe6f7e3 | |||
2259e7ccba | |||
5e6b3e09f7 | |||
9f060ce2ed | |||
3709f5c7b9 | |||
fa0a2d80f8 | |||
894a5d9641 | |||
30ff055126 | |||
f8d74a6ac9 | |||
d625c79f7c | |||
c500bfc55c | |||
c3da6f29e2 | |||
196cffb099 | |||
cb9599271a | |||
5ec4e3331d | |||
739d486b09 | |||
322b70c6b5 | |||
470efa07ab | |||
b7572f8d2c | |||
c9a914b5d5 | |||
51696e8054 | |||
a6bcd96415 | |||
08694f5ae8 | |||
8c71dbff68 | |||
568b0e479f | |||
d1da08a376 | |||
5768c61bc7 | |||
864120d3fe | |||
99b414530a | |||
4820eb2fec | |||
9af61a336b |
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@ -17,6 +17,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
@ -24,4 +26,5 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
ldflags: "-w -s"
|
||||
ldflags: -w -s -X "github.com/Mrs4s/go-cqhttp/coolq.Version=${{ env.RELEASE_VERSION }}"
|
||||
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
vendor/
|
||||
.idea
|
151
coolq/api.go
151
coolq/api.go
@ -1,20 +1,23 @@
|
||||
package coolq
|
||||
|
||||
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"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"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
|
||||
func (bot *CQBot) CQGetLoginInfo() MSG {
|
||||
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
|
||||
func (bot *CQBot) CQGetGroupList() MSG {
|
||||
func (bot *CQBot) CQGetGroupList(noCache bool) MSG {
|
||||
var gs []MSG
|
||||
if noCache {
|
||||
_ = bot.Client.ReloadGroupList()
|
||||
}
|
||||
for _, g := range bot.Client.GroupList {
|
||||
gs = append(gs, MSG{
|
||||
"group_id": g.Code,
|
||||
@ -96,11 +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
|
||||
func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}) MSG {
|
||||
func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}, autoEscape bool) MSG {
|
||||
var str string
|
||||
fixAt := func(elem []message.IMessageElement) {
|
||||
for _, e := range elem {
|
||||
if at, ok := e.(*message.AtElement); ok && at.Target != 0 {
|
||||
at.Display = "@" + func() string {
|
||||
mem := bot.Client.FindGroup(groupId).FindMember(at.Target)
|
||||
if mem != nil {
|
||||
return mem.DisplayName()
|
||||
}
|
||||
return strconv.FormatInt(at.Target, 10)
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
if m, ok := i.(gjson.Result); ok {
|
||||
if m.Type == gjson.JSON {
|
||||
elem := bot.ConvertObjectMessage(m, true)
|
||||
fixAt(elem)
|
||||
mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100)
|
||||
@ -113,26 +133,20 @@ func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}) MSG {
|
||||
}
|
||||
return m.Raw
|
||||
}()
|
||||
}
|
||||
if s, ok := i.(string); ok {
|
||||
} else if s, ok := i.(string); ok {
|
||||
str = s
|
||||
}
|
||||
if str == "" {
|
||||
log.Warnf("群消息发送失败: 信息为空. MSG: %v", i)
|
||||
return Failed(100)
|
||||
}
|
||||
elem := bot.ConvertStringMessage(str, true)
|
||||
// fix at display
|
||||
for _, e := range elem {
|
||||
if at, ok := e.(*message.AtElement); ok && at.Target != 0 {
|
||||
at.Display = "@" + func() string {
|
||||
mem := bot.Client.FindGroup(groupId).FindMember(at.Target)
|
||||
if mem != nil {
|
||||
return mem.DisplayName()
|
||||
}
|
||||
return strconv.FormatInt(at.Target, 10)
|
||||
}()
|
||||
}
|
||||
var elem []message.IMessageElement
|
||||
if autoEscape {
|
||||
elem = append(elem, message.NewText(str))
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, true)
|
||||
}
|
||||
fixAt(elem)
|
||||
mid := bot.SendGroupMessage(groupId, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100)
|
||||
@ -211,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
|
||||
func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}) MSG {
|
||||
func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}, autoEscape bool) MSG {
|
||||
var str string
|
||||
if m, ok := i.(gjson.Result); ok {
|
||||
if m.Type == gjson.JSON {
|
||||
@ -228,14 +242,18 @@ func (bot *CQBot) CQSendPrivateMessage(userId int64, i interface{}) MSG {
|
||||
}
|
||||
return m.Raw
|
||||
}()
|
||||
}
|
||||
if s, ok := i.(string); ok {
|
||||
} else if s, ok := i.(string); ok {
|
||||
str = s
|
||||
}
|
||||
if str == "" {
|
||||
return Failed(100)
|
||||
}
|
||||
elem := bot.ConvertStringMessage(str, false)
|
||||
var elem []message.IMessageElement
|
||||
if autoEscape {
|
||||
elem = append(elem, message.NewText(str))
|
||||
} else {
|
||||
elem = bot.ConvertStringMessage(str, false)
|
||||
}
|
||||
mid := bot.SendPrivateMessage(userId, &message.SendingMessage{Elements: elem})
|
||||
if mid == -1 {
|
||||
return Failed(100)
|
||||
@ -328,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
|
||||
func (bot *CQBot) CQProcessGroupRequest(flag, subType string, approve bool) MSG {
|
||||
func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bool) MSG {
|
||||
if subType == "add" {
|
||||
req, ok := bot.joinReqCache.Load(flag)
|
||||
if !ok {
|
||||
@ -338,7 +356,7 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType string, approve bool) MSG
|
||||
if approve {
|
||||
req.(*client.UserJoinGroupRequest).Accept()
|
||||
} else {
|
||||
req.(*client.UserJoinGroupRequest).Reject()
|
||||
req.(*client.UserJoinGroupRequest).Reject(false, reason)
|
||||
}
|
||||
return OK(nil)
|
||||
}
|
||||
@ -348,7 +366,7 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType string, approve bool) MSG
|
||||
if approve {
|
||||
req.(*client.GroupInvitedRequest).Accept()
|
||||
} else {
|
||||
req.(*client.GroupInvitedRequest).Reject()
|
||||
req.(*client.GroupInvitedRequest).Reject(false, reason)
|
||||
}
|
||||
return OK(nil)
|
||||
}
|
||||
@ -365,6 +383,61 @@ func (bot *CQBot) CQDeleteMessage(messageId int32) MSG {
|
||||
return OK(nil)
|
||||
}
|
||||
|
||||
// https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
|
||||
func (bot *CQBot) CQGetGroupHonorInfo(groupId int64, t string) MSG {
|
||||
msg := MSG{"group_id": groupId}
|
||||
convertMem := func(memList []client.HonorMemberInfo) (ret []MSG) {
|
||||
for _, mem := range memList {
|
||||
ret = append(ret, MSG{
|
||||
"user_id": mem.Uin,
|
||||
"nickname": mem.Name,
|
||||
"avatar": mem.Avatar,
|
||||
"description": mem.Desc,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
if t == "talkative" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Talkative); err == nil {
|
||||
if honor.CurrentTalkative.Uin != 0 {
|
||||
msg["current_talkative"] = MSG{
|
||||
"user_id": honor.CurrentTalkative.Uin,
|
||||
"nickname": honor.CurrentTalkative.Name,
|
||||
"avatar": honor.CurrentTalkative.Avatar,
|
||||
"day_count": honor.CurrentTalkative.DayCount,
|
||||
}
|
||||
}
|
||||
msg["talkative_list"] = convertMem(honor.TalkativeList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "performer" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Performer); err == nil {
|
||||
msg["performer_lis"] = convertMem(honor.ActorList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "legend" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Legend); err == nil {
|
||||
msg["legend_list"] = convertMem(honor.LegendList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "strong_newbie" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.StrongNewbie); err == nil {
|
||||
msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList)
|
||||
}
|
||||
}
|
||||
|
||||
if t == "emotion" || t == "all" {
|
||||
if honor, err := bot.Client.GetGroupHonorInfo(groupId, client.Emotion); err == nil {
|
||||
msg["emotion_list"] = convertMem(honor.EmotionList)
|
||||
}
|
||||
}
|
||||
|
||||
return OK(msg)
|
||||
}
|
||||
|
||||
// https://cqhttp.cc/docs/4.15/#/API?id=-handle_quick_operation-%E5%AF%B9%E4%BA%8B%E4%BB%B6%E6%89%A7%E8%A1%8C%E5%BF%AB%E9%80%9F%E6%93%8D%E4%BD%9C
|
||||
// https://github.com/richardchien/coolq-http-api/blob/master/src/cqhttp/plugins/web/http.cpp#L376
|
||||
func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
@ -374,6 +447,7 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
msgType := context.Get("message_type").Str
|
||||
reply := operation.Get("reply")
|
||||
if reply.Exists() {
|
||||
autoEscape := global.EnsureBool(operation.Get("auto_escape"), false)
|
||||
/*
|
||||
at := true
|
||||
if operation.Get("at_sender").Exists() {
|
||||
@ -382,10 +456,10 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
*/
|
||||
// TODO: 处理at字段
|
||||
if msgType == "group" {
|
||||
bot.CQSendGroupMessage(context.Get("group_id").Int(), reply)
|
||||
bot.CQSendGroupMessage(context.Get("group_id").Int(), reply, autoEscape)
|
||||
}
|
||||
if msgType == "private" {
|
||||
bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply)
|
||||
bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply, autoEscape)
|
||||
}
|
||||
}
|
||||
if msgType == "group" {
|
||||
@ -415,7 +489,7 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG {
|
||||
bot.CQProcessFriendRequest(context.Get("flag").Str, operation.Get("approve").Bool())
|
||||
}
|
||||
if reqType == "group" {
|
||||
bot.CQProcessGroupRequest(context.Get("flag").Str, context.Get("sub_type").Str, operation.Get("approve").Bool())
|
||||
bot.CQProcessGroupRequest(context.Get("flag").Str, context.Get("sub_type").Str, operation.Get("reason").Str, operation.Get("approve").Bool())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -429,11 +503,19 @@ func (bot *CQBot) CQGetImage(file string) MSG {
|
||||
if b, err := ioutil.ReadFile(path.Join(global.IMAGE_PATH, file)); err == nil {
|
||||
r := binary.NewReader(b)
|
||||
r.ReadBytes(16)
|
||||
return OK(MSG{
|
||||
msg := MSG{
|
||||
"size": r.ReadInt32(),
|
||||
"filename": 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)
|
||||
}
|
||||
@ -508,6 +590,7 @@ func (bot *CQBot) CQGetVersionInfo() MSG {
|
||||
"plugin_build_configuration": "release",
|
||||
"runtime_version": runtime.Version(),
|
||||
"runtime_os": runtime.GOOS,
|
||||
"version": Version,
|
||||
})
|
||||
}
|
||||
|
||||
|
73
coolq/bot.go
73
coolq/bot.go
@ -5,17 +5,19 @@ import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"path"
|
||||
"sync"
|
||||
"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"
|
||||
"github.com/xujiajun/nutsdb"
|
||||
"hash/crc32"
|
||||
"math/rand"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CQBot struct {
|
||||
@ -27,10 +29,13 @@ type CQBot struct {
|
||||
invitedReqCache sync.Map
|
||||
joinReqCache sync.Map
|
||||
tempMsgCache sync.Map
|
||||
oneWayMsgCache sync.Map
|
||||
}
|
||||
|
||||
type MSG map[string]interface{}
|
||||
|
||||
var ForceFragmented = false
|
||||
|
||||
func NewQQBot(cli *client.QQClient, conf *global.JsonConfig) *CQBot {
|
||||
bot := &CQBot{
|
||||
Client: cli,
|
||||
@ -64,6 +69,24 @@ func NewQQBot(cli *client.QQClient, conf *global.JsonConfig) *CQBot {
|
||||
bot.Client.OnNewFriendAdded(bot.friendAddedEvent)
|
||||
bot.Client.OnGroupInvited(bot.groupInvitedEvent)
|
||||
bot.Client.OnUserWantJoinGroup(bot.groupJoinReqEvent)
|
||||
go func() {
|
||||
i := conf.HeartbeatInterval
|
||||
if i < 1 {
|
||||
log.Warn("警告: 心跳功能已关闭,若非预期,请检查配置文件。")
|
||||
return
|
||||
}
|
||||
for {
|
||||
time.Sleep(time.Second * i)
|
||||
bot.dispatchEventMessage(MSG{
|
||||
"time": time.Now().Unix(),
|
||||
"self_id": bot.Client.Uin,
|
||||
"post_type": "meta_event",
|
||||
"meta_event_type": "heartbeat",
|
||||
"status": nil,
|
||||
"interval": 1000 * i,
|
||||
})
|
||||
}
|
||||
}()
|
||||
return bot
|
||||
}
|
||||
|
||||
@ -95,7 +118,6 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
|
||||
var newElem []message.IMessageElement
|
||||
for _, elem := range m.Elements {
|
||||
if i, ok := elem.(*message.ImageElement); ok {
|
||||
_, _ = bot.Client.UploadGroupImage(int64(rand.Intn(11451419)), i.Data)
|
||||
gm, err := bot.Client.UploadGroupImage(groupId, i.Data)
|
||||
if err != nil {
|
||||
log.Warnf("警告: 群 %v 消息图片上传失败: %v", groupId, err)
|
||||
@ -116,7 +138,11 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
|
||||
newElem = append(newElem, elem)
|
||||
}
|
||||
m.Elements = newElem
|
||||
ret := bot.Client.SendGroupMessage(groupId, m)
|
||||
ret := bot.Client.SendGroupMessage(groupId, m, ForceFragmented)
|
||||
if ret == nil || ret.Id == -1 {
|
||||
log.Warnf("群消息发送失败: 账号可能被风控.")
|
||||
return -1
|
||||
}
|
||||
return bot.InsertGroupMessage(ret)
|
||||
}
|
||||
|
||||
@ -135,15 +161,25 @@ func (bot *CQBot) SendPrivateMessage(target int64, m *message.SendingMessage) in
|
||||
newElem = append(newElem, elem)
|
||||
}
|
||||
m.Elements = newElem
|
||||
var id int32
|
||||
var id int32 = -1
|
||||
if bot.Client.FindFriend(target) != nil {
|
||||
id = bot.Client.SendPrivateMessage(target, m).Id
|
||||
} else {
|
||||
if code, ok := bot.tempMsgCache.Load(target); ok {
|
||||
id = bot.Client.SendTempMessage(code.(int64), target, m).Id
|
||||
} else {
|
||||
return -1
|
||||
msg := bot.Client.SendPrivateMessage(target, m)
|
||||
if msg != nil {
|
||||
id = msg.Id
|
||||
}
|
||||
} else if code, ok := bot.tempMsgCache.Load(target); ok {
|
||||
msg := bot.Client.SendTempMessage(code.(int64), target, m)
|
||||
if msg != nil {
|
||||
id = msg.Id
|
||||
}
|
||||
} else if _, ok := bot.oneWayMsgCache.Load(target); ok {
|
||||
msg := bot.Client.SendPrivateMessage(target, m)
|
||||
if msg != nil {
|
||||
id = msg.Id
|
||||
}
|
||||
}
|
||||
if id == -1 {
|
||||
return -1
|
||||
}
|
||||
return ToGlobalId(target, id)
|
||||
}
|
||||
@ -186,6 +222,12 @@ func (bot *CQBot) Release() {
|
||||
}
|
||||
|
||||
func (bot *CQBot) dispatchEventMessage(m MSG) {
|
||||
payload := gjson.Parse(m.ToJson())
|
||||
filter := global.GetFilter()
|
||||
if filter != nil && (*filter).Eval(payload) == false {
|
||||
log.Debug("Event filtered!")
|
||||
return
|
||||
}
|
||||
for _, f := range bot.events {
|
||||
fn := f
|
||||
go func() {
|
||||
@ -204,6 +246,9 @@ func formatGroupName(group *client.GroupInfo) string {
|
||||
}
|
||||
|
||||
func formatMemberName(mem *client.GroupMemberInfo) string {
|
||||
if mem == nil {
|
||||
return "未知"
|
||||
}
|
||||
return fmt.Sprintf("%s(%d)", mem.DisplayName(), mem.Uin)
|
||||
}
|
||||
|
||||
|
118
coolq/cqcode.go
118
coolq/cqcode.go
@ -1,15 +1,11 @@
|
||||
package coolq
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"path"
|
||||
@ -17,12 +13,20 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/binary"
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var matchReg = regexp.MustCompile(`\[CQ:\w+?.*?]`)
|
||||
var typeReg = regexp.MustCompile(`\[CQ:(\w+)`)
|
||||
var paramReg = regexp.MustCompile(`,([\w\-.]+?)=([^,\]]+)`)
|
||||
|
||||
var IgnoreInvalidCQCode = false
|
||||
|
||||
func ToArrayMessage(e []message.IMessageElement, code int64, raw ...bool) (r []MSG) {
|
||||
ur := false
|
||||
if len(raw) != 0 {
|
||||
@ -110,6 +114,15 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
|
||||
if len(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 {
|
||||
switch o := elem.(type) {
|
||||
case *message.TextElement:
|
||||
@ -120,8 +133,6 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st
|
||||
continue
|
||||
}
|
||||
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:
|
||||
r += fmt.Sprintf("[CQ:forward,id=%s]", o.ResId)
|
||||
case *message.FaceElement:
|
||||
@ -190,8 +201,12 @@ func (bot *CQBot) ConvertStringMessage(m string, group bool) (r []message.IMessa
|
||||
}
|
||||
elem, err := bot.ToElement(t, d, group)
|
||||
if err != nil {
|
||||
log.Warnf("转换CQ码到MiraiGo Element时出现错误: %v 将原样发送.", err)
|
||||
if !IgnoreInvalidCQCode {
|
||||
log.Warnf("转换CQ码 %v 到MiraiGo Element时出现错误: %v 将原样发送.", code, err)
|
||||
r = append(r, message.NewText(code))
|
||||
} else {
|
||||
log.Warnf("转换CQ码 %v 到MiraiGo Element时出现错误: %v 将忽略.", code, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
r = append(r, elem)
|
||||
@ -261,10 +276,23 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
case "image":
|
||||
f := d["file"]
|
||||
if strings.HasPrefix(f, "http") || strings.HasPrefix(f, "https") {
|
||||
cache := d["cache"]
|
||||
if cache == "" {
|
||||
cache = "1"
|
||||
}
|
||||
hash := md5.Sum([]byte(f))
|
||||
cacheFile := path.Join(global.CACHE_PATH, hex.EncodeToString(hash[:])+".cache")
|
||||
if global.PathExists(cacheFile) && cache == "1" {
|
||||
b, err := ioutil.ReadFile(cacheFile)
|
||||
if err == nil {
|
||||
return message.NewImage(b), nil
|
||||
}
|
||||
}
|
||||
b, err := global.GetBytes(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = ioutil.WriteFile(cacheFile, b, 0644)
|
||||
return message.NewImage(b), nil
|
||||
}
|
||||
if strings.HasPrefix(f, "base64") {
|
||||
@ -308,6 +336,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
}
|
||||
var size int32
|
||||
var hash []byte
|
||||
var url string
|
||||
if path.Ext(rawPath) == ".cqimg" {
|
||||
for _, line := range strings.Split(global.ReadAllText(rawPath), "\n") {
|
||||
kv := strings.SplitN(line, "=", 2)
|
||||
@ -323,8 +352,13 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
r := binary.NewReader(b)
|
||||
hash = r.ReadBytes(16)
|
||||
size = r.ReadInt32()
|
||||
r.ReadString()
|
||||
url = r.ReadString()
|
||||
}
|
||||
if size == 0 {
|
||||
if url != "" {
|
||||
return bot.ToElement(t, map[string]string{"file": url}, group)
|
||||
}
|
||||
return nil, errors.New("img size is 0")
|
||||
}
|
||||
if len(hash) != 16 {
|
||||
@ -333,12 +367,18 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
if group {
|
||||
rsp, err := bot.Client.QueryGroupImage(1, hash, size)
|
||||
if err != nil {
|
||||
if url != "" {
|
||||
return bot.ToElement(t, map[string]string{"file": url}, group)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
rsp, err := bot.Client.QueryFriendImage(1, hash, size)
|
||||
if err != nil {
|
||||
if url != "" {
|
||||
return bot.ToElement(t, map[string]string{"file": url}, group)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return rsp, nil
|
||||
@ -385,7 +425,7 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
}
|
||||
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 &message.VoiceElement{Data: data}, nil
|
||||
@ -404,6 +444,66 @@ func (bot *CQBot) ToElement(t string, d map[string]string, group bool) (message.
|
||||
return message.NewAt(t), nil
|
||||
case "share":
|
||||
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"] == "163" {
|
||||
info, err := global.NeteaseMusicSongInfo(d["id"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !info.Exists() {
|
||||
return nil, errors.New("song not found")
|
||||
}
|
||||
name := info.Get("name").Str
|
||||
artistName := ""
|
||||
if info.Get("artists.0").Exists() {
|
||||
artistName = info.Get("artists.0.name").Str
|
||||
}
|
||||
xml := fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="2" templateID="1" action="web" brief="[分享] %s" sourceMsgId="0" url="http://music.163.com/m/song/%s" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2"><audio cover="%s?param=90y90" src="https://music.163.com/song/media/outer/url?id=%s.mp3" /><title>%s</title><summary>%s</summary></item><source name="网易云音乐" icon="https://pic.rmb.bdstatic.com/911423bee2bef937975b29b265d737b3.png" url="http://web.p.qq.com/qqmpmobile/aio/app.html?id=100495085" action="app" a_actionData="com.netease.cloudmusic" i_actionData="tencent100495085://" appid="100495085" /></msg>`,
|
||||
name, d["id"], info.Get("album.picUrl").Str, d["id"], name, artistName)
|
||||
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"])
|
||||
case "xml":
|
||||
resId := d["resid"]
|
||||
template := CQCodeEscapeValue(d["data"])
|
||||
//println(template)
|
||||
i, _ := strconv.ParseInt(resId, 10, 64)
|
||||
msg := global.NewXmlMsg(template, i)
|
||||
return msg, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported cq code: " + t)
|
||||
}
|
||||
|
@ -32,6 +32,9 @@ func ToFormattedMessage(e []message.IMessageElement, code int64, raw ...bool) (r
|
||||
func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMessage) {
|
||||
bot.checkMedia(m.Elements)
|
||||
cqm := ToStringMessage(m.Elements, 0, true)
|
||||
if !m.Sender.IsFriend {
|
||||
bot.oneWayMsgCache.Store(m.Sender.Uin, "")
|
||||
}
|
||||
log.Infof("收到好友 %v(%v) 的消息: %v", m.Sender.DisplayName(), m.Sender.Uin, cqm)
|
||||
fm := MSG{
|
||||
"post_type": "message",
|
||||
|
@ -25,12 +25,15 @@ go-cqhttp 支持导入CQHTTP的配置文件, 具体步骤为:
|
||||
"relogin": false,
|
||||
"relogin_delay": 0,
|
||||
"post_message_format": "string",
|
||||
"ignore_invalid_cqcode": false,
|
||||
"force_fragmented": true,
|
||||
"heartbeat_interval": 5,
|
||||
"http_config": {
|
||||
"enabled": true,
|
||||
"host": "0.0.0.0",
|
||||
"port": 5700,
|
||||
"timeout": 5,
|
||||
"post_urls": {"url:port": "secret"},
|
||||
"post_urls": {"url:port": "secret"}
|
||||
},
|
||||
"ws_config": {
|
||||
"enabled": true,
|
||||
@ -59,10 +62,19 @@ go-cqhttp 支持导入CQHTTP的配置文件, 具体步骤为:
|
||||
| access_token | string | 同CQHTTP的 `access_token` 用于身份验证 |
|
||||
| relogin | bool | 是否自动重新登录 |
|
||||
| relogin_delay | int | 重登录延时(秒) |
|
||||
| post_message_format | string | 上报信息类型 |
|
||||
| ignore_invalid_cqcode| bool | 是否忽略错误的CQ码 |
|
||||
| force_fragmented | bool | 是否强制分片发送群长消息 |
|
||||
| heartbeat_interval | int64 | 心跳间隔时间,单位秒,若0则关闭心跳 |
|
||||
| http_config | object | HTTP API配置 |
|
||||
| ws_config | object | Websocket API 配置 |
|
||||
| ws_reverse_servers | object[] | 反向 Websocket API 配置 |
|
||||
| log_level | string | 指定日志收集级别,将收集的日志单独存放到固定文件中,便于查看日志线索 当前支持 warn,error|
|
||||
|
||||
> 注: 开启密码加密后程序将在每次启动时要求输入解密密钥, 密钥错误会导致登录时提示密码错误.
|
||||
> 解密后密码将储存在内存中,用于自动重连等功能. 所以此加密并不能防止内存读取.
|
||||
> 解密密钥在使用完成后并不会留存在内存中, 所以可用相对简单的字符串作为密钥
|
||||
|
||||
> 注2: 分片发送为原酷Q发送长消息的老方案, 发送速度更优/兼容性更好。关闭后将优先使用新方案, 能发送更长的消息, 但发送速度更慢,在部分老客户端将无法解析.
|
||||
|
||||
> 注3:关闭心跳服务可能引起断线,请谨慎关闭
|
@ -119,6 +119,51 @@ Type: `node`
|
||||
]
|
||||
````
|
||||
|
||||
### xml支持
|
||||
|
||||
Type: `xml`
|
||||
|
||||
范围: **发送**
|
||||
|
||||
参数:
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
| ------ | ------ | ------------------------------------------------------------ |
|
||||
| data | string | xml内容,xml中的value部分,记得实体化处理|
|
||||
| resid | int32 | 可以不填|
|
||||
|
||||
示例: `[CQ:xml,data=xxxx]`
|
||||
|
||||
####一些xml样例
|
||||
####ps:重要:xml中的value部分,记得html实体化处理后,再打加入到cq码中
|
||||
#### qq音乐
|
||||
```xml
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="2" templateID="1" action="web" brief="[分享] 十年" sourceMsgId="0" url="https://i.y.qq.com/v8/playsong.html?_wv=1&songid=4830342&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/26/500_albumpic_89526_0.jpg" src="http://ws.stream.qqmusic.qq.com/C400003mAan70zUy5O.m4a?guid=1535153710&vkey=D5315B8C0603653592AD4879A8A3742177F59D582A7A86546E24DD7F282C3ACF81526C76E293E57EA1E42CF19881C561275D919233333ADE&uin=&fromtag=3" /><title>十年</title><summary>陈奕迅</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>
|
||||
```
|
||||
#### 网易音乐
|
||||
```xml
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="2" templateID="1" action="web" brief="[分享] 十年" sourceMsgId="0" url="http://music.163.com/m/song/409650368" flag="0" adverSign="0" multiMsgFlag="0" ><item layout="2"><audio cover="http://p2.music.126.net/g-Qgb9ibk9Wp_0HWra0xQQ==/16636710440565853.jpg?param=90y90" src="https://music.163.com/song/media/outer/url?id=409650368.mp3" /><title>十年</title><summary>黄梦之</summary></item><source name="网易云音乐" icon="https://pic.rmb.bdstatic.com/911423bee2bef937975b29b265d737b3.png" url="http://web.p.qq.com/qqmpmobile/aio/app.html?id=1101079856" action="app" a_actionData="com.netease.cloudmusic" i_actionData="tencent100495085://" appid="100495085" /></msg>
|
||||
```
|
||||
|
||||
#### 卡片消息1
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<msg serviceID="1">
|
||||
<item><title>生死8秒!女司机高速急刹,他一个操作救下一车性命</title></item>
|
||||
<source name="官方认证消息" icon="https://qzs.qq.com/ac/qzone_v5/client/auth_icon.png" action="" appid="-1" />
|
||||
</msg>
|
||||
```
|
||||
|
||||
#### 卡片消息2
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<msg serviceID="1">
|
||||
<item layout="4">
|
||||
<title>test title</title>
|
||||
<picture cover="http://url.cn/5CEwIUy"/>
|
||||
</item>
|
||||
</msg>
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
@ -2,6 +2,8 @@ package global
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -14,12 +16,15 @@ type JsonConfig struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ReLogin bool `json:"relogin"`
|
||||
ReLoginDelay int `json:"relogin_delay"`
|
||||
AsyncLoad bool `json:"async_load"`
|
||||
IgnoreInvalidCQCode bool `json:"ignore_invalid_cqcode"`
|
||||
ForceFragmented bool `json:"force_fragmented"`
|
||||
HeartbeatInterval time.Duration `json:"heartbeat_interval"`
|
||||
HttpConfig *GoCQHttpConfig `json:"http_config"`
|
||||
WSConfig *GoCQWebsocketConfig `json:"ws_config"`
|
||||
ReverseServers []*GoCQReverseWebsocketConfig `json:"ws_reverse_servers"`
|
||||
PostMessageFormat string `json:"post_message_format"`
|
||||
Debug bool `json:"debug"`
|
||||
LogLevel string `json:"log_level"`
|
||||
}
|
||||
|
||||
type CQHttpApiConfig struct {
|
||||
@ -69,6 +74,7 @@ func DefaultConfig() *JsonConfig {
|
||||
ReLogin: true,
|
||||
ReLoginDelay: 3,
|
||||
PostMessageFormat: "string",
|
||||
ForceFragmented: true,
|
||||
HttpConfig: &GoCQHttpConfig{
|
||||
Enabled: true,
|
||||
Host: "0.0.0.0",
|
||||
|
267
global/filter.go
Normal file
267
global/filter.go
Normal 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
|
||||
}
|
23
global/fs.go
23
global/fs.go
@ -1,17 +1,23 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var IMAGE_PATH = path.Join("data", "images")
|
||||
var (
|
||||
IMAGE_PATH = path.Join("data", "images")
|
||||
VOICE_PATH = path.Join("data", "voices")
|
||||
VIDEO_PATH = path.Join("data", "videos")
|
||||
CACHE_PATH = path.Join("data", "cache")
|
||||
|
||||
var VOICE_PATH = path.Join("data", "voices")
|
||||
|
||||
var VIDEO_PATH = path.Join("data", "videos")
|
||||
HEADER_AMR = []byte("#!AMR")
|
||||
HEADER_SILK = []byte("\x02#!SILK_V3")
|
||||
)
|
||||
|
||||
func PathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
@ -36,9 +42,6 @@ func Check(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func IsAMR(b []byte) bool {
|
||||
if len(b) <= 6 {
|
||||
return false
|
||||
}
|
||||
return b[0] == 0x23 && b[1] == 0x21 && b[2] == 0x41 && b[3] == 0x4D && b[4] == 0x52 // amr file header
|
||||
func IsAMRorSILK(b []byte) bool {
|
||||
return bytes.HasPrefix(b, HEADER_AMR) || bytes.HasPrefix(b, HEADER_SILK)
|
||||
}
|
||||
|
@ -3,9 +3,14 @@ package global
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Mrs4s/MiraiGo/message"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func GetBytes(url string) ([]byte, error) {
|
||||
@ -32,3 +37,35 @@ func GetBytes(url string) ([]byte, error) {
|
||||
}
|
||||
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¬ice=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
|
||||
}
|
||||
|
||||
func NeteaseMusicSongInfo(id string) (gjson.Result, error) {
|
||||
d, err := GetBytes(fmt.Sprintf("http://music.163.com/api/song/detail/?id=%s&ids=%%5B%s%%5D", id, id))
|
||||
if err != nil {
|
||||
return gjson.Result{}, err
|
||||
}
|
||||
return gjson.ParseBytes(d).Get("songs.0"), nil
|
||||
}
|
||||
|
||||
func NewXmlMsg(template string, ResId int64) *message.ServiceElement {
|
||||
var serviceid string
|
||||
if ResId == 0 {
|
||||
serviceid = "2" //默认值2
|
||||
} else {
|
||||
serviceid = strconv.FormatInt(ResId, 10)
|
||||
}
|
||||
//println(serviceid)
|
||||
return &message.ServiceElement{
|
||||
Id: int32(ResId),
|
||||
Content: template,
|
||||
ResId: serviceid,
|
||||
SubType: "xml",
|
||||
}
|
||||
}
|
||||
|
50
global/param.go
Normal file
50
global/param.go
Normal file
@ -0,0 +1,50 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"github.com/tidwall/gjson"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var trueSet = map[string]struct{}{
|
||||
"true": {},
|
||||
"yes": {},
|
||||
"1": {},
|
||||
}
|
||||
|
||||
var falseSet = map[string]struct{}{
|
||||
"false": {},
|
||||
"no": {},
|
||||
"0": {},
|
||||
}
|
||||
|
||||
func EnsureBool(p interface{}, defaultVal bool) bool {
|
||||
var str string
|
||||
if b, ok := p.(bool); ok {
|
||||
return b
|
||||
}
|
||||
if j, ok := p.(gjson.Result); ok {
|
||||
if !j.Exists() {
|
||||
return defaultVal
|
||||
}
|
||||
if j.Type == gjson.True {
|
||||
return true
|
||||
}
|
||||
if j.Type == gjson.False {
|
||||
return false
|
||||
}
|
||||
if j.Type != gjson.String {
|
||||
return defaultVal
|
||||
}
|
||||
str = j.Str
|
||||
} else if s, ok := p.(string); ok {
|
||||
str = s
|
||||
}
|
||||
str = strings.ToLower(str)
|
||||
if _, ok := trueSet[str]; ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := falseSet[str]; ok {
|
||||
return false
|
||||
}
|
||||
return defaultVal
|
||||
}
|
8
go.mod
8
go.mod
@ -3,18 +3,22 @@ module github.com/Mrs4s/go-cqhttp
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200816111850-988a766ae224
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200827182935-51e155ef20da
|
||||
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
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/strftime v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
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/xujiajun/nutsdb v0.5.0
|
||||
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
|
||||
)
|
||||
|
12
go.sum
12
go.sum
@ -1,9 +1,7 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200813091456-988a010b51df h1:ERLrnv7bONrg4NqvC8AWhtEgCZk97uCZdRpQS4gF8UE=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200813091456-988a010b51df/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200816111850-988a766ae224 h1:tlWc7RpBCh5VhT0H6wzm/knxj2PpAV3J7wQyieF0nkk=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200816111850-988a766ae224/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200827182935-51e155ef20da h1:T2Qz4w6sMrBxw+oiwbUa/c996jWYulCAtM+x1L0l3R8=
|
||||
github.com/Mrs4s/MiraiGo v0.0.0-20200827182935-51e155ef20da/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@ -12,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/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/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/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
@ -50,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/guonaihong/gout v0.1.1 h1:2i3eqQ1KUhTlj7AFeIHqVUFku5QwUhwE2wNgYTVpbxQ=
|
||||
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.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=
|
||||
@ -75,6 +76,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
@ -85,6 +88,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/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/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/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
|
61
main.go
61
main.go
@ -13,6 +13,7 @@ import (
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/Mrs4s/go-cqhttp/server"
|
||||
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||
"github.com/rifflock/lfshook"
|
||||
log "github.com/sirupsen/logrus"
|
||||
easy "github.com/t-tomalak/logrus-easy-formatter"
|
||||
asciiart "github.com/yinghau76/go-ascii-art"
|
||||
@ -51,6 +52,11 @@ func init() {
|
||||
log.Fatalf("创建视频缓存文件夹失败: %v", err)
|
||||
}
|
||||
}
|
||||
if !global.PathExists(global.CACHE_PATH) {
|
||||
if err := os.MkdirAll(global.CACHE_PATH, 0755); err != nil {
|
||||
log.Fatalf("创建发送图片缓存文件夹失败: %v", err)
|
||||
}
|
||||
}
|
||||
if global.PathExists("cqhttp.json") {
|
||||
log.Info("发现 cqhttp.json 将在五秒后尝试导入配置,按 Ctrl+C 取消.")
|
||||
log.Warn("警告: 该操作会删除 cqhttp.json 并覆盖 config.json 文件.")
|
||||
@ -128,9 +134,50 @@ func main() {
|
||||
time.Sleep(time.Second * 5)
|
||||
return
|
||||
}
|
||||
|
||||
// log classified by level
|
||||
// Collect all records up to the specified level (default level: warn)
|
||||
logLevel := conf.LogLevel
|
||||
if logLevel != "" {
|
||||
date := time.Now().Format("2006-01-02")
|
||||
var logPathMap lfshook.PathMap
|
||||
switch conf.LogLevel {
|
||||
case "warn":
|
||||
logPathMap = lfshook.PathMap{
|
||||
log.WarnLevel: path.Join("logs", date+"-warn.log"),
|
||||
log.ErrorLevel: path.Join("logs", date+"-warn.log"),
|
||||
log.FatalLevel: path.Join("logs", date+"-warn.log"),
|
||||
log.PanicLevel: path.Join("logs", date+"-warn.log"),
|
||||
}
|
||||
case "error":
|
||||
logPathMap = lfshook.PathMap{
|
||||
log.ErrorLevel: path.Join("logs", date+"-error.log"),
|
||||
log.FatalLevel: path.Join("logs", date+"-error.log"),
|
||||
log.PanicLevel: path.Join("logs", date+"-error.log"),
|
||||
}
|
||||
default:
|
||||
logPathMap = lfshook.PathMap{
|
||||
log.WarnLevel: path.Join("logs", date+"-warn.log"),
|
||||
log.ErrorLevel: path.Join("logs", date+"-warn.log"),
|
||||
log.FatalLevel: path.Join("logs", date+"-warn.log"),
|
||||
log.PanicLevel: path.Join("logs", date+"-warn.log"),
|
||||
}
|
||||
}
|
||||
|
||||
log.AddHook(lfshook.NewHook(
|
||||
logPathMap,
|
||||
&easy.Formatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
LogFormat: "[%time%] [%lvl%]: %msg% \n",
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
log.Info("当前版本:", coolq.Version)
|
||||
if conf.Debug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Warnf("已开启Debug模式.")
|
||||
log.Debugf("开发交流群: 192548878")
|
||||
}
|
||||
if !global.PathExists("device.json") {
|
||||
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
|
||||
@ -165,6 +212,16 @@ func main() {
|
||||
time.Sleep(time.Second * 5)
|
||||
log.Info("开始尝试登录并同步消息...")
|
||||
cli := client.NewClient(conf.Uin, conf.Password)
|
||||
cli.OnLog(func(c *client.QQClient, e *client.LogEvent) {
|
||||
switch e.Type {
|
||||
case "INFO":
|
||||
log.Info("Protocol -> " + e.Message)
|
||||
case "ERROR":
|
||||
log.Error("Protocol -> " + e.Message)
|
||||
case "DEBUG":
|
||||
log.Debug("Protocol -> " + e.Message)
|
||||
}
|
||||
})
|
||||
rsp, err := cli.Login()
|
||||
for {
|
||||
global.Check(err)
|
||||
@ -195,7 +252,7 @@ func main() {
|
||||
global.Check(cli.ReloadFriendList())
|
||||
log.Infof("共加载 %v 个好友.", len(cli.FriendList))
|
||||
log.Infof("开始加载群列表...")
|
||||
global.Check(cli.ReloadGroupList(conf.AsyncLoad))
|
||||
global.Check(cli.ReloadGroupList())
|
||||
log.Infof("共加载 %v 个群.", len(cli.GroupList))
|
||||
b := coolq.NewQQBot(cli, conf)
|
||||
if conf.PostMessageFormat != "string" && conf.PostMessageFormat != "array" {
|
||||
@ -204,6 +261,8 @@ func main() {
|
||||
} else {
|
||||
coolq.SetMessageFormat(conf.PostMessageFormat)
|
||||
}
|
||||
coolq.IgnoreInvalidCQCode = conf.IgnoreInvalidCQCode
|
||||
coolq.ForceFragmented = conf.ForceFragmented
|
||||
if conf.HttpConfig != nil && conf.HttpConfig.Enabled {
|
||||
server.HttpServer.Run(fmt.Sprintf("%s:%d", conf.HttpConfig.Host, conf.HttpConfig.Port), conf.AccessToken, b)
|
||||
for k, v := range conf.HttpConfig.PostUrls {
|
||||
|
@ -4,11 +4,13 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Mrs4s/go-cqhttp/coolq"
|
||||
"github.com/Mrs4s/go-cqhttp/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/guonaihong/gout"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -39,7 +41,7 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) {
|
||||
c.Status(404)
|
||||
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()
|
||||
if err != nil {
|
||||
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("/get_image", s.GetImage)
|
||||
s.engine.Any("/get_image_async", s.GetImage)
|
||||
|
||||
s.engine.Any("/get_forward_msg", s.GetForwardMessage)
|
||||
|
||||
s.engine.Any("/get_group_msg", s.GetGroupMessage)
|
||||
s.engine.Any("/get_group_msg_async", s.GetGroupMessage)
|
||||
|
||||
s.engine.Any("/get_group_honor_info", s.GetGroupHonorInfo)
|
||||
|
||||
s.engine.Any("/can_send_image", s.CanSendImage)
|
||||
s.engine.Any("/can_send_image_async", s.CanSendImage)
|
||||
@ -156,7 +158,13 @@ func (s *httpServer) Run(addr, authToken string, bot *coolq.CQBot) {
|
||||
|
||||
go func() {
|
||||
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) {
|
||||
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) {
|
||||
@ -249,21 +258,23 @@ func (s *httpServer) SendMessage(c *gin.Context) {
|
||||
func (s *httpServer) SendPrivateMessage(c *gin.Context) {
|
||||
uid, _ := strconv.ParseInt(getParam(c, "user_id"), 10, 64)
|
||||
msg, t := getParamWithType(c, "message")
|
||||
autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
|
||||
if t == gjson.JSON {
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg)))
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, gjson.Parse(msg), autoEscape))
|
||||
return
|
||||
}
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, msg))
|
||||
c.JSON(200, s.bot.CQSendPrivateMessage(uid, msg, autoEscape))
|
||||
}
|
||||
|
||||
func (s *httpServer) SendGroupMessage(c *gin.Context) {
|
||||
gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64)
|
||||
msg, t := getParamWithType(c, "message")
|
||||
autoEscape := global.EnsureBool(getParam(c, "auto_escape"), false)
|
||||
if t == gjson.JSON {
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg)))
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, gjson.Parse(msg), autoEscape))
|
||||
return
|
||||
}
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, msg))
|
||||
c.JSON(200, s.bot.CQSendGroupMessage(gid, msg, autoEscape))
|
||||
}
|
||||
|
||||
func (s *httpServer) SendGroupForwardMessage(c *gin.Context) {
|
||||
@ -282,6 +293,11 @@ func (s *httpServer) GetGroupMessage(c *gin.Context) {
|
||||
c.JSON(200, s.bot.CQGetGroupMessage(int32(mid)))
|
||||
}
|
||||
|
||||
func (s *httpServer) GetGroupHonorInfo(c *gin.Context) {
|
||||
gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64)
|
||||
c.JSON(200, s.bot.CQGetGroupHonorInfo(gid, getParam(c, "type")))
|
||||
}
|
||||
|
||||
func (s *httpServer) ProcessFriendRequest(c *gin.Context) {
|
||||
flag := getParam(c, "flag")
|
||||
approve := getParamOrDefault(c, "approve", "true")
|
||||
@ -295,7 +311,7 @@ func (s *httpServer) ProcessGroupRequest(c *gin.Context) {
|
||||
subType = getParam(c, "type")
|
||||
}
|
||||
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) {
|
||||
@ -384,6 +400,7 @@ func getParamOrDefault(c *gin.Context, k, def string) string {
|
||||
return def
|
||||
}
|
||||
|
||||
|
||||
func getParam(c *gin.Context, k string) string {
|
||||
p, _ := getParamWithType(c, k)
|
||||
return p
|
||||
@ -395,12 +412,12 @@ func getParamWithType(c *gin.Context, k string) (string, gjson.Type) {
|
||||
}
|
||||
if c.Request.Method == "POST" {
|
||||
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 {
|
||||
return p, gjson.Null
|
||||
}
|
||||
}
|
||||
if h == "application/json" {
|
||||
if strings.Contains(h, "application/json") {
|
||||
if obj, ok := c.Get("json_body"); ok {
|
||||
res := obj.(gjson.Result).Get(k)
|
||||
if res.Exists() {
|
||||
|
@ -2,24 +2,24 @@ package server
|
||||
|
||||
import (
|
||||
"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"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"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 {
|
||||
bot *coolq.CQBot
|
||||
token string
|
||||
eventConn []*websocket.Conn
|
||||
pushLock *sync.Mutex
|
||||
eventConn []*websocketConn
|
||||
eventConnMutex sync.Mutex
|
||||
handshake string
|
||||
}
|
||||
|
||||
@ -28,9 +28,13 @@ type websocketClient struct {
|
||||
token string
|
||||
bot *coolq.CQBot
|
||||
|
||||
pushLock *sync.Mutex
|
||||
universalConn *wsc.Conn
|
||||
eventConn *wsc.Conn
|
||||
universalConn *websocketConn
|
||||
eventConn *websocketConn
|
||||
}
|
||||
|
||||
type websocketConn struct {
|
||||
*websocket.Conn
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var WebsocketServer = &websocketServer{}
|
||||
@ -42,7 +46,6 @@ var upgrader = websocket.Upgrader{
|
||||
|
||||
func (s *websocketServer) Run(addr, authToken string, b *coolq.CQBot) {
|
||||
s.token = authToken
|
||||
s.pushLock = new(sync.Mutex)
|
||||
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.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 {
|
||||
return &websocketClient{conf: conf, token: authToken, bot: b, pushLock: new(sync.Mutex)}
|
||||
return &websocketClient{conf: conf, token: authToken, bot: b}
|
||||
}
|
||||
|
||||
func (c *websocketClient) Run() {
|
||||
@ -79,18 +82,15 @@ func (c *websocketClient) Run() {
|
||||
|
||||
func (c *websocketClient) connectApi() {
|
||||
log.Infof("开始尝试连接到反向Websocket API服务器: %v", c.conf.ReverseApiUrl)
|
||||
wsConf, err := wsc.NewConfig(c.conf.ReverseApiUrl, c.conf.ReverseApiUrl)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket API服务器 %v 时出现致命错误: %v", c.conf.ReverseApiUrl, err)
|
||||
return
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{"API"},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
wsConf.Header["X-Client-Role"] = []string{"API"}
|
||||
wsConf.Header["X-Self-ID"] = []string{strconv.FormatInt(c.bot.Client.Uin, 10)}
|
||||
wsConf.Header["User-Agent"] = []string{"CQHttp/4.15.0"}
|
||||
if c.token != "" {
|
||||
wsConf.Header["Authorization"] = []string{"Token " + c.token}
|
||||
header["Authorization"] = []string{"Token " + c.token}
|
||||
}
|
||||
conn, err := wsc.DialConfig(wsConf)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseApiUrl, header)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket API服务器 %v 时出现错误: %v", c.conf.ReverseApiUrl, err)
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
@ -100,49 +100,44 @@ func (c *websocketClient) connectApi() {
|
||||
return
|
||||
}
|
||||
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() {
|
||||
log.Infof("开始尝试连接到反向Websocket Event服务器: %v", c.conf.ReverseEventUrl)
|
||||
wsConf, err := wsc.NewConfig(c.conf.ReverseEventUrl, c.conf.ReverseEventUrl)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket Event服务器 %v 时出现致命错误: %v", c.conf.ReverseApiUrl, err)
|
||||
return
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{"Event"},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
wsConf.Header["X-Client-Role"] = []string{"Event"}
|
||||
wsConf.Header["X-Self-ID"] = []string{strconv.FormatInt(c.bot.Client.Uin, 10)}
|
||||
wsConf.Header["User-Agent"] = []string{"CQHttp/4.15.0"}
|
||||
if c.token != "" {
|
||||
wsConf.Header["Authorization"] = []string{"Token " + c.token}
|
||||
header["Authorization"] = []string{"Token " + c.token}
|
||||
}
|
||||
conn, err := wsc.DialConfig(wsConf)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseEventUrl, header)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket API服务器 %v 时出现错误: %v", c.conf.ReverseApiUrl, err)
|
||||
log.Warnf("连接到反向Websocket Event服务器 %v 时出现错误: %v", c.conf.ReverseEventUrl, err)
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
c.connectApi()
|
||||
c.connectEvent()
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Infof("已连接到反向Websocket Event服务器 %v", c.conf.ReverseEventUrl)
|
||||
c.eventConn = conn
|
||||
c.eventConn = &websocketConn{Conn: conn}
|
||||
}
|
||||
|
||||
func (c *websocketClient) connectUniversal() {
|
||||
log.Infof("开始尝试连接到反向Websocket Universal服务器: %v", c.conf.ReverseUrl)
|
||||
wsConf, err := wsc.NewConfig(c.conf.ReverseUrl, c.conf.ReverseUrl)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket Universal服务器 %v 时出现致命错误: %v", c.conf.ReverseUrl, err)
|
||||
return
|
||||
header := http.Header{
|
||||
"X-Client-Role": []string{"Universal"},
|
||||
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
|
||||
"User-Agent": []string{"CQHttp/4.15.0"},
|
||||
}
|
||||
wsConf.Header["X-Client-Role"] = []string{"Universal"}
|
||||
wsConf.Header["X-Self-ID"] = []string{strconv.FormatInt(c.bot.Client.Uin, 10)}
|
||||
wsConf.Header["User-Agent"] = []string{"CQHttp/4.15.0"}
|
||||
if c.token != "" {
|
||||
wsConf.Header["Authorization"] = []string{"Token " + c.token}
|
||||
header["Authorization"] = []string{"Token " + c.token}
|
||||
}
|
||||
conn, err := wsc.DialConfig(wsConf)
|
||||
conn, _, err := websocket.DefaultDialer.Dial(c.conf.ReverseUrl, header)
|
||||
if err != nil {
|
||||
log.Warnf("连接到反向Websocket Universal服务器 %v 时出现错误: %v", c.conf.ReverseUrl, err)
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
@ -151,64 +146,59 @@ func (c *websocketClient) connectUniversal() {
|
||||
}
|
||||
return
|
||||
}
|
||||
go c.listenApi(conn, true)
|
||||
c.universalConn = conn
|
||||
wrappedConn := &websocketConn{Conn: 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()
|
||||
for {
|
||||
var buf []byte
|
||||
err := wsc.Message.Receive(conn, &buf)
|
||||
_, buf, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Warnf("监听反向WS API时出现错误: %v", err)
|
||||
break
|
||||
}
|
||||
j := gjson.ParseBytes(buf)
|
||||
t := strings.ReplaceAll(j.Get("action").Str, "_async", "")
|
||||
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()
|
||||
}
|
||||
|
||||
go conn.handleRequest(c.bot, buf)
|
||||
|
||||
}
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
if !u {
|
||||
c.connectApi()
|
||||
go c.connectApi()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketClient) onBotPushEvent(m coolq.MSG) {
|
||||
c.pushLock.Lock()
|
||||
defer c.pushLock.Unlock()
|
||||
if c.eventConn != nil {
|
||||
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()
|
||||
if c.conf.ReverseReconnectInterval != 0 {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
c.connectEvent()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.universalConn != nil {
|
||||
log.Debugf("向WS服务器 %v 推送Event: %v", c.universalConn.RemoteAddr().String(), m.ToJson())
|
||||
if _, err := c.universalConn.Write([]byte(m.ToJson())); err != nil {
|
||||
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 {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
|
||||
c.connectUniversal()
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,10 +218,19 @@ func (s *websocketServer) event(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake))
|
||||
if err == nil {
|
||||
log.Infof("接受 Websocket 连接: %v (/event)", r.RemoteAddr)
|
||||
s.eventConn = append(s.eventConn, c)
|
||||
if err != nil {
|
||||
log.Warnf("Websocket 握手时出现错误: %v", err)
|
||||
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) {
|
||||
@ -248,7 +247,8 @@ func (s *websocketServer) api(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
@ -265,52 +265,75 @@ func (s *websocketServer) any(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
err = c.WriteMessage(websocket.TextMessage, []byte(s.handshake))
|
||||
if err == nil {
|
||||
log.Infof("接受 Websocket 连接: %v (/)", r.RemoteAddr)
|
||||
s.eventConn = append(s.eventConn, c)
|
||||
s.listenApi(c)
|
||||
}
|
||||
if err != nil {
|
||||
log.Warnf("Websocket 握手时出现错误: %v", err)
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *websocketServer) listenApi(c *websocket.Conn) {
|
||||
log.Infof("接受 Websocket 连接: %v (/)", r.RemoteAddr)
|
||||
conn := &websocketConn{Conn: c}
|
||||
s.eventConn = append(s.eventConn, conn)
|
||||
s.listenApi(conn)
|
||||
}
|
||||
|
||||
func (s *websocketServer) listenApi(c *websocketConn) {
|
||||
defer c.Close()
|
||||
for {
|
||||
t, payload, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if t == websocket.TextMessage {
|
||||
j := gjson.ParseBytes(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()
|
||||
}
|
||||
go c.handleRequest(s.bot, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketConn) handleRequest(bot *coolq.CQBot, payload []byte) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("处置WS命令时发生无法恢复的异常:%v", err)
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
j := gjson.ParseBytes(payload)
|
||||
t := strings.ReplaceAll(j.Get("action").Str, "_async", "")
|
||||
log.Debugf("WS接收到API调用: %v 参数: %v", t, j.Get("params").Raw)
|
||||
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.pushLock.Lock()
|
||||
defer s.pushLock.Unlock()
|
||||
pos := 0
|
||||
for _, conn := range s.eventConn {
|
||||
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())
|
||||
err := conn.WriteMessage(websocket.TextMessage, []byte(m.ToJson()))
|
||||
if err != nil {
|
||||
conn.Lock()
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(m.ToJson())); err != nil {
|
||||
_ = conn.Close()
|
||||
s.eventConn = append(s.eventConn[:pos], s.eventConn[pos+1:]...)
|
||||
if pos > 0 {
|
||||
pos++
|
||||
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
|
||||
continue
|
||||
}
|
||||
pos++
|
||||
conn.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,7 +345,7 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
return bot.CQGetFriendList()
|
||||
},
|
||||
"get_group_list": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupList()
|
||||
return bot.CQGetGroupList(p.Get("no_cache").Bool())
|
||||
},
|
||||
"get_group_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupInfo(p.Get("group_id").Int())
|
||||
@ -337,28 +360,29 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
)
|
||||
},
|
||||
"send_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
autoEscape := global.EnsureBool(p.Get("auto_escape"), false)
|
||||
if p.Get("message_type").Str == "private" {
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"))
|
||||
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"))
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
if p.Get("group_id").Int() != 0 {
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"))
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
if p.Get("user_id").Int() != 0 {
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"))
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"), autoEscape)
|
||||
}
|
||||
return coolq.MSG{}
|
||||
},
|
||||
"send_group_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"))
|
||||
return bot.CQSendGroupMessage(p.Get("group_id").Int(), p.Get("message"), global.EnsureBool(p.Get("auto_escape"), false))
|
||||
},
|
||||
"send_group_forward_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQSendGroupForwardMessage(p.Get("group_id").Int(), p.Get("messages"))
|
||||
},
|
||||
"send_private_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"))
|
||||
return bot.CQSendPrivateMessage(p.Get("user_id").Int(), p.Get("message"), global.EnsureBool(p.Get("auto_escape"), false))
|
||||
},
|
||||
"delete_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQDeleteMessage(int32(p.Get("message_id").Int()))
|
||||
@ -379,7 +403,7 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
if p.Get("approve").Exists() {
|
||||
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 {
|
||||
return bot.CQSetGroupCard(p.Get("group_id").Int(), p.Get("user_id").Int(), p.Get("card").Str)
|
||||
@ -421,6 +445,9 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{
|
||||
"get_group_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupMessage(int32(p.Get("message_id").Int()))
|
||||
},
|
||||
"get_group_honor_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQGetGroupHonorInfo(p.Get("group_id").Int(), p.Get("type").Str)
|
||||
},
|
||||
"can_send_image": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG {
|
||||
return bot.CQCanSendImage()
|
||||
},
|
||||
|
Reference in New Issue
Block a user