1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-06-29 19:43:24 +00:00

Compare commits

...

33 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
13 changed files with 502 additions and 113 deletions

View File

@ -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 }}"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor/

View File

@ -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})
@ -134,6 +137,7 @@ func (bot *CQBot) CQSendGroupMessage(groupId int64, i interface{}, autoEscape bo
str = s
}
if str == "" {
log.Warnf("群消息发送失败: 信息为空. MSG: %v", i)
return Failed(100)
}
var elem []message.IMessageElement
@ -342,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 {
@ -352,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)
}
@ -362,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)
}
@ -485,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())
}
}
}
@ -499,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)
}
@ -578,6 +590,7 @@ func (bot *CQBot) CQGetVersionInfo() MSG {
"plugin_build_configuration": "release",
"runtime_version": runtime.Version(),
"runtime_os": runtime.GOOS,
"version": version,
})
}

View File

@ -12,7 +12,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/xujiajun/nutsdb"
"hash/crc32"
"math/rand"
"path"
"sync"
"time"
@ -108,7 +107,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)
@ -131,6 +129,7 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int
m.Elements = newElem
ret := bot.Client.SendGroupMessage(groupId, m)
if ret == nil || ret.Id == -1 {
log.Warnf("群消息发送失败: 账号可能被风控.")
return -1
}
return bot.InsertGroupMessage(ret)
@ -151,16 +150,23 @@ 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
msg := bot.Client.SendPrivateMessage(target, m)
if msg != nil {
id = msg.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.SendTempMessage(code.(int64), target, m)
if msg != nil {
id = msg.Id
}
}
}
if id == -1 {
return -1
}
return ToGlobalId(target, id)
}
@ -220,6 +226,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)
}

View File

@ -111,6 +111,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:
@ -121,8 +130,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:
@ -418,6 +425,38 @@ 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"] == "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:
return nil, errors.New("unsupported cq code: " + t)
}

View File

@ -14,7 +14,6 @@ type JsonConfig struct {
AccessToken string `json:"access_token"`
ReLogin bool `json:"relogin"`
ReLoginDelay int `json:"relogin_delay"`
AsyncLoad bool `json:"async_load"`
HttpConfig *GoCQHttpConfig `json:"http_config"`
WSConfig *GoCQWebsocketConfig `json:"ws_config"`
ReverseServers []*GoCQReverseWebsocketConfig `json:"ws_reverse_servers"`

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

@ -3,6 +3,7 @@ package global
import (
"bytes"
"compress/gzip"
"github.com/tidwall/gjson"
"io/ioutil"
"net/http"
"strings"
@ -32,3 +33,11 @@ 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&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
}

7
go.mod
View File

@ -3,18 +3,21 @@ module github.com/Mrs4s/go-cqhttp
go 1.14
require (
github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17
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/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/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
)

8
go.sum
View File

@ -1,7 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17 h1:sY4lwW34serGKnD26hP7ZLpnJ4NfVYKdS2SJIW1Mezs=
github.com/Mrs4s/MiraiGo v0.0.0-20200819185537-8d1e5fb04c17/go.mod h1:0je03wji/tSw4bUH4QCF2Z4/EjyNWjSJTyy5tliX6EM=
github.com/Mrs4s/MiraiGo v0.0.0-20200823075559-507fe33e842d h1:F7ssNQDHqB7NZVwTeADRY+AxKT3eeSlBzfzeZYTUfxM=
github.com/Mrs4s/MiraiGo v0.0.0-20200823075559-507fe33e842d/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=
@ -10,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=
@ -48,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=
@ -83,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/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=

View File

@ -136,6 +136,7 @@ func main() {
if conf.Debug {
log.SetLevel(log.DebugLevel)
log.Warnf("已开启Debug模式.")
log.Debugf("开发交流群: 192548878")
}
if !global.PathExists("device.json") {
log.Warn("虚拟设备信息不存在, 将自动生成随机设备.")
@ -200,7 +201,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" {

View File

@ -4,6 +4,7 @@ import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"os"
"strconv"
"strings"
"time"
@ -40,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)
@ -157,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)
}
}()
}
@ -304,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) {
@ -404,12 +411,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() {

View File

@ -2,24 +2,25 @@ 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"
"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
handshake string
bot *coolq.CQBot
token string
eventConn []*websocketConn
eventConnMutex sync.Mutex
handshake string
}
type websocketClient struct {
@ -27,9 +28,13 @@ type websocketClient struct {
token string
bot *coolq.CQBot
pushLock *sync.Mutex
universalConn *websocket.Conn
eventConn *websocket.Conn
universalConn *websocketConn
eventConn *websocketConn
}
type websocketConn struct {
*websocket.Conn
sync.Mutex
}
var WebsocketServer = &websocketServer{}
@ -41,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())
@ -56,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() {
@ -96,7 +100,8 @@ 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() {
@ -119,7 +124,7 @@ func (c *websocketClient) connectEvent() {
return
}
log.Infof("已连接到反向Websocket Event服务器 %v", c.conf.ReverseEventUrl)
c.eventConn = conn
c.eventConn = &websocketConn{Conn: conn}
}
func (c *websocketClient) connectUniversal() {
@ -141,11 +146,12 @@ 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 *websocket.Conn, u bool) {
func (c *websocketClient) listenApi(conn *websocketConn, u bool) {
defer conn.Close()
for {
_, buf, err := conn.ReadMessage()
@ -153,54 +159,52 @@ func (c *websocketClient) listenApi(conn *websocket.Conn, u bool) {
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.WriteJSON(ret)
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()
payload := gjson.Parse(m.ToJson())
filter := global.GetFilter()
if filter != nil && (*filter).Eval(payload) == false {
log.Debug("Event filtered!")
return
}
if c.eventConn != nil {
log.Debugf("向WS服务器 %v 推送Event: %v", c.eventConn.RemoteAddr().String(), m.ToJson())
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()
}()
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())
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()
}()
time.Sleep(time.Millisecond * time.Duration(c.conf.ReverseReconnectInterval))
c.connectUniversal()
}
}
}
@ -220,10 +224,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) {
@ -240,7 +253,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) {
@ -257,52 +271,72 @@ 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
}
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()
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 (s *websocketServer) onBotPushEvent(m coolq.MSG) {
s.pushLock.Lock()
defer s.pushLock.Unlock()
pos := 0
for _, conn := range s.eventConn {
log.Debugf("向WS客户端 %v 推送Event: %v", conn.RemoteAddr().String(), m.ToJson())
err := conn.WriteMessage(websocket.TextMessage, []byte(m.ToJson()))
if err != nil {
_ = conn.Close()
s.eventConn = append(s.eventConn[:pos], s.eventConn[pos+1:]...)
if pos > 0 {
pos++
}
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.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++
}
}
@ -372,7 +406,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)