Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
G
go-cqhttp
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nanahira
go-cqhttp
Commits
229098d2
Commit
229098d2
authored
Aug 26, 2020
by
nanahira
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of github.com:Mrs4s/go-cqhttp
parents
df1592ff
174ebfae
Pipeline
#629
passed with stages
in 4 minutes and 49 seconds
Changes
15
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
535 additions
and
83 deletions
+535
-83
.dockerignore
.dockerignore
+1
-0
.gitignore
.gitignore
+1
-0
coolq/api.go
coolq/api.go
+11
-3
coolq/bot.go
coolq/bot.go
+10
-1
coolq/cqcode.go
coolq/cqcode.go
+89
-9
docs/config.md
docs/config.md
+20
-13
docs/cqhttp.md
docs/cqhttp.md
+45
-0
global/config.go
global/config.go
+16
-13
global/filter.go
global/filter.go
+267
-0
global/net.go
global/net.go
+37
-0
go.mod
go.mod
+1
-1
go.sum
go.sum
+2
-22
main.go
main.go
+12
-0
server/http.go
server/http.go
+2
-1
server/websocket.go
server/websocket.go
+21
-20
No files found.
.dockerignore
View file @
229098d2
...
...
@@ -10,3 +10,4 @@ device.json
data/
vendor/
/go-cqhttp
.idea
.gitignore
View file @
229098d2
...
...
@@ -5,3 +5,4 @@ device.json
data/
vendor/
/go-cqhttp
.idea
coolq/api.go
View file @
229098d2
...
...
@@ -489,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
,
context
.
Get
(
"reason"
)
.
Str
,
operation
.
Get
(
"approve"
)
.
Bool
())
bot
.
CQProcessGroupRequest
(
context
.
Get
(
"flag"
)
.
Str
,
context
.
Get
(
"sub_type"
)
.
Str
,
operation
.
Get
(
"reason"
)
.
Str
,
operation
.
Get
(
"approve"
)
.
Bool
())
}
}
}
...
...
@@ -503,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
)
}
...
...
coolq/bot.go
View file @
229098d2
...
...
@@ -10,6 +10,7 @@ import (
"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"
"path"
...
...
@@ -30,6 +31,8 @@ type CQBot struct {
type
MSG
map
[
string
]
interface
{}
var
ForceFragmented
=
false
func
NewQQBot
(
cli
*
client
.
QQClient
,
conf
*
global
.
JsonConfig
)
*
CQBot
{
bot
:=
&
CQBot
{
Client
:
cli
,
...
...
@@ -127,7 +130,7 @@ 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
...
...
@@ -208,6 +211,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
()
{
...
...
coolq/cqcode.go
View file @
229098d2
...
...
@@ -6,11 +6,6 @@ import (
"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"
...
...
@@ -18,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
{
...
...
@@ -111,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
:
...
...
@@ -121,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
:
...
...
@@ -191,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
)
r
=
append
(
r
,
message
.
NewText
(
code
))
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
)
...
...
@@ -322,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
)
...
...
@@ -337,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
{
...
...
@@ -418,6 +438,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=1101079856" 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
)
}
...
...
docs/config.md
View file @
229098d2
...
...
@@ -25,12 +25,14 @@ go-cqhttp 支持导入CQHTTP的配置文件, 具体步骤为:
"relogin"
:
false
,
"relogin_delay"
:
0
,
"post_message_format"
:
"string"
,
"ignore_invalid_cqcode"
:
false
,
"force_fragmented"
:
true
,
"http_config"
:
{
"enabled"
:
true
,
"host"
:
"0.0.0.0"
,
"port"
:
5700
,
"timeout"
:
5
,
"post_urls"
:
{
"url:port"
:
"secret"
}
,
"timeout"
:
5
,
"post_urls"
:
{
"url:port"
:
"secret"
}
},
"ws_config"
:
{
"enabled"
:
true
,
...
...
@@ -51,18 +53,23 @@ go-cqhttp 支持导入CQHTTP的配置文件, 具体步骤为:
| 字段 | 类型 | 说明 |
| ------------------ | -------- | ------------------------------------------------------------------- |
| uin | int64 | 登录用QQ号 |
| password | string | 登录用密码 |
| encrypt_password | bool | 是否对密码进行加密. |
| password_encrypted | string | 加密后的密码(请勿修改) |
| enable_db | bool | 是否开启内置数据库, 关闭后将无法使用
**回复/撤回**
等上下文相关接口 |
| access_token | string | 同CQHTTP的
`access_token`
用于身份验证 |
| relogin | bool | 是否自动重新登录 |
| relogin_delay | int | 重登录延时(秒) |
| http_config | object | HTTP API配置 |
| ws_config | object | Websocket API 配置 |
| ws_reverse_servers | object[] | 反向 Websocket API 配置 |
| uin | int64 | 登录用QQ号 |
| password | string | 登录用密码 |
| encrypt_password | bool | 是否对密码进行加密. |
| password_encrypted | string | 加密后的密码(请勿修改) |
| enable_db | bool | 是否开启内置数据库, 关闭后将无法使用
**回复/撤回**
等上下文相关接口 |
| access_token | string | 同CQHTTP的
`access_token`
用于身份验证 |
| relogin | bool | 是否自动重新登录 |
| relogin_delay | int | 重登录延时(秒) |
| post_message_format | string | 上报信息类型 |
| ignore_invalid_cqcode| bool | 是否忽略错误的CQ码 |
| force_fragmented | bool | 是否强制分片发送群长消息 |
| http_config | object | HTTP API配置 |
| ws_config | object | Websocket API 配置 |
| ws_reverse_servers | object[] | 反向 Websocket API 配置 |
> 注: 开启密码加密后程序将在每次启动时要求输入解密密钥, 密钥错误会导致登录时提示密码错误.
> 解密后密码将储存在内存中,用于自动重连等功能. 所以此加密并不能防止内存读取.
> 解密密钥在使用完成后并不会留存在内存中, 所以可用相对简单的字符串作为密钥
> 注2: 分片发送为原酷Q发送长消息的老方案, 发送速度更优/兼容性更好。关闭后将优先使用新方案, 能发送更长的消息, 但发送速度更慢,在部分老客户端将无法解析.
\ No newline at end of file
docs/cqhttp.md
View file @
229098d2
...
...
@@ -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
...
...
global/config.go
View file @
229098d2
...
...
@@ -6,19 +6,21 @@ import (
)
type
JsonConfig
struct
{
Uin
int64
`json:"uin"`
Password
string
`json:"password"`
EncryptPassword
bool
`json:"encrypt_password"`
PasswordEncrypted
string
`json:"password_encrypted"`
EnableDB
bool
`json:"enable_db"`
AccessToken
string
`json:"access_token"`
ReLogin
bool
`json:"relogin"`
ReLoginDelay
int
`json:"relogin_delay"`
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"`
Uin
int64
`json:"uin"`
Password
string
`json:"password"`
EncryptPassword
bool
`json:"encrypt_password"`
PasswordEncrypted
string
`json:"password_encrypted"`
EnableDB
bool
`json:"enable_db"`
AccessToken
string
`json:"access_token"`
ReLogin
bool
`json:"relogin"`
ReLoginDelay
int
`json:"relogin_delay"`
IgnoreInvalidCQCode
bool
`json:"ignore_invalid_cqcode"`
ForceFragmented
bool
`json:"force_fragmented"`
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"`
}
type
CQHttpApiConfig
struct
{
...
...
@@ -68,6 +70,7 @@ func DefaultConfig() *JsonConfig {
ReLogin
:
true
,
ReLoginDelay
:
3
,
PostMessageFormat
:
"string"
,
ForceFragmented
:
true
,
HttpConfig
:
&
GoCQHttpConfig
{
Enabled
:
true
,
Host
:
"0.0.0.0"
,
...
...
global/filter.go
0 → 100644
View file @
229098d2
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
}
\ No newline at end of file
global/net.go
View file @
229098d2
...
...
@@ -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"
,
}
}
go.mod
View file @
229098d2
...
...
@@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp
go 1.14
require (
github.com/Mrs4s/MiraiGo v0.0.0-2020082
1182324-7654a7a2a106
github.com/Mrs4s/MiraiGo v0.0.0-2020082
5052841-d3b0f5f9e839
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
...
...
go.sum
View file @
229098d2
This diff is collapsed.
Click to expand it.
main.go
View file @
229098d2
...
...
@@ -186,6 +186,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
)
...
...
@@ -225,6 +235,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
{
...
...
server/http.go
View file @
229098d2
...
...
@@ -41,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
)
...
...
@@ -400,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
...
...
server/websocket.go
View file @
229098d2
...
...
@@ -174,33 +174,31 @@ func (c *websocketClient) listenApi(conn *websocketConn, u bool) {
func
(
c
*
websocketClient
)
onBotPushEvent
(
m
coolq
.
MSG
)
{
if
c
.
eventConn
!=
nil
{
log
.
Debugf
(
"向WS服务器 %v 推送Event: %v"
,
c
.
eventConn
.
RemoteAddr
()
.
String
(),
m
.
ToJson
())
c
.
eventConn
.
Lock
()
defer
c
.
eventConn
.
Unlock
()
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
())
c
.
universalConn
.
Lock
()
defer
c
.
universalConn
.
Unlock
()
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
()
}
}
}
...
...
@@ -318,18 +316,21 @@ func (c *websocketConn) handleRequest(bot *coolq.CQBot, payload []byte) {
func
(
s
*
websocketServer
)
onBotPushEvent
(
m
coolq
.
MSG
)
{
s
.
eventConnMutex
.
Lock
()
defer
s
.
eventConnMutex
.
Unlock
()
pos
:=
0
for
_
,
conn
:=
range
s
.
eventConn
{
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
{
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
}
pos
++
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment