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

Compare commits

...

65 Commits

Author SHA1 Message Date
177ba9d8c2 make lint happy 2022-06-21 21:29:41 +08:00
2a0babad99 feat: slider captcha processor 2022-06-21 21:26:33 +08:00
ae7fefad13 coolq: fix upload_private_file
pc client now can receive file sent by this api.
2022-06-21 13:55:41 +08:00
23d594be29 coolq: support upload_private_file
pc client can't receive file sent by this api.
2022-06-20 16:12:22 +08:00
7349fd4b82 update dep fix #1538 2022-06-14 17:39:56 +08:00
7e24f8b6e6 fix buffer race 2022-06-14 17:31:09 +08:00
babf35e495 coolq: remove redundant message type
Fixes #1534
2022-06-13 22:32:45 +08:00
a4a8e94b27 update dep 2022-06-11 18:14:22 +08:00
6fc3f0b649 Merge branch 'master' into dev 2022-06-06 22:42:59 +08:00
b013f66209 fix #1514 2022-06-06 22:40:09 +08:00
13abf92b76 Merge pull request #1492 from fumiama/base16384
feat: support binary encoding method base16384
2022-06-06 20:26:28 +08:00
7d97216612 fix: data文件夹权限问题 (#1529)
* fix: data文件夹权限问题

当权限644时候,data文件夹会由于没有执行权限而无法创建后续的图片...等一系列文件夹。

* 0o744->0o755
2022-06-06 12:38:50 +08:00
23eea9188f ci: update go version 2022-06-05 21:41:49 +08:00
12391f8df3 ci: change release to draft 2022-06-05 21:34:48 +08:00
a3da5baae1 Merge branch 'dev' 2022-06-05 21:24:09 +08:00
e4c73d59a5 fix #1527 2022-06-05 19:05:02 +08:00
d25209c366 fix #1520 2022-06-05 17:35:37 +08:00
70d1bfe510 dep: update dependency 2022-06-05 16:55:18 +08:00
cc3745130e chore: bump yaml.in/yaml.v3 v3.0.0 to v3.0.1 (#1523)
Signed-off-by: Ink33 <Ink33@smlk.org>
2022-05-31 14:53:34 +08:00
bdf68ec694 feat: use the same buffer pool as MiraiGo (#1297) 2022-05-30 14:19:54 +08:00
15602e1daa revert: re-include url in message (#1521)
* revert: re-include url in message

* fix: wrong writer usage

* fix: composite literal uses unkeyed fields

* 优化buffer

* fix: illegal use of non-zero Builder

* fix: illegal use of non-zero Builder

* fix: make lint happy

* fix: replace io.Writer to *strings.Builder

* fix: replace io.Writer to *strings.Builder
2022-05-30 14:17:34 +08:00
43ea459365 feat: supported finger-guessing message (#1519) 2022-05-28 21:54:10 +08:00
c275806c62 add group_id for get_forward_msg (#1510) 2022-05-27 17:10:23 +08:00
d313effb79 feat: send forward msg to private (#1513)
* feature(forward_msg): support send forward msg to private

Added two apis: send_forward_msg & send_private_forward_msg

* typo: messages

* fix: message source target id
2022-05-27 14:24:48 +08:00
5daea94157 fix golangci-lint error 2022-05-26 20:48:11 +08:00
9e136b21fa coolq: refactor event msg 2022-05-26 20:45:05 +08:00
7dbda5cec7 coolq: make linter happy 2022-05-25 15:31:26 +08:00
296668441f coolq: unify converting IMessage to string&array message 2022-05-25 15:24:48 +08:00
810c781c25 server: simplify long poll implementation 2022-05-25 14:19:59 +08:00
111a5506b9 dep: update MiraiGo 2022-05-24 15:07:24 +08:00
cfa35b6b0a dep: update go.sum 2022-05-23 11:15:51 +08:00
c141501ae5 all: update MiraiGo and some minor changes 2022-05-23 11:13:44 +08:00
df3168ffd3 修改了错别字 (#1471)
第346行的文字: `线` => `限`
2022-05-18 22:05:02 +08:00
859f40db83 feat: support binary encoding method base16384 2022-05-10 16:50:38 +08:00
18a091145a style: go fmt ./...
also delete mkrw.go, we can maintain this file by hand.
2022-04-14 21:53:00 +08:00
eaf34288de fix forward 2022-04-05 21:49:03 +08:00
b22ff34cb7 coolq: fix static check 2022-04-04 22:02:10 +08:00
4a27a60456 coolq: fix static check 2022-04-04 21:56:18 +08:00
0d291f79fa ci: add static check
golangci-lint didn't release yet
2022-04-04 21:40:58 +08:00
550c17c184 dep: update MiraiGo 2022-03-28 15:30:10 +08:00
b4c3f2340e dep: update MiraiGo 2022-03-28 13:43:39 +08:00
294bd05dad Merge pull request #1443 from ishkong/patch-7
👽️ Update ffrmpeg screenshot command
2022-03-28 13:37:30 +08:00
fb33d93b31 👽️ Update ffrmpeg screenshot command
Ref: https://trac.ffmpeg.org/wiki/Create%20a%20thumbnail%20image%20every%20X%20seconds%20of%20the%20video  
Force a screenshot of the 0th second of the video
2022-03-28 13:00:29 +08:00
d522378315 all: optimize
detected by go-perfguard
2022-03-27 23:01:47 +08:00
ee9af5fa69 server: refactor http post retry 2022-03-26 14:39:28 +08:00
d161f35c69 server: fix unix socket path
For #1415
2022-03-23 21:25:31 +08:00
40a765b117 server: support unix socket
Fixes #1415
2022-03-23 21:06:32 +08:00
d42d8dd395 server: add uri Path to address
For #1415
2022-03-23 19:55:21 +08:00
f63c59f1a4 server: new config format for HTTP server and Websocket server
For #1415
Fixes #1438
2022-03-23 19:32:32 +08:00
e4d10eb2ae global: use net/netip 2022-03-22 22:56:06 +08:00
112441d76e coolq: refactor send forward message 2022-03-21 21:41:29 +08:00
062eea62ab coolq: use generic sync.Map 2022-03-20 15:19:47 +08:00
5b148d6c5e ci: disable some lint
some lint work failed with generic
2022-03-19 10:53:58 +08:00
70da0ce6e4 ci: enable golangci-lint
latest version support go1.18
2022-03-19 10:11:15 +08:00
d34531790c coolq: handle extract cover error
For #1426
2022-03-18 23:50:21 +08:00
de4cfe0733 dep: update MiraiGo
Fix wrong self message event dispatch
2022-03-18 22:58:28 +08:00
429ff80cf0 update MiraiGo & remove _get_vip_info
Fixes #1429
2022-03-18 21:39:28 +08:00
cbcfee9f69 feat: support get group notice #493 (#1418)
* feat: support get group notice #493

* feat: update `go.mod`

* fix
2022-03-18 21:15:53 +08:00
937538a7cb coolq: remove support for old cache path 2022-03-18 19:49:32 +08:00
afbf42b709 dep: update MiraiGo 2022-03-18 19:36:06 +08:00
0a603dee92 dep: revert go-silk version 2022-03-18 18:49:08 +08:00
34613306b3 internal/mime: use stdlib to detect mimetype 2022-03-18 17:07:10 +08:00
779fa20704 Dockerfile: update to go1.18
Fixes #1427
2022-03-17 21:20:34 +08:00
d48dc4fb3c ci: disable golangci-lint 2022-03-17 17:58:07 +08:00
Sam
a75f412b82 [update] Update 633 link (#1401) 2022-03-02 15:42:21 +08:00
35 changed files with 1041 additions and 1085 deletions

View File

@ -11,7 +11,7 @@ body:
## 感谢您愿意填写错误回报!
## 以下是一些注意事项,请务必阅读让我们能够更容易处理
### ❗ | 确定没有相同问题的ISSUE已被提出. (教程: https://github.com/Mrs4s/go-cqhttp/issues/633)
### ❗ | 确定没有相同问题的ISSUE已被提出. (教程: https://forums.go-cqhttp.org/t/topic/141)
### 🌎| 请准确填写环境信息
### ❔ | 打开DEBUG模式复现并提供出现问题前后至少 10 秒的完整日志内容。请自行删除日志内存在的个人信息及敏感内容。
### ⚠ | 如果涉及内存泄漏/CPU占用异常请打开DEBUG模式并下载pprof性能分析.
@ -24,7 +24,7 @@ body:
attributes:
label: 请确保您已阅读以上注意事项,并勾选下方的确认框。
options:
- label: "我已经仔细阅读上述教程和 [\"提问前需知\"](https://github.com/Mrs4s/go-cqhttp/issues/633)"
- label: "我已经仔细阅读上述教程和 [\"提问前需知\"](https://forums.go-cqhttp.org/t/topic/141)"
required: true
- label: "我已经使用 [dev分支版本](https://github.com/Mrs4s/go-cqhttp/actions/workflows/ci.yml) 测试过,问题依旧存在。"
required: true

View File

@ -28,8 +28,7 @@ jobs:
- name: Setup Go environment
uses: actions/setup-go@v2.1.3
with:
stable: false
go-version: 1.18.0-rc1
go-version: 1.18
- name: Cache downloaded module
uses: actions/cache@v2
with:

View File

@ -12,13 +12,19 @@ jobs:
- name: Setup Go environment
uses: actions/setup-go@v2.1.3
with:
go-version: 1.17
go-version: 1.18
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: latest
- name: Static Check
uses: dominikh/staticcheck-action@v1.2.0
with:
install-go: false
version: "2022.1"
- name: Tests
run: |
go test $(go list ./...)

View File

@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.17'
go-version: '1.18'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2

View File

@ -21,35 +21,28 @@ linters:
disable-all: true
fast: false
enable:
- bodyclose
- deadcode
- depguard
- dogsled
#- bodyclose
#- deadcode
#- depguard
#- dogsled
- gofmt
- goimports
- errcheck
- exportloopref
- exhaustive
- bidichk
#- funlen
#- goconst
- gocritic
#- gocyclo
- gofmt
- goimports
- goprintffuncname
#- gosec
- gosimple
#- gosimple
- govet
- ineffassign
#- misspell
- nolintlint
- rowserrcheck
- staticcheck
#- nolintlint
#- rowserrcheck
#- staticcheck
- structcheck
- stylecheck
- typecheck
#- stylecheck
- unconvert
- unparam
- unused
#- unparam
#- unused
- varcheck
- whitespace
- prealloc

View File

@ -3,6 +3,9 @@ env:
before:
hooks:
- go mod tidy
release:
draft: true
discussion_category_name: General
builds:
- id: nowin
env:

View File

@ -1,4 +1,4 @@
FROM golang:1.17-alpine AS builder
FROM golang:1.18-alpine AS builder
RUN go env -w GO111MODULE=auto \
&& go env -w CGO_ENABLED=0 \

View File

@ -3,6 +3,7 @@ package gocq
import (
"bufio"
"bytes"
"fmt"
"image"
"image/png"
"os"
@ -10,9 +11,11 @@ import (
"time"
"github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/mattn/go-colorable"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/Mrs4s/go-cqhttp/global"
)
@ -142,7 +145,19 @@ func loginResponseProcessor(res *client.LoginResponse) error {
var text string
switch res.Error {
case client.SliderNeededError:
log.Warnf("登录需要滑条验证码, 请使用手机QQ扫描二维码以继续登录.")
log.Warnf("登录需要滑条验证码, 请选择验证方式: ")
log.Warnf("1. 使用浏览器抓取滑条并登录")
log.Warnf("2. 使用手机QQ扫码验证 (需要手Q和gocq在同一网络下).")
log.Warn("请输入(1 - 2) (将在10秒后自动选择1)")
text = readLineTimeout(time.Second*10, "1")
if strings.Contains(text, "1") {
ticket := sliderCaptchaProcessor(res.VerifyUrl)
if ticket == "" {
os.Exit(0)
}
res, err = cli.SubmitTicket(ticket)
continue
}
cli.Disconnect()
cli.Release()
cli = client.NewClientEmpty()
@ -192,8 +207,7 @@ func loginResponseProcessor(res *client.LoginResponse) error {
msg := res.ErrorMessage
if strings.Contains(msg, "版本") {
msg = "密码错误或账号被冻结"
}
if strings.Contains(msg, "冻结") {
} else if strings.Contains(msg, "冻结") {
log.Fatalf("账号被冻结")
}
log.Warnf("登录失败: %v", msg)
@ -203,3 +217,23 @@ func loginResponseProcessor(res *client.LoginResponse) error {
}
}
}
func sliderCaptchaProcessor(u string) string {
id := utils.RandomString(8)
log.Warnf("请前往该地址验证 -> %v", strings.ReplaceAll(u, "https://ssl.captcha.qq.com/template/wireless_mqq_captcha.html?", fmt.Sprintf("https://captcha.go-cqhttp.org/captcha?id=%v&", id)))
start := time.Now()
for time.Since(start).Minutes() < 2 {
time.Sleep(time.Second)
data, err := global.GetBytes("https://captcha.go-cqhttp.org/captcha/ticket?id=" + id)
if err != nil {
log.Warnf("获取 Ticket 时出现错误: %v", err)
return ""
}
g := gjson.ParseBytes(data)
if g.Get("ticket").Exists() {
return g.Get("ticket").String()
}
}
log.Warnf("验证超时")
return ""
}

View File

@ -81,7 +81,7 @@ func Main() {
mkCacheDir := func(path string, _type string) {
if !global.PathExists(path) {
if err := os.MkdirAll(path, 0o644); err != nil {
if err := os.MkdirAll(path, 0o755); err != nil {
log.Fatalf("创建%s缓存文件夹失败: %v", _type, err)
}
}
@ -324,10 +324,9 @@ func Main() {
log.Info("资源初始化完成, 开始处理信息.")
log.Info("アトリは、高性能ですから!")
go selfupdate.CheckUpdate()
go func() {
time.Sleep(5 * time.Second)
go selfdiagnosis.NetworkDiagnosis(cli)
selfupdate.CheckUpdate()
selfdiagnosis.NetworkDiagnosis(cli)
}()
<-global.SetupMainSignalHandler()
@ -366,6 +365,7 @@ func PasswordHashDecrypt(encryptedPasswordHash string, key []byte) ([]byte, erro
func newClient() *client.QQClient {
c := client.NewClientEmpty()
c.UseFragmentMessage = base.ForceFragmented
c.OnServerUpdated(func(bot *client.QQClient, e *client.ServerUpdatedEvent) bool {
if !base.UseSSOAddress {
log.Infof("收到服务器地址更新通知, 根据配置文件已忽略.")

View File

@ -38,7 +38,7 @@ type guildMemberPageToken struct {
nextQueryParam string
}
var defaultPageToken = &guildMemberPageToken{
var defaultPageToken = guildMemberPageToken{
guildID: 0,
nextIndex: 0,
nextRoleID: 2,
@ -150,7 +150,7 @@ func (bot *CQBot) CQGetGuildMembers(guildID uint64, nextToken string) global.MSG
if guild == nil {
return Failed(100, "GUILD_NOT_FOUND")
}
token := defaultPageToken
token := &defaultPageToken
if nextToken != "" {
i, exists := bot.nextTokenCache.Get(nextToken)
if !exists {
@ -407,7 +407,6 @@ func (bot *CQBot) CQGetGroupList(noCache bool) global.MSG {
gs = append(gs, global.MSG{
"group_id": g.Code,
"group_name": g.Name,
"group_memo": g.Memo,
"group_create_time": g.GroupCreateTime,
"group_level": g.GroupLevel,
"max_member_count": g.MaxMemberCount,
@ -449,7 +448,6 @@ func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool) global.MSG {
return OK(global.MSG{
"group_id": group.Code,
"group_name": group.Name,
"group_memo": group.Memo,
"group_create_time": group.GroupCreateTime,
"group_level": group.GroupLevel,
"max_member_count": group.MaxMemberCount,
@ -603,6 +601,30 @@ func (bot *CQBot) CQUploadGroupFile(groupID int64, file, name, folder string) gl
return OK(nil)
}
// CQUploadPrivateFile 扩展API-上传私聊文件
//
// @route(upload_private_file)
func (bot *CQBot) CQUploadPrivateFile(userID int64, file, name string) global.MSG {
target := message.Source{
SourceType: message.SourcePrivate,
PrimaryID: userID,
}
fileBody, err := os.Open(file)
if err != nil {
log.Warnf("上传私聊文件 %v 失败: %+v", file, err)
return Failed(100, "OPEN_FILE_ERROR", "打开文件失败")
}
localFile := &client.LocalFile{
FileName: name,
Body: fileBody,
}
if err := bot.Client.UploadFile(target, localFile); err != nil {
log.Warnf("上传私聊 %v 文件 %v 失败: %+v", userID, file, err)
return Failed(100, "FILE_SYSTEM_UPLOAD_API_ERROR", err.Error())
}
return OK(nil)
}
// CQGroupFileCreateFolder 拓展API-创建群文件文件夹
//
// @route(create_group_file_folder)
@ -686,6 +708,24 @@ func (bot *CQBot) CQSendMessage(groupID, userID int64, m gjson.Result, messageTy
return global.MSG{}
}
// CQSendForwardMessage 发送合并转发消息
//
// @route(send_forward_msg)
// @rename(m->messages)
func (bot *CQBot) CQSendForwardMessage(groupID, userID int64, m gjson.Result, messageType string) global.MSG {
switch {
case messageType == "group":
return bot.CQSendGroupForwardMessage(groupID, m)
case messageType == "private":
fallthrough
case userID != 0:
return bot.CQSendPrivateForwardMessage(userID, m)
case groupID != 0:
return bot.CQSendGroupForwardMessage(groupID, m)
}
return global.MSG{}
}
// CQSendGroupMessage 发送群消息
//
// https://git.io/Jtz1c
@ -787,118 +827,126 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R
return OK(global.MSG{"message_id": mid})
}
func (bot *CQBot) uploadForwardElement(m gjson.Result, groupID int64) *message.ForwardElement {
func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType message.SourceType) *message.ForwardElement {
ts := time.Now().Add(-time.Minute * 5)
fm := message.NewForwardMessage()
source := message.Source{SourceType: message.SourceGroup, PrimaryID: groupID}
var w worker
resolveElement := func(elems []message.IMessageElement) []message.IMessageElement {
for i, elem := range elems {
p := &elems[i]
switch o := elem.(type) {
case *LocalVideoElement:
w.do(func() {
gm, err := bot.uploadLocalVideo(source, o)
if err != nil {
log.Warnf(uploadFailedTemplate, "群", groupID, "视频", err)
} else {
*p = gm
}
})
case *LocalImageElement:
w.do(func() {
gm, err := bot.uploadLocalImage(source, o)
if err != nil {
log.Warnf(uploadFailedTemplate, "群", groupID, "图片", err)
} else {
*p = gm
}
})
}
}
return elems
groupID := target
source := message.Source{SourceType: sourceType, PrimaryID: target}
if sourceType == message.SourcePrivate {
groupID = 0
}
builder := bot.Client.NewForwardMessageBuilder(groupID)
convert := func(e gjson.Result) *message.ForwardNode {
if e.Get("type").Str != "node" {
return nil
}
ts.Add(time.Second)
if e.Get("data.id").Exists() {
i := e.Get("data.id").Int()
m, _ := db.GetGroupMessageByGlobalID(int32(i))
if m != nil {
return &message.ForwardNode{
SenderId: m.Attribute.SenderUin,
SenderName: m.Attribute.SenderName,
Time: func() int32 {
msgTime := m.Attribute.Timestamp
if msgTime == 0 {
return int32(ts.Unix())
var convertMessage func(m gjson.Result) *message.ForwardMessage
convertMessage = func(m gjson.Result) *message.ForwardMessage {
fm := message.NewForwardMessage()
var w worker
resolveElement := func(elems []message.IMessageElement) []message.IMessageElement {
for i, elem := range elems {
p := &elems[i]
switch o := elem.(type) {
case *LocalVideoElement:
w.do(func() {
gm, err := bot.uploadLocalVideo(source, o)
if err != nil {
log.Warnf(uploadFailedTemplate, "合并转发", target, "视频", err)
} else {
*p = gm
}
return int32(msgTime)
}(),
Message: resolveElement(bot.ConvertContentMessage(m.Content, message.SourceGroup)),
})
case *LocalImageElement:
w.do(func() {
gm, err := bot.uploadLocalImage(source, o)
if err != nil {
log.Warnf(uploadFailedTemplate, "合并转发", target, "图片", err)
} else {
*p = gm
}
})
}
}
log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
return nil
return elems
}
uin := e.Get("data.[user_id,uin].0").Int()
msgTime := e.Get("data.time").Int()
if msgTime == 0 {
msgTime = ts.Unix()
}
name := e.Get("data.name").Str
c := e.Get("data.content")
if c.IsArray() {
nested := false
c.ForEach(func(_, value gjson.Result) bool {
if value.Get("type").Str == "node" {
nested = true
return false
convert := func(e gjson.Result) *message.ForwardNode {
if e.Get("type").Str != "node" {
return nil
}
if e.Get("data.id").Exists() {
i := e.Get("data.id").Int()
m, _ := db.GetGroupMessageByGlobalID(int32(i))
if m != nil {
msgTime := m.Attribute.Timestamp
if msgTime == 0 {
msgTime = ts.Unix()
}
return &message.ForwardNode{
SenderId: m.Attribute.SenderUin,
SenderName: m.Attribute.SenderName,
Time: int32(msgTime),
Message: resolveElement(bot.ConvertContentMessage(m.Content, message.SourceGroup)),
}
}
return true
})
if nested { // 处理嵌套
fe := bot.uploadForwardElement(c, groupID)
log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
return nil
}
uin := e.Get("data.[user_id,uin].0").Int()
msgTime := e.Get("data.time").Int()
if msgTime == 0 {
msgTime = ts.Unix()
}
name := e.Get("data.[name,nickname].0").Str
c := e.Get("data.content")
if c.IsArray() {
nested := false
c.ForEach(func(_, value gjson.Result) bool {
if value.Get("type").Str == "node" {
nested = true
return false
}
return true
})
if nested { // 处理嵌套
nestedNode := builder.NestedNode()
builder.Link(nestedNode, convertMessage(c))
return &message.ForwardNode{
SenderId: uin,
SenderName: name,
Time: int32(msgTime),
Message: []message.IMessageElement{nestedNode},
}
}
}
content := bot.ConvertObjectMessage(c, message.SourceGroup)
if uin != 0 && name != "" && len(content) > 0 {
return &message.ForwardNode{
SenderId: uin,
SenderName: name,
Time: int32(msgTime),
Message: []message.IMessageElement{fe},
Message: resolveElement(content),
}
}
log.Warnf("警告: 非法 Forward node 将跳过. uin: %v name: %v content count: %v", uin, name, len(content))
return nil
}
content := bot.ConvertObjectMessage(c, message.SourceGroup)
if uin != 0 && name != "" && len(content) > 0 {
return &message.ForwardNode{
SenderId: uin,
SenderName: name,
Time: int32(msgTime),
Message: resolveElement(content),
}
}
log.Warnf("警告: 非法 Forward node 将跳过. uin: %v name: %v content count: %v", uin, name, len(content))
return nil
}
if m.IsArray() {
for _, item := range m.Array() {
node := convert(item)
if m.IsArray() {
for _, item := range m.Array() {
node := convert(item)
if node != nil {
fm.AddNode(node)
}
}
} else {
node := convert(m)
if node != nil {
fm.AddNode(node)
}
}
} else {
node := convert(m)
if node != nil {
fm.AddNode(node)
}
w.wait()
return fm
}
w.wait()
return bot.Client.UploadGroupForwardMessage(groupID, fm)
return builder.Main(convertMessage(m))
}
// CQSendGroupForwardMessage 扩展API-发送合并转发(群)
@ -911,18 +959,39 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa
return Failed(100)
}
fe := bot.uploadForwardElement(m, groupID)
if fe != nil {
ret := bot.Client.SendGroupForwardMessage(groupID, fe)
if ret == nil || ret.Id == -1 {
log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.")
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
}
return OK(global.MSG{
"message_id": bot.InsertGroupMessage(ret),
})
fe := bot.uploadForwardElement(m, groupID, message.SourceGroup)
if fe == nil {
return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
}
return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
ret := bot.Client.SendGroupForwardMessage(groupID, fe)
if ret == nil || ret.Id == -1 {
log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.")
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
}
return OK(global.MSG{
"message_id": bot.InsertGroupMessage(ret),
})
}
// CQSendPrivateForwardMessage 扩展API-发送合并转发(好友)
//
// https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-%E7%BE%A4
// @route(send_private_forward_msg)
// @rename(m->messages)
func (bot *CQBot) CQSendPrivateForwardMessage(userID int64, m gjson.Result) global.MSG {
if m.Type != gjson.JSON {
return Failed(100)
}
fe := bot.uploadForwardElement(m, userID, message.SourcePrivate)
if fe == nil {
return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
}
mid := bot.SendPrivateMessage(userID, 0, &message.SendingMessage{Elements: []message.IMessageElement{fe}})
if mid == -1 {
log.Warnf("合并转发(好友)消息发送失败: 账号可能被风控.")
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
}
return OK(global.MSG{"message_id": mid})
}
// CQSendPrivateMessage 发送私聊消息
@ -995,6 +1064,17 @@ func (bot *CQBot) CQSetGroupName(groupID int64, name string) global.MSG {
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
}
// CQGetGroupMemo 扩展API-获取群公告
// @route(_get_group_notice)
func (bot *CQBot) CQGetGroupMemo(groupID int64) global.MSG {
r, err := bot.Client.GetGroupNotice(groupID)
if err != nil {
return Failed(100, "获取群公告失败", err.Error())
}
return OK(r)
}
// CQSetGroupMemo 扩展API-发送群公告
//
// https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E7%BE%A4%E5%85%AC%E5%91%8A
@ -1115,9 +1195,9 @@ func (bot *CQBot) CQProcessFriendRequest(flag string, approve bool) global.MSG {
return Failed(100, "FLAG_NOT_FOUND", "FLAG不存在")
}
if approve {
req.(*client.NewFriendRequest).Accept()
req.Accept()
} else {
req.(*client.NewFriendRequest).Reject()
req.Reject()
}
return OK(nil)
}
@ -1224,27 +1304,6 @@ func (bot *CQBot) CQSetGroupAdmin(groupID, userID int64, enable bool) global.MSG
return OK(nil)
}
// CQGetVipInfo 扩展API-获取VIP信息
//
// https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96vip%E4%BF%A1%E6%81%AF
// @route(_get_vip_info)
func (bot *CQBot) CQGetVipInfo(userID int64) global.MSG {
vip, err := bot.Client.GetVipInfo(userID)
if err != nil {
return Failed(100, "VIP_API_ERROR", err.Error())
}
msg := global.MSG{
"user_id": vip.Uin,
"nickname": vip.Name,
"level": vip.Level,
"level_speed": vip.LevelSpeed,
"vip_level": vip.VipLevel,
"vip_growth_speed": vip.VipGrowthSpeed,
"vip_growth_total": vip.VipGrowthTotal,
}
return OK(msg)
}
// CQGetGroupHonorInfo 获取群荣誉信息
//
// https://git.io/Jtz1H
@ -1348,7 +1407,7 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global
if reply.Exists() {
autoEscape := param.EnsureBool(operation.Get("auto_escape"), false)
at := operation.Get("at_sender").Bool() && !isAnonymous && msgType == "group"
at := !isAnonymous && operation.Get("at_sender").Bool() && msgType == "group"
if at && reply.IsArray() {
// 在 reply 数组头部插入CQ码
replySegments := make([]global.MSG, 0)
@ -1394,7 +1453,7 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global
if operation.Get("delete").Bool() {
bot.CQDeleteMessage(int32(context.Get("message_id").Int()))
}
if operation.Get("kick").Bool() && !isAnonymous {
if !isAnonymous && operation.Get("kick").Bool() {
bot.CQSetGroupKick(context.Get("group_id").Int(), context.Get("user_id").Int(), "", operation.Get("reject_add_request").Bool())
}
if operation.Get("ban").Bool() {
@ -1476,18 +1535,18 @@ func (bot *CQBot) CQDownloadFile(url string, headers gjson.Result, threadCount i
h := map[string]string{}
if headers.IsArray() {
for _, sub := range headers.Array() {
str := strings.SplitN(sub.String(), "=", 2)
if len(str) == 2 {
h[str[0]] = str[1]
first, second, ok := strings.Cut(sub.String(), "=")
if ok {
h[first] = second
}
}
}
if headers.Type == gjson.String {
lines := strings.Split(headers.String(), "\r\n")
for _, sub := range lines {
str := strings.SplitN(sub, "=", 2)
if len(str) == 2 {
h[str[0]] = str[1]
first, second, ok := strings.Cut(sub, "=")
if ok {
h[first] = second
}
}
}
@ -1526,7 +1585,7 @@ func (bot *CQBot) CQGetForwardMessage(resID string) global.MSG {
r := make([]global.MSG, len(nodes))
for i, n := range nodes {
bot.checkMedia(n.Message, 0)
content := ToFormattedMessage(n.Message, message.Source{SourceType: message.SourceGroup}, false)
content := ToFormattedMessage(n.Message, message.Source{SourceType: message.SourceGroup})
if len(n.Message) == 1 {
if forward, ok := n.Message[0].(*message.ForwardMessage); ok {
content = transformNodes(forward.Nodes)
@ -1537,8 +1596,9 @@ func (bot *CQBot) CQGetForwardMessage(resID string) global.MSG {
"user_id": n.SenderId,
"nickname": n.SenderName,
},
"time": n.Time,
"content": content,
"time": n.Time,
"content": content,
"group_id": n.GroupId,
}
}
return r
@ -1574,9 +1634,9 @@ func (bot *CQBot) CQGetMessage(messageID int32) global.MSG {
switch o := msg.(type) {
case *db.StoredGroupMessage:
m["group_id"] = o.GroupCode
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourceGroup), message.Source{SourceType: message.SourceGroup, PrimaryID: o.GroupCode}, false)
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourceGroup), message.Source{SourceType: message.SourceGroup, PrimaryID: o.GroupCode})
case *db.StoredPrivateMessage:
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourcePrivate), message.Source{SourceType: message.SourcePrivate}, false)
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourcePrivate), message.Source{SourceType: message.SourcePrivate})
}
return OK(m)
}
@ -1585,7 +1645,7 @@ func (bot *CQBot) CQGetMessage(messageID int32) global.MSG {
// @route(get_guild_msg)
func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
source, seq := decodeGuildMessageID(messageID)
if source == nil {
if source.SourceType == 0 {
log.Warnf("获取消息时出现错误: 无效消息ID")
return Failed(100, "INVALID_MESSAGE_ID", "无效消息ID")
}
@ -1621,7 +1681,7 @@ func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
"tiny_id": fU64(pull[0].Sender.TinyId),
"nickname": pull[0].Sender.Nickname,
}
m["message"] = ToFormattedMessage(pull[0].Elements, *source, false)
m["message"] = ToFormattedMessage(pull[0].Elements, source)
m["reactions"] = convertReactions(pull[0].Reactions)
bot.InsertGuildChannelMessage(pull[0])
} else {
@ -1636,7 +1696,7 @@ func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
"tiny_id": fU64(channelMsgByDB.Attribute.SenderTinyID),
"nickname": channelMsgByDB.Attribute.SenderName,
}
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel), *source)
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel), source)
}
case message.SourceGuildDirect:
// todo(mrs4s): 支持 direct 消息
@ -1679,12 +1739,12 @@ func (bot *CQBot) CQGetGroupMessageHistory(groupID int64, seq int64) global.MSG
log.Warnf("获取群历史消息失败: %v", err)
return Failed(100, "MESSAGES_API_ERROR", err.Error())
}
ms := make([]global.MSG, 0, len(msg))
ms := make([]*event, 0, len(msg))
for _, m := range msg {
bot.checkMedia(m.Elements, groupID)
id := bot.InsertGroupMessage(m)
t := bot.formatGroupMessage(m)
t["message_id"] = id
t.Others["message_id"] = id
ms = append(ms, t)
}
return OK(global.MSG{
@ -1778,12 +1838,10 @@ func (bot *CQBot) CQSetGroupAnonymousBan(groupID int64, flag string, duration in
return Failed(100, "INVALID_FLAG", "无效的flag")
}
if g := bot.Client.FindGroup(groupID); g != nil {
s := strings.SplitN(flag, "|", 2)
if len(s) != 2 {
id, nick, ok := strings.Cut(flag, "|")
if !ok {
return Failed(100, "INVALID_FLAG", "无效的flag")
}
id := s[0]
nick := s[1]
if err := g.MuteAnonymous(id, nick, duration); err != nil {
log.Warnf("anonymous ban error: %v", err)
return Failed(100, "CALL_API_ERROR", err.Error())
@ -1940,6 +1998,15 @@ func (bot *CQBot) CQGetModelShow(model string) global.MSG {
})
}
// CQSendGroupSign 群打卡
//
// https://club.vip.qq.com/onlinestatus/set
// @route(send_group_sign)
func (bot *CQBot) CQSendGroupSign(groupID int64) global.MSG {
bot.Client.SendGroupSign(groupID)
return OK(nil)
}
// CQSetModelShow 设置在线机型
//
// https://club.vip.qq.com/onlinestatus/set

View File

@ -7,6 +7,7 @@ import (
"fmt"
"os"
"runtime/debug"
"strings"
"sync"
"time"
@ -14,6 +15,7 @@ import (
"github.com/Mrs4s/MiraiGo/client"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/RomiChan/syncx"
"github.com/pkg/errors"
"github.com/segmentio/asm/base64"
log "github.com/sirupsen/logrus"
@ -30,16 +32,15 @@ type CQBot struct {
lock sync.RWMutex
events []func(*Event)
friendReqCache sync.Map
tempSessionCache sync.Map
friendReqCache syncx.Map[string, *client.NewFriendRequest]
tempSessionCache syncx.Map[int64, *client.TempSessionInfo]
nextTokenCache *utils.Cache[*guildMemberPageToken]
}
// Event 事件
type Event struct {
RawMsg global.MSG
once sync.Once
Raw *event
buffer *bytes.Buffer
}
@ -47,7 +48,7 @@ func (e *Event) marshal() {
if e.buffer == nil {
e.buffer = global.NewBuffer()
}
_ = json.NewEncoder(e.buffer).Encode(e.RawMsg)
_ = json.NewEncoder(e.buffer).Encode(e.Raw)
}
// JSONBytes return byes of json by lazy marshalling.
@ -109,13 +110,9 @@ func NewQQBot(cli *client.QQClient) *CQBot {
t := time.NewTicker(base.HeartbeatInterval)
for {
<-t.C
bot.dispatchEventMessage(global.MSG{
"time": time.Now().Unix(),
"self_id": bot.Client.Uin,
"post_type": "meta_event",
"meta_event_type": "heartbeat",
"status": bot.CQGetStatus()["data"],
"interval": base.HeartbeatInterval.Milliseconds(),
bot.dispatchEvent("meta_event/heartbeat", global.MSG{
"status": bot.CQGetStatus()["data"],
"interval": base.HeartbeatInterval.Milliseconds(),
})
}
}()
@ -288,7 +285,7 @@ func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) int
}
m.Elements = newElem
bot.checkMedia(newElem, groupID)
ret := bot.Client.SendGroupMessage(groupID, m, base.ForceFragmented)
ret := bot.Client.SendGroupMessage(groupID, m)
if ret == nil || ret.Id == -1 {
log.Warnf("群消息发送失败: 账号可能被风控.")
return -1
@ -360,17 +357,19 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
default:
if session == nil && groupID != 0 {
msg := bot.Client.SendGroupTempMessage(groupID, target, m)
//lint:ignore SA9003 there is a todo
if msg != nil { // nolint
// todo(Mrs4s)
// id = bot.InsertTempMessage(target, msg)
}
break
}
msg, err := session.(*client.TempSessionInfo).SendMessage(m)
msg, err := session.SendMessage(m)
if err != nil {
log.Errorf("发送临时会话消息失败: %v", err)
break
}
//lint:ignore SA9003 there is a todo
if msg != nil { // nolint
// todo(Mrs4s)
// id = bot.InsertTempMessage(target, msg)
@ -566,20 +565,40 @@ func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) stri
return msg.ID
}
func (bot *CQBot) dispatchEventMessage(m global.MSG) {
func (bot *CQBot) event(typ string, others global.MSG) *event {
ev := new(event)
post, detail, ok := strings.Cut(typ, "/")
ev.PostType = post
ev.DetailType = detail
if ok {
detail, sub, _ := strings.Cut(detail, "/")
ev.DetailType = detail
ev.SubType = sub
}
ev.Time = time.Now().Unix()
ev.SelfID = bot.Client.Uin
ev.Others = others
return ev
}
func (bot *CQBot) dispatchEvent(typ string, others global.MSG) {
bot.dispatch(bot.event(typ, others))
}
func (bot *CQBot) dispatch(ev *event) {
bot.lock.RLock()
defer bot.lock.RUnlock()
event := &Event{RawMsg: m}
event := &Event{Raw: ev}
wg := sync.WaitGroup{}
wg.Add(len(bot.events))
for _, f := range bot.events {
go func(fn func(*Event)) {
defer func() {
wg.Done()
if pan := recover(); pan != nil {
log.Warnf("处理事件 %v 时出现错误: %v \n%s", m, pan, debug.Stack())
log.Warnf("处理事件 %v 时出现错误: %v \n%s", event.JSONString(), pan, debug.Stack())
}
wg.Done()
}()
start := time.Now()
@ -627,13 +646,13 @@ func encodeGuildMessageID(primaryID, subID, seq uint64, source message.SourceTyp
}))
}
func decodeGuildMessageID(id string) (source *message.Source, seq uint64) {
func decodeGuildMessageID(id string) (source message.Source, seq uint64) {
b, _ := base64.StdEncoding.DecodeString(id)
if len(b) < 25 {
return
}
r := binary.NewReader(b)
source = &message.Source{
source = message.Source{
SourceType: message.SourceType(r.ReadByte()),
PrimaryID: r.ReadInt64(),
SecondaryID: r.ReadInt64(),

View File

@ -2,6 +2,7 @@ package coolq
import (
"strconv"
"strings"
"github.com/Mrs4s/MiraiGo/topic"
@ -41,7 +42,7 @@ func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG
"role": role,
"unfriendly": false,
"title": m.SpecialTitle,
"title_expire_time": m.SpecialTitleExpireTime,
"title_expire_time": 0,
"card_changeable": false,
}
}
@ -59,26 +60,23 @@ func convertGuildMemberInfo(m []*client.GuildMemberInfo) (r []global.MSG) {
return
}
func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) global.MSG {
func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) *event {
source := message.Source{
SourceType: message.SourceGroup,
PrimaryID: m.GroupCode,
}
cqm := ToStringMessage(m.Elements, source, true)
postType := "message"
cqm := toStringMessage(m.Elements, source)
typ := "message/group/normal"
if m.Sender.Uin == bot.Client.Uin {
postType = "message_sent"
typ = "message_sent/group/normal"
}
gm := global.MSG{
"anonymous": nil,
"font": 0,
"group_id": m.GroupCode,
"message": ToFormattedMessage(m.Elements, source, false),
"message_type": "group",
"message_seq": m.Id,
"post_type": postType,
"raw_message": cqm,
"self_id": bot.Client.Uin,
"anonymous": nil,
"font": 0,
"group_id": m.GroupCode,
"message": ToFormattedMessage(m.Elements, source),
"message_seq": m.Id,
"raw_message": cqm,
"sender": global.MSG{
"age": 0,
"area": "",
@ -86,9 +84,7 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) global.MSG {
"sex": "unknown",
"user_id": m.Sender.Uin,
},
"sub_type": "normal",
"time": m.Time,
"user_id": m.Sender.Uin,
"user_id": m.Sender.Uin,
}
if m.Sender.IsAnonymous() {
gm["anonymous"] = global.MSG{
@ -127,7 +123,9 @@ func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) global.MSG {
ms["card"] = mem.CardName
ms["title"] = mem.SpecialTitle
}
return gm
ev := bot.event(typ, gm)
ev.Time = int64(m.Time)
return ev
}
func convertChannelInfo(c *client.ChannelInfo) global.MSG {
@ -211,6 +209,15 @@ func convertReactions(reactions []*message.GuildMessageEmojiReaction) (r []globa
return
}
func toStringMessage(m []message.IMessageElement, source message.Source) string {
elems := toElements(m, source)
var sb strings.Builder
for _, elem := range elems {
elem.WriteCQCodeTo(&sb)
}
return sb.String()
}
func fU64(v uint64) string {
return strconv.FormatUint(v, 10)
}

View File

@ -19,6 +19,7 @@ import (
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
b14 "github.com/fumiama/go-base16384"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
@ -92,9 +93,14 @@ func replyID(r *message.ReplyElement, source message.Source) int32 {
return db.ToGlobalID(id, seq)
}
// ToArrayMessage 将消息元素数组转为MSG数组以用于消息上报
func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []global.MSG) {
r = make([]global.MSG, 0, len(e))
// toElements 将消息元素数组转为MSG数组以用于消息上报
//
// nolint:govet
func toElements(e []message.IMessageElement, source message.Source) (r []cqcode.Element) {
type pair = cqcode.Pair // simplify code
type pairs = []pair
r = make([]cqcode.Element, 0, len(e))
m := &message.SendingMessage{Elements: e}
reply := m.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
@ -103,26 +109,24 @@ func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []glo
if reply != nil && source.SourceType&(message.SourceGroup|message.SourcePrivate) != 0 {
replyElem := reply.(*message.ReplyElement)
id := replyID(replyElem, source)
if base.ExtraReplyData {
r = append(r, global.MSG{
"type": "reply",
"data": map[string]string{
"id": strconv.FormatInt(int64(id), 10),
"seq": strconv.FormatInt(int64(replyElem.ReplySeq), 10),
"qq": strconv.FormatInt(replyElem.Sender, 10),
"time": strconv.FormatInt(int64(replyElem.Time), 10),
"text": ToStringMessage(replyElem.Elements, source),
},
})
} else {
r = append(r, global.MSG{
"type": "reply",
"data": map[string]string{"id": strconv.FormatInt(int64(id), 10)},
})
elem := cqcode.Element{
Type: "reply",
Data: pairs{
{K: "id", V: strconv.FormatInt(int64(id), 10)},
},
}
if base.ExtraReplyData {
elem.Data = append(elem.Data,
pair{K: "seq", V: strconv.FormatInt(int64(replyElem.ReplySeq), 10)},
pair{K: "qq", V: strconv.FormatInt(replyElem.Sender, 10)},
pair{K: "time", V: strconv.FormatInt(int64(replyElem.Time), 10)},
pair{K: "text", V: toStringMessage(replyElem.Elements, source)},
)
}
r = append(r, elem)
}
for i, elem := range e {
var m global.MSG
var m cqcode.Element
switch o := elem.(type) {
case *message.ReplyElement:
if base.RemoveReplyAt && i+1 < len(e) {
@ -131,241 +135,155 @@ func ToArrayMessage(e []message.IMessageElement, source message.Source) (r []glo
e[i+1] = nil
}
}
continue
case *message.TextElement:
m = global.MSG{
"type": "text",
"data": map[string]string{"text": o.Content},
m = cqcode.Element{
Type: "text",
Data: pairs{
{K: "text", V: o.Content},
},
}
case *message.LightAppElement:
m = global.MSG{
"type": "json",
"data": map[string]string{"data": o.Content},
m = cqcode.Element{
Type: "json",
Data: pairs{
{K: "data", V: o.Content},
},
}
case *message.AtElement:
if o.Target == 0 {
m = global.MSG{
"type": "at",
"data": map[string]string{"qq": "all"},
}
} else {
m = global.MSG{
"type": "at",
"data": map[string]string{"qq": strconv.FormatUint(uint64(o.Target), 10)},
}
target := "all"
if o.Target != 0 {
target = strconv.FormatUint(uint64(o.Target), 10)
}
m = cqcode.Element{
Type: "at",
Data: pairs{
{K: "qq", V: target},
},
}
case *message.RedBagElement:
m = global.MSG{
"type": "redbag",
"data": map[string]string{"title": o.Title},
m = cqcode.Element{
Type: "redbag",
Data: pairs{
{K: "title", V: o.Title},
},
}
case *message.ForwardElement:
m = global.MSG{
"type": "forward",
"data": map[string]string{"id": o.ResId},
m = cqcode.Element{
Type: "forward",
Data: pairs{
{K: "id", V: o.ResId},
},
}
case *message.FaceElement:
m = global.MSG{
"type": "face",
"data": map[string]string{"id": strconv.FormatInt(int64(o.Index), 10)},
m = cqcode.Element{
Type: "face",
Data: pairs{
{K: "id", V: strconv.FormatInt(int64(o.Index), 10)},
},
}
case *message.VoiceElement:
m = global.MSG{
"type": "record",
"data": map[string]string{"file": o.Name, "url": o.Url},
m = cqcode.Element{
Type: "record",
Data: pairs{
{K: "file", V: o.Name},
{K: "url", V: o.Url},
},
}
case *message.ShortVideoElement:
m = global.MSG{
"type": "video",
"data": map[string]string{"file": o.Name, "url": o.Url},
m = cqcode.Element{
Type: "video",
Data: pairs{
{K: "file", V: o.Name},
{K: "url", V: o.Url},
},
}
case *message.GroupImageElement:
data := map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url, "subType": strconv.FormatInt(int64(o.ImageBizType), 10)}
data := pairs{
{K: "file", V: hex.EncodeToString(o.Md5) + ".image"},
{K: "subType", V: strconv.FormatInt(int64(o.ImageBizType), 10)},
{K: "url", V: o.Url},
}
switch {
case o.Flash:
data["type"] = "flash"
data = append(data, pair{K: "type", V: "flash"})
case o.EffectID != 0:
data["type"] = "show"
data["id"] = strconv.FormatInt(int64(o.EffectID), 10)
data = append(data, pair{K: "type", V: "show"})
data = append(data, pair{K: "id", V: strconv.FormatInt(int64(o.EffectID), 10)})
}
m = global.MSG{
"type": "image",
"data": data,
m = cqcode.Element{
Type: "image",
Data: data,
}
case *message.GuildImageElement:
data := map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url}
m = global.MSG{
"type": "image",
"data": data,
data := pairs{
{K: "file", V: hex.EncodeToString(o.Md5) + ".image"},
{K: "url", V: o.Url},
}
m = cqcode.Element{
Type: "image",
Data: data,
}
case *message.FriendImageElement:
data := map[string]string{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url}
if o.Flash {
data["type"] = "flash"
data := pairs{
{K: "file", V: hex.EncodeToString(o.Md5) + ".image"},
{K: "url", V: o.Url},
}
m = global.MSG{
"type": "image",
"data": data,
if o.Flash {
data = append(data, pair{K: "type", V: "flash"})
}
m = cqcode.Element{
Type: "image",
Data: data,
}
case *message.DiceElement:
m = global.MSG{
"type": "dice",
"data": map[string]string{"value": fmt.Sprint(o.Value)},
m = cqcode.Element{
Type: "dice",
Data: pairs{
{K: "value", V: strconv.FormatInt(int64(o.Value), 10)},
},
}
case *message.FingerGuessingElement:
m = cqcode.Element{
Type: "rps",
Data: pairs{
{K: "value", V: strconv.FormatInt(int64(o.Value), 10)},
},
}
case *message.MarketFaceElement:
m = global.MSG{
"type": "text",
"data": map[string]string{"text": o.Name},
m = cqcode.Element{
Type: "text",
Data: pairs{
{K: "text", V: o.Name},
},
}
case *message.ServiceElement:
if isOk := strings.Contains(o.Content, "<?xml"); isOk {
m = global.MSG{
"type": "xml",
"data": map[string]string{"data": o.Content, "resid": strconv.FormatInt(int64(o.Id), 10)},
}
} else {
m = global.MSG{
"type": "json",
"data": map[string]string{"data": o.Content, "resid": strconv.FormatInt(int64(o.Id), 10)},
}
m = cqcode.Element{
Type: "xml",
Data: pairs{
{K: "data", V: o.Content},
{K: "resid", V: o.ResId},
},
}
if !strings.Contains(o.Content, "<?xml") {
m.Type = "json"
}
case *message.AnimatedSticker:
m = global.MSG{
"type": "face",
"data": map[string]string{"id": strconv.FormatInt(int64(o.ID), 10), "type": "sticker"},
m = cqcode.Element{
Type: "face",
Data: pairs{
{K: "id", V: strconv.FormatInt(int64(o.ID), 10)},
{K: "type", V: "sticker"},
},
}
default:
continue
}
if m != nil {
r = append(r, m)
}
r = append(r, m)
}
return
}
// ToStringMessage 将消息元素数组转为字符串以用于消息上报
func ToStringMessage(e []message.IMessageElement, source message.Source, isRaw ...bool) (r string) {
sb := global.NewBuffer()
sb.Reset()
write := func(format string, a ...interface{}) {
_, _ = fmt.Fprintf(sb, format, a...)
}
ur := false
if len(isRaw) != 0 {
ur = isRaw[0]
}
// 方便
m := &message.SendingMessage{Elements: e}
reply := m.FirstOrNil(func(e message.IMessageElement) bool {
_, ok := e.(*message.ReplyElement)
return ok
})
if reply != nil && source.SourceType&(message.SourceGroup|message.SourcePrivate) != 0 {
replyElem := reply.(*message.ReplyElement)
id := replyID(replyElem, source)
if base.ExtraReplyData {
write("[CQ:reply,id=%d,seq=%d,qq=%d,time=%d,text=%s]",
id, replyElem.ReplySeq, replyElem.Sender, replyElem.Time,
cqcode.EscapeValue(ToStringMessage(replyElem.Elements, source)))
} else {
write("[CQ:reply,id=%d]", id)
}
}
for i, elem := range e {
switch o := elem.(type) {
case *message.ReplyElement:
if base.RemoveReplyAt && len(e) > i+1 {
elem, ok := e[i+1].(*message.AtElement)
if ok && elem.Target == o.Sender {
e[i+1] = nil
}
}
case *message.TextElement:
sb.WriteString(cqcode.EscapeText(o.Content))
case *message.AtElement:
if o.Target == 0 {
write("[CQ:at,qq=all]")
continue
}
write("[CQ:at,qq=%d]", uint64(o.Target))
case *message.RedBagElement:
write("[CQ:redbag,title=%s]", o.Title)
case *message.ForwardElement:
write("[CQ:forward,id=%s]", o.ResId)
case *message.FaceElement:
write(`[CQ:face,id=%d]`, o.Index)
case *message.VoiceElement:
if ur {
write(`[CQ:record,file=%s]`, o.Name)
} else {
write(`[CQ:record,file=%s,url=%s]`, o.Name, cqcode.EscapeValue(o.Url))
}
case *message.ShortVideoElement:
if ur {
write(`[CQ:video,file=%s]`, o.Name)
} else {
write(`[CQ:video,file=%s,url=%s]`, o.Name, cqcode.EscapeValue(o.Url))
}
case *message.GroupImageElement:
var arg string
if o.Flash {
arg = ",type=flash"
} else if o.EffectID != 0 {
arg = ",type=show,id=" + strconv.FormatInt(int64(o.EffectID), 10)
}
arg += ",subType=" + strconv.FormatInt(int64(o.ImageBizType), 10)
if ur {
write("[CQ:image,file=%s%s]", hex.EncodeToString(o.Md5)+".image", arg)
} else {
write("[CQ:image,file=%s,url=%s%s]", hex.EncodeToString(o.Md5)+".image", cqcode.EscapeValue(o.Url), arg)
}
case *message.FriendImageElement:
var arg string
if o.Flash {
arg = ",type=flash"
}
if ur {
write("[CQ:image,file=%s%s]", hex.EncodeToString(o.Md5)+".image", arg)
} else {
write("[CQ:image,file=%s,url=%s%s]", hex.EncodeToString(o.Md5)+".image", cqcode.EscapeValue(o.Url), arg)
}
case *LocalImageElement:
var arg string
if o.Flash {
arg = ",type=flash"
}
data, err := os.ReadFile(o.File)
if err == nil {
m := md5.Sum(data)
if ur {
write("[CQ:image,file=%s%s]", hex.EncodeToString(m[:])+".image", arg)
} else {
write("[CQ:image,file=%s,url=%s%s]", hex.EncodeToString(m[:])+".image", cqcode.EscapeValue(o.URL), arg)
}
}
case *message.GuildImageElement:
write("[CQ:image,file=%s,url=%s]", hex.EncodeToString(o.Md5)+".image", cqcode.EscapeValue(o.Url))
case *message.DiceElement:
write("[CQ:dice,value=%v]", o.Value)
case *message.MarketFaceElement:
sb.WriteString(o.Name)
case *message.ServiceElement:
if isOk := strings.Contains(o.Content, "<?xml"); isOk {
write(`[CQ:xml,data=%s,resid=%d]`, cqcode.EscapeValue(o.Content), o.Id)
} else {
write(`[CQ:json,data=%s,resid=%d]`, cqcode.EscapeValue(o.Content), o.Id)
}
case *message.LightAppElement:
write(`[CQ:json,data=%s]`, cqcode.EscapeValue(o.Content))
case *message.AnimatedSticker:
write(`[CQ:face,id=%d,type=sticker]`, o.ID)
}
}
r = sb.String() // 内部已拷贝
global.PutBuffer(sb)
return
}
// ToMessageContent 将消息转换成 Content. 忽略 Reply
// 不同于 onebot 的 Array Message, 此函数转换出来的 Content 的 data 段为实际类型
// 方便数据库查询
@ -455,6 +373,8 @@ func ToMessageContent(e []message.IMessageElement) (r []global.MSG) {
}
case *message.DiceElement:
m = global.MSG{"type": "dice", "data": global.MSG{"value": o.Value}}
case *message.FingerGuessingElement:
m = global.MSG{"type": "rps", "data": global.MSG{"value": o.Value}}
case *message.MarketFaceElement:
m = global.MSG{"type": "text", "data": global.MSG{"text": o.Name}}
case *message.ServiceElement:
@ -477,9 +397,7 @@ func ToMessageContent(e []message.IMessageElement) (r []global.MSG) {
default:
continue
}
if m != nil {
r = append(r, m)
}
r = append(r, m)
}
return
}
@ -917,9 +835,6 @@ func (bot *CQBot) ToElement(t string, d map[string]string, sourceType message.So
case "record":
f := d["file"]
data, err := global.FindFile(f, d["cache"], global.VoicePath)
if err == global.ErrSyntax {
data, err = global.FindFile(f, d["cache"], global.VoicePathOld)
}
if err != nil {
return nil, err
}
@ -1053,6 +968,13 @@ func (bot *CQBot) ToElement(t string, d map[string]string, sourceType message.So
return nil, errors.New("invalid dice value " + value)
}
return message.NewDice(int32(i)), nil
case "rps":
value := d["value"]
i, _ := strconv.ParseInt(value, 10, 64)
if i < 0 || i > 2 {
return nil, errors.New("invalid finger-guessing value " + value)
}
return message.NewFingerGuessing(int32(i)), nil
case "xml":
resID := d["resid"]
template := cqcode.EscapeValue(d["data"])
@ -1106,33 +1028,28 @@ func (bot *CQBot) ToElement(t string, d map[string]string, sourceType message.So
if cover, ok := d["cover"]; ok {
data, _ = global.FindFile(cover, d["cache"], global.ImagePath)
} else {
_ = global.ExtractCover(v.File, v.File+".jpg")
err = global.ExtractCover(v.File, v.File+".jpg")
if err != nil {
return nil, err
}
data, _ = os.ReadFile(v.File + ".jpg")
}
v.thumb = bytes.NewReader(data)
video, _ := os.Open(v.File)
defer video.Close()
_, err = video.Seek(4, io.SeekStart)
if err != nil {
return nil, err
}
_, _ = video.Seek(4, io.SeekStart)
header := make([]byte, 4)
_, err = video.Read(header)
if err != nil {
return nil, err
}
_, _ = video.Read(header)
if !bytes.Equal(header, []byte{0x66, 0x74, 0x79, 0x70}) { // check file header ftyp
_, _ = video.Seek(0, io.SeekStart)
hash, _ := utils.ComputeMd5AndLength(video)
cacheFile := path.Join(global.CachePath, hex.EncodeToString(hash)+".mp4")
if global.PathExists(cacheFile) && (d["cache"] == "" || d["cache"] == "1") {
goto ok
if !(d["cache"] == "" || d["cache"] == "1") || !global.PathExists(cacheFile) {
err = global.EncodeMP4(v.File, cacheFile)
if err != nil {
return nil, err
}
}
err = global.EncodeMP4(v.File, cacheFile)
if err != nil {
return nil, err
}
ok:
v.File = cacheFile
}
return v, nil
@ -1198,13 +1115,20 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video bool, sourceTy
}
return &LocalImageElement{File: fu.Path, URL: f}, nil
}
if strings.HasPrefix(f, "base64") && !video {
if !video && strings.HasPrefix(f, "base64") {
b, err := param.Base64DecodeString(strings.TrimPrefix(f, "base64://"))
if err != nil {
return nil, err
}
return &LocalImageElement{Stream: bytes.NewReader(b), URL: f}, nil
}
if !video && strings.HasPrefix(f, "base16384") {
b, err := b14.UTF82UTF16BE(utils.S2B(strings.TrimPrefix(f, "base16384://")))
if err != nil {
return nil, err
}
return &LocalImageElement{Stream: bytes.NewReader(b14.Decode(b)), URL: f}, nil
}
rawPath := path.Join(global.ImagePath, f)
if video {
if strings.HasSuffix(f, ".video") {
@ -1241,10 +1165,6 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video bool, sourceTy
}
}
exist := global.PathExists(rawPath)
if !exist && global.PathExists(path.Join(global.ImagePathOld, f)) {
exist = true
rawPath = path.Join(global.ImagePathOld, f)
}
if !exist {
if d["url"] != "" {
return bot.makeImageOrVideoElem(map[string]string{"file": d["url"]}, false, sourceType)

67
coolq/cqcode/element.go Normal file
View File

@ -0,0 +1,67 @@
package cqcode
import (
"bytes"
"strconv"
"strings"
"github.com/Mrs4s/MiraiGo/binary"
)
// Element single message
type Element struct {
Type string
Data []Pair
}
// Pair key value pair
type Pair struct {
K string
V string
}
// CQCode convert element to cqcode
func (e *Element) CQCode() string {
buf := strings.Builder{}
e.WriteCQCodeTo(&buf)
return buf.String()
}
// WriteCQCodeTo write element's cqcode into sb
func (e *Element) WriteCQCodeTo(sb *strings.Builder) {
if e.Type == "text" {
sb.WriteString(EscapeText(e.Data[0].V)) // must be {"text": value}
return
}
sb.WriteString("[CQ:")
sb.WriteString(e.Type)
for _, data := range e.Data {
sb.WriteByte(',')
sb.WriteString(data.K)
sb.WriteByte('=')
sb.WriteString(EscapeValue(data.V))
}
sb.WriteByte(']')
}
// MarshalJSON see encoding/json.Marshaler
func (e *Element) MarshalJSON() ([]byte, error) {
return binary.NewWriterF(func(w *binary.Writer) {
buf := (*bytes.Buffer)(w)
// fmt.Fprintf(buf, `{"type":"%s","data":{`, e.Type)
buf.WriteString(`{"type":"`)
buf.WriteString(e.Type)
buf.WriteString(`","data":{`)
for i, data := range e.Data {
if i != 0 {
buf.WriteByte(',')
}
// fmt.Fprintf(buf, `"%s":%q`, data.K, data.V)
buf.WriteByte('"')
buf.WriteString(data.K)
buf.WriteString(`":`)
buf.WriteString(strconv.Quote(data.V))
}
buf.WriteString(`}}`)
}), nil
}

View File

@ -3,15 +3,11 @@ package cqcode
import "strings"
/*EscapeText 将字符串raw中部分字符转义
& -> &amp;
[ -> &#91;
] -> &#93;
*/
// EscapeText 将字符串raw中部分字符转义
//
// - & -> &amp;
// - [ -> &#91;
// - ] -> &#93;
func EscapeText(s string) string {
count := strings.Count(s, "&")
count += strings.Count(s, "[")
@ -47,31 +43,22 @@ func EscapeText(s string) string {
return b.String()
}
/*EscapeValue 将字符串value中部分字符转义
, -> &#44;
& -> &amp;
[ -> &#91;
] -> &#93;
*/
// EscapeValue 将字符串value中部分字符转义
//
// - , -> &#44;
// - & -> &amp;
// - [ -> &#91;
// - ] -> &#93;
func EscapeValue(value string) string {
ret := EscapeText(value)
return strings.ReplaceAll(ret, ",", "&#44;")
}
/*UnescapeText 将字符串content中部分字符反转义
&amp; -> &
&#91; -> [
&#93; -> ]
*/
// UnescapeText 将字符串content中部分字符反转义
//
// - &amp; -> &
// - &#91; -> [
// - &#93; -> ]
func UnescapeText(content string) string {
ret := content
ret = strings.ReplaceAll(ret, "&#91;", "[")
@ -80,17 +67,12 @@ func UnescapeText(content string) string {
return ret
}
/*UnescapeValue 将字符串content中部分字符反转义
&#44; -> ,
&amp; -> &
&#91; -> [
&#93; -> ]
*/
// UnescapeValue 将字符串content中部分字符反转义
//
// - &#44; -> ,
// - &amp; -> &
// - &#91; -> [
// - &#93; -> ]
func UnescapeValue(content string) string {
ret := strings.ReplaceAll(content, "&#44;", ",")
return UnescapeText(ret)

View File

@ -2,12 +2,12 @@ package coolq
import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client"
@ -21,41 +21,73 @@ import (
)
// ToFormattedMessage 将给定[]message.IMessageElement转换为通过coolq.SetMessageFormat所定义的消息上报格式
func ToFormattedMessage(e []message.IMessageElement, source message.Source, isRaw ...bool) (r interface{}) {
func ToFormattedMessage(e []message.IMessageElement, source message.Source) (r interface{}) {
if base.PostFormat == "string" {
r = ToStringMessage(e, source, isRaw...)
r = toStringMessage(e, source)
} else if base.PostFormat == "array" {
r = ToArrayMessage(e, source)
r = toElements(e, source)
}
return
}
func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMessage) {
type event struct {
PostType string
DetailType string
SubType string
Time int64
SelfID int64
Others global.MSG
}
func (ev *event) MarshalJSON() ([]byte, error) {
buf := global.NewBuffer()
defer global.PutBuffer(buf)
detail := ""
switch ev.PostType {
case "message", "message_sent":
detail = "message_type"
case "notice":
detail = "notice_type"
case "request":
detail = "request_type"
case "meta_event":
detail = "meta_event_type"
default:
panic("unknown post type: " + ev.PostType)
}
fmt.Fprintf(buf, `{"post_type":"%s","%s":"%s","time":%d,"self_id":%d`, ev.PostType, detail, ev.DetailType, ev.Time, ev.SelfID)
if ev.SubType != "" {
fmt.Fprintf(buf, `,"sub_type":"%s"`, ev.SubType)
}
for k, v := range ev.Others {
v, _ := json.Marshal(v)
fmt.Fprintf(buf, `,"%s":%s`, k, v)
}
buf.WriteByte('}')
return append([]byte(nil), buf.Bytes()...), nil
}
func (bot *CQBot) privateMessageEvent(_ *client.QQClient, m *message.PrivateMessage) {
bot.checkMedia(m.Elements, m.Sender.Uin)
source := message.Source{
SourceType: message.SourcePrivate,
PrimaryID: m.Sender.Uin,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source)
id := bot.InsertPrivateMessage(m)
log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
typ := "message/private/friend"
if m.Sender.Uin == bot.Client.Uin {
typ = "message_sent/private/friend"
}
fm := global.MSG{
"post_type": func() string {
if m.Sender.Uin == bot.Client.Uin {
return "message_sent"
}
return "message"
}(),
"message_type": "private",
"sub_type": "friend",
"message_id": id,
"user_id": m.Sender.Uin,
"target_id": m.Target,
"message": ToFormattedMessage(m.Elements, source, false),
"raw_message": cqm,
"font": 0,
"self_id": c.Uin,
"time": time.Now().Unix(),
"message_id": id,
"user_id": m.Sender.Uin,
"target_id": m.Target,
"message": ToFormattedMessage(m.Elements, source),
"raw_message": cqm,
"font": 0,
"sender": global.MSG{
"user_id": m.Sender.Uin,
"nickname": m.Sender.Nickname,
@ -63,7 +95,7 @@ func (bot *CQBot) privateMessageEvent(c *client.QQClient, m *message.PrivateMess
"age": 0,
},
}
bot.dispatchEventMessage(fm)
bot.dispatchEvent(typ, fm)
}
func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage) {
@ -71,11 +103,9 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
for _, elem := range m.Elements {
if file, ok := elem.(*message.GroupFileElement); ok {
log.Infof("群 %v(%v) 内 %v(%v) 上传了文件: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, file.Name)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "group_upload",
"group_id": m.GroupCode,
"user_id": m.Sender.Uin,
bot.dispatchEvent("notice/group_upload", global.MSG{
"group_id": m.GroupCode,
"user_id": m.Sender.Uin,
"file": global.MSG{
"id": file.Path,
"name": file.Name,
@ -83,8 +113,6 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
"busid": file.Busid,
"url": c.GetGroupFileUrl(m.GroupCode, file.Path, file.Busid),
},
"self_id": c.Uin,
"time": time.Now().Unix(),
})
return
}
@ -93,15 +121,15 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
SourceType: message.SourceGroup,
PrimaryID: m.GroupCode,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source)
id := bot.InsertGroupMessage(m)
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
gm := bot.formatGroupMessage(m)
if gm == nil {
return
}
gm["message_id"] = id
bot.dispatchEventMessage(gm)
gm.Others["message_id"] = id
bot.dispatch(gm)
}
func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEvent) {
@ -111,7 +139,7 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
SourceType: message.SourcePrivate,
PrimaryID: e.Session.Sender,
}
cqm := ToStringMessage(m.Elements, source, true)
cqm := toStringMessage(m.Elements, source)
bot.tempSessionCache.Store(m.Sender.Uin, e.Session)
id := m.Id
// todo(Mrs4s)
@ -120,17 +148,12 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
// }
log.Infof("收到来自群 %v(%v) 内 %v(%v) 的临时会话消息: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm)
tm := global.MSG{
"post_type": "message",
"message_type": "private",
"sub_type": "group",
"temp_source": e.Session.Source,
"message_id": id,
"user_id": m.Sender.Uin,
"message": ToFormattedMessage(m.Elements, source, false),
"raw_message": cqm,
"font": 0,
"self_id": c.Uin,
"time": time.Now().Unix(),
"temp_source": e.Session.Source,
"message_id": id,
"user_id": m.Sender.Uin,
"message": ToFormattedMessage(m.Elements, source),
"raw_message": cqm,
"font": 0,
"sender": global.MSG{
"user_id": m.Sender.Uin,
"group_id": m.GroupCode,
@ -139,7 +162,7 @@ func (bot *CQBot) tempMessageEvent(c *client.QQClient, e *client.TempMessageEven
"age": 0,
},
}
bot.dispatchEventMessage(tm)
bot.dispatchEvent("message/private/group", tm)
}
func (bot *CQBot) guildChannelMessageEvent(c *client.QQClient, m *message.GuildChannelMessage) {
@ -154,26 +177,23 @@ func (bot *CQBot) guildChannelMessageEvent(c *client.QQClient, m *message.GuildC
PrimaryID: int64(m.GuildId),
SecondaryID: int64(m.ChannelId),
}
log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, ToStringMessage(m.Elements, source, true))
log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, toStringMessage(m.Elements, source))
id := bot.InsertGuildChannelMessage(m)
bot.dispatchEventMessage(global.MSG{
"post_type": "message",
"message_type": "guild",
"sub_type": "channel",
ev := bot.event("message/guild/channel", global.MSG{
"guild_id": fU64(m.GuildId),
"channel_id": fU64(m.ChannelId),
"message_id": id,
"user_id": fU64(m.Sender.TinyId),
"message": ToFormattedMessage(m.Elements, source, false), // todo: 增加对频道消息 Reply 的支持
"self_id": bot.Client.Uin,
"message": ToFormattedMessage(m.Elements, source), // todo: 增加对频道消息 Reply 的支持
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
"time": m.Time,
"sender": global.MSG{
"user_id": m.Sender.TinyId,
"tiny_id": fU64(m.Sender.TinyId),
"nickname": m.Sender.Nickname,
},
})
ev.Time = m.Time
bot.dispatch(ev)
}
func (bot *CQBot) guildMessageReactionsUpdatedEvent(c *client.QQClient, e *client.GuildMessageReactionsUpdatedEvent) {
@ -199,16 +219,12 @@ func (bot *CQBot) guildMessageReactionsUpdatedEvent(c *client.QQClient, e *clien
str += "无任何表情"
}
log.Infof(str)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "message_reactions_updated",
bot.dispatchEvent("notice/message_reactions_updated", global.MSG{
"guild_id": fU64(e.GuildId),
"channel_id": fU64(e.ChannelId),
"message_id": msgID,
"operator_id": fU64(e.OperatorId),
"current_reactions": currentReactions,
"time": time.Now().Unix(),
"self_id": bot.Client.Uin,
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
"user_id": e.OperatorId,
})
@ -230,15 +246,11 @@ func (bot *CQBot) guildChannelMessageRecalledEvent(c *client.QQClient, e *client
}
msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, message.SourceGuildChannel)
log.Infof("用户 %v(%v) 撤回了频道 %v(%v) 子频道 %v(%v) 的消息 %v", operator.Nickname, operator.TinyId, guild.GuildName, guild.GuildId, channel.ChannelName, channel.ChannelId, msgID)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "guild_channel_recall",
bot.dispatchEvent("notice/guild_channel_recall", global.MSG{
"guild_id": fU64(e.GuildId),
"channel_id": fU64(e.ChannelId),
"operator_id": fU64(e.OperatorId),
"message_id": msgID,
"time": time.Now().Unix(),
"self_id": bot.Client.Uin,
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
"user_id": e.OperatorId,
})
@ -250,14 +262,10 @@ func (bot *CQBot) guildChannelUpdatedEvent(c *client.QQClient, e *client.GuildCh
return
}
log.Infof("频道 %v(%v) 子频道 %v(%v) 信息已更新", guild.GuildName, guild.GuildId, e.NewChannelInfo.ChannelName, e.NewChannelInfo.ChannelId)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "channel_updated",
bot.dispatchEvent("notice/channel_updated", global.MSG{
"guild_id": fU64(e.GuildId),
"channel_id": fU64(e.ChannelId),
"operator_id": fU64(e.OperatorId),
"time": time.Now().Unix(),
"self_id": bot.Client.Uin,
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
"user_id": e.OperatorId,
"old_info": convertChannelInfo(e.OldChannelInfo),
@ -275,16 +283,12 @@ func (bot *CQBot) guildChannelCreatedEvent(c *client.QQClient, e *client.GuildCh
member = &client.GuildUserProfile{Nickname: "未知"}
}
log.Infof("频道 %v(%v) 内用户 %v(%v) 创建了子频道 %v(%v)", guild.GuildName, guild.GuildId, member.Nickname, member.TinyId, e.ChannelInfo.ChannelName, e.ChannelInfo.ChannelId)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "channel_created",
bot.dispatchEvent("notice/channel_created", global.MSG{
"guild_id": fU64(e.GuildId),
"channel_id": fU64(e.ChannelInfo.ChannelId),
"operator_id": fU64(e.OperatorId),
"self_id": bot.Client.Uin,
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
"user_id": e.OperatorId,
"time": time.Now().Unix(),
"channel_info": convertChannelInfo(e.ChannelInfo),
})
}
@ -299,16 +303,12 @@ func (bot *CQBot) guildChannelDestroyedEvent(c *client.QQClient, e *client.Guild
member = &client.GuildUserProfile{Nickname: "未知"}
}
log.Infof("频道 %v(%v) 内用户 %v(%v) 删除了子频道 %v(%v)", guild.GuildName, guild.GuildId, member.Nickname, member.TinyId, e.ChannelInfo.ChannelName, e.ChannelInfo.ChannelId)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "channel_destroyed",
bot.dispatchEvent("notice/channel_destroyed", global.MSG{
"guild_id": fU64(e.GuildId),
"channel_id": fU64(e.ChannelInfo.ChannelId),
"operator_id": fU64(e.OperatorId),
"self_id": bot.Client.Uin,
"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
"user_id": e.OperatorId,
"time": time.Now().Unix(),
"channel_info": convertChannelInfo(e.ChannelInfo),
})
}
@ -332,22 +332,15 @@ func (bot *CQBot) groupMutedEvent(c *client.QQClient, e *client.GroupMuteEvent)
formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)))
}
}
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
typ := "notice/group_ban/ban"
if e.Time == 0 {
typ = "notice/group_ban/lift_ban"
}
bot.dispatchEvent(typ, global.MSG{
"duration": e.Time,
"group_id": e.GroupCode,
"notice_type": "group_ban",
"operator_id": e.OperatorUin,
"self_id": c.Uin,
"user_id": e.TargetUin,
"time": time.Now().Unix(),
"sub_type": func() string {
if e.Time == 0 {
return "lift_ban"
}
return "ban"
}(),
})
}
@ -356,16 +349,15 @@ func (bot *CQBot) groupRecallEvent(c *client.QQClient, e *client.GroupMessageRec
gid := db.ToGlobalID(e.GroupCode, e.MessageId)
log.Infof("群 %v 内 %v 撤回了 %v 的消息: %v.",
formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)), formatMemberName(g.FindMember(e.AuthorUin)), gid)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
ev := bot.event("notice/group_recall", global.MSG{
"group_id": e.GroupCode,
"notice_type": "group_recall",
"self_id": c.Uin,
"user_id": e.AuthorUin,
"operator_id": e.OperatorUin,
"time": e.Time,
"message_id": gid,
})
ev.Time = int64(e.Time)
bot.dispatch(ev)
}
func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
@ -375,42 +367,27 @@ func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
sender := group.FindMember(notify.Sender)
receiver := group.FindMember(notify.Receiver)
log.Infof("群 %v 内 %v 戳了戳 %v", formatGroupName(group), formatMemberName(sender), formatMemberName(receiver))
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"group_id": group.Code,
"notice_type": "notify",
"sub_type": "poke",
"self_id": c.Uin,
"user_id": notify.Sender,
"sender_id": notify.Sender,
"target_id": notify.Receiver,
"time": time.Now().Unix(),
bot.dispatchEvent("notice/notify/poke", global.MSG{
"group_id": group.Code,
"user_id": notify.Sender,
"sender_id": notify.Sender,
"target_id": notify.Receiver,
})
case *client.GroupRedBagLuckyKingNotifyEvent:
sender := group.FindMember(notify.Sender)
luckyKing := group.FindMember(notify.LuckyKing)
log.Infof("群 %v 内 %v 的红包被抢完, %v 是运气王", formatGroupName(group), formatMemberName(sender), formatMemberName(luckyKing))
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"group_id": group.Code,
"notice_type": "notify",
"sub_type": "lucky_king",
"self_id": c.Uin,
"user_id": notify.Sender,
"sender_id": notify.Sender,
"target_id": notify.LuckyKing,
"time": time.Now().Unix(),
bot.dispatchEvent("notice/notify/lucky_king", global.MSG{
"group_id": group.Code,
"user_id": notify.Sender,
"sender_id": notify.Sender,
"target_id": notify.LuckyKing,
})
case *client.MemberHonorChangedNotifyEvent:
log.Info(notify.Content())
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"group_id": group.Code,
"notice_type": "notify",
"sub_type": "honor",
"self_id": c.Uin,
"user_id": notify.Uin,
"time": time.Now().Unix(),
bot.dispatchEvent("notice/notify/honor", global.MSG{
"group_id": group.Code,
"user_id": notify.Uin,
"honor_type": func() string {
switch notify.Honor {
case client.Talkative:
@ -439,15 +416,10 @@ func (bot *CQBot) friendNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
} else {
log.Infof("好友 %v 戳了戳你.", friend.Nickname)
}
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "notify",
"sub_type": "poke",
"self_id": c.Uin,
"user_id": notify.Sender,
"sender_id": notify.Sender,
"target_id": notify.Receiver,
"time": time.Now().Unix(),
bot.dispatchEvent("notice/notify/poke", global.MSG{
"user_id": notify.Sender,
"sender_id": notify.Sender,
"target_id": notify.Receiver,
})
}
}
@ -456,15 +428,10 @@ func (bot *CQBot) memberTitleUpdatedEvent(c *client.QQClient, e *client.MemberSp
group := c.FindGroup(e.GroupCode)
mem := group.FindMember(e.Uin)
log.Infof("群 %v(%v) 内成员 %v(%v) 获得了新的头衔: %v", group.Name, group.Code, mem.DisplayName(), mem.Uin, e.NewTitle)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "notify",
"sub_type": "title",
"group_id": group.Code,
"self_id": c.Uin,
"user_id": e.Uin,
"time": time.Now().Unix(),
"title": e.NewTitle,
bot.dispatchEvent("notice/notify/title", global.MSG{
"group_id": group.Code,
"user_id": e.Uin,
"title": e.NewTitle,
})
}
@ -476,14 +443,12 @@ func (bot *CQBot) friendRecallEvent(c *client.QQClient, e *client.FriendMessageR
} else {
log.Infof("好友 %v 撤回了消息: %v", e.FriendUin, gid)
}
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "friend_recall",
"self_id": c.Uin,
"user_id": e.FriendUin,
"time": e.Time,
"message_id": gid,
ev := bot.event("notice/friend_recall", global.MSG{
"user_id": e.FriendUin,
"message_id": gid,
})
ev.Time = e.Time
bot.dispatch(ev)
}
func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEvent) {
@ -492,23 +457,19 @@ func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEven
return
}
log.Infof("好友 %v(%v) 发送了离线文件 %v", f.Nickname, f.Uin, e.FileName)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "offline_file",
"user_id": e.Sender,
bot.dispatchEvent("notice/offline_file", global.MSG{
"user_id": e.Sender,
"file": global.MSG{
"name": e.FileName,
"size": e.FileSize,
"url": e.DownloadUrl,
},
"self_id": c.Uin,
"time": time.Now().Unix(),
})
}
func (bot *CQBot) joinGroupEvent(c *client.QQClient, group *client.GroupInfo) {
log.Infof("Bot进入了群 %v.", formatGroupName(group))
bot.dispatchEventMessage(bot.groupIncrease(group.Code, 0, c.Uin))
bot.dispatch(bot.groupIncrease(group.Code, 0, c.Uin))
}
func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *client.GroupLeaveEvent) {
@ -517,44 +478,33 @@ func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *client.GroupLeaveEvent)
} else {
log.Infof("Bot退出了群 %v.", formatGroupName(e.Group))
}
bot.dispatchEventMessage(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
}
func (bot *CQBot) memberPermissionChangedEvent(c *client.QQClient, e *client.MemberPermissionChangedEvent) {
st := func() string {
if e.NewPermission == client.Administrator {
return "set"
}
return "unset"
}()
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "group_admin",
"sub_type": st,
"group_id": e.Group.Code,
"user_id": e.Member.Uin,
"time": time.Now().Unix(),
"self_id": c.Uin,
st := "unset"
if e.NewPermission == client.Administrator {
st = "set"
}
bot.dispatchEvent("notice/group_admin/"+st, global.MSG{
"group_id": e.Group.Code,
"user_id": e.Member.Uin,
})
}
func (bot *CQBot) memberCardUpdatedEvent(c *client.QQClient, e *client.MemberCardUpdatedEvent) {
log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "group_card",
"group_id": e.Group.Code,
"user_id": e.Member.Uin,
"card_new": e.Member.CardName,
"card_old": e.OldCard,
"time": time.Now().Unix(),
"self_id": c.Uin,
bot.dispatchEvent("notice/group_card", global.MSG{
"group_id": e.Group.Code,
"user_id": e.Member.Uin,
"card_new": e.Member.CardName,
"card_old": e.OldCard,
})
}
func (bot *CQBot) memberJoinEvent(_ *client.QQClient, e *client.MemberJoinGroupEvent) {
log.Infof("新成员 %v 进入了群 %v.", formatMemberName(e.Member), formatGroupName(e.Group))
bot.dispatchEventMessage(bot.groupIncrease(e.Group.Code, 0, e.Member.Uin))
bot.dispatch(bot.groupIncrease(e.Group.Code, 0, e.Member.Uin))
}
func (bot *CQBot) memberLeaveEvent(_ *client.QQClient, e *client.MemberLeaveGroupEvent) {
@ -563,65 +513,47 @@ func (bot *CQBot) memberLeaveEvent(_ *client.QQClient, e *client.MemberLeaveGrou
} else {
log.Infof("成员 %v 离开了群 %v.", formatMemberName(e.Member), formatGroupName(e.Group))
}
bot.dispatchEventMessage(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
}
func (bot *CQBot) friendRequestEvent(c *client.QQClient, e *client.NewFriendRequest) {
log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message)
flag := strconv.FormatInt(e.RequestId, 10)
bot.friendReqCache.Store(flag, e)
bot.dispatchEventMessage(global.MSG{
"post_type": "request",
"request_type": "friend",
"user_id": e.RequesterUin,
"comment": e.Message,
"flag": flag,
"time": time.Now().Unix(),
"self_id": c.Uin,
bot.dispatchEvent("request/friend", global.MSG{
"user_id": e.RequesterUin,
"comment": e.Message,
"flag": flag,
})
}
func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent) {
log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin)
bot.tempSessionCache.Delete(e.Friend.Uin)
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "friend_add",
"self_id": c.Uin,
"user_id": e.Friend.Uin,
"time": time.Now().Unix(),
bot.dispatchEvent("notice/friend_add", global.MSG{
"user_id": e.Friend.Uin,
})
}
func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRequest) {
log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin)
flag := strconv.FormatInt(e.RequestId, 10)
bot.dispatchEventMessage(global.MSG{
"post_type": "request",
"request_type": "group",
"sub_type": "invite",
"group_id": e.GroupCode,
"user_id": e.InvitorUin,
"comment": "",
"flag": flag,
"time": time.Now().Unix(),
"self_id": c.Uin,
bot.dispatchEvent("request/group/invite", global.MSG{
"group_id": e.GroupCode,
"user_id": e.InvitorUin,
"comment": "",
"flag": flag,
})
}
func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupRequest) {
log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin)
flag := strconv.FormatInt(e.RequestId, 10)
bot.dispatchEventMessage(global.MSG{
"post_type": "request",
"request_type": "group",
"sub_type": "add",
"group_id": e.GroupCode,
"user_id": e.RequesterUin,
"comment": e.Message,
"flag": flag,
"time": time.Now().Unix(),
"self_id": c.Uin,
bot.dispatchEvent("request/group/add", global.MSG{
"group_id": e.GroupCode,
"user_id": e.RequesterUin,
"comment": e.Message,
"flag": flag,
})
}
@ -631,17 +563,13 @@ func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.Ot
} else {
log.Infof("Bot 账号在客户端 %v (%v) 登出.", e.Client.DeviceName, e.Client.DeviceKind)
}
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
"notice_type": "client_status",
"online": e.Online,
bot.dispatchEvent("notice/client_status", global.MSG{
"online": e.Online,
"client": global.MSG{
"app_id": e.Client.AppId,
"device_name": e.Client.DeviceName,
"device_kind": e.Client.DeviceKind,
},
"self_id": c.Uin,
"time": time.Now().Unix(),
})
}
@ -668,61 +596,44 @@ func (bot *CQBot) groupEssenceMsg(c *client.QQClient, e *client.GroupDigestEvent
if e.OperatorUin == bot.Client.Uin {
return
}
bot.dispatchEventMessage(global.MSG{
"post_type": "notice",
subtype := "delete"
if e.OperationType == 1 {
subtype = "add"
}
bot.dispatchEvent("notice/essence/"+subtype, global.MSG{
"group_id": e.GroupCode,
"notice_type": "essence",
"sub_type": func() string {
if e.OperationType == 1 {
return "add"
}
return "delete"
}(),
"self_id": c.Uin,
"sender_id": e.SenderUin,
"operator_id": e.OperatorUin,
"time": time.Now().Unix(),
"message_id": gid,
})
}
func (bot *CQBot) groupIncrease(groupCode, operatorUin, userUin int64) global.MSG {
return global.MSG{
"post_type": "notice",
"notice_type": "group_increase",
func (bot *CQBot) groupIncrease(groupCode, operatorUin, userUin int64) *event {
return bot.event("notice/group_increase/approve", global.MSG{
"group_id": groupCode,
"operator_id": operatorUin,
"self_id": bot.Client.Uin,
"sub_type": "approve",
"time": time.Now().Unix(),
"user_id": userUin,
}
})
}
func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.GroupMemberInfo) global.MSG {
return global.MSG{
"post_type": "notice",
"notice_type": "group_decrease",
"group_id": groupCode,
"operator_id": func() int64 {
if operator != nil {
return operator.Uin
}
return userUin
}(),
"self_id": bot.Client.Uin,
"sub_type": func() string {
if operator != nil {
if userUin == bot.Client.Uin {
return "kick_me"
}
return "kick"
}
return "leave"
}(),
"time": time.Now().Unix(),
"user_id": userUin,
func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.GroupMemberInfo) *event {
op := userUin
if operator != nil {
op = operator.Uin
}
subtype := "leave"
if operator != nil {
if userUin == bot.Client.Uin {
subtype = "kick_me"
} else {
subtype = "kick"
}
}
return bot.event("notice/group_decrease/"+subtype, global.MSG{
"group_id": groupCode,
"operator_id": op,
"user_id": userUin,
})
}
func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) {

View File

@ -1,129 +0,0 @@
//go:build ignore
package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
)
var output bytes.Buffer
func fprintf(format string, args ...interface{}) {
_, _ = fmt.Fprintf(&output, format, args...)
}
func main() {
f, _ := parser.ParseFile(token.NewFileSet(), "./../database.go", nil, 0)
fprintf("// Code generated by mkrw.go; DO NOT EDIT.\n\n")
fprintf("package leveldb\n\n")
fprintf("import \"github.com/Mrs4s/go-cqhttp/db\"\n\n")
ast.Inspect(f, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.FuncDecl:
return false
case *ast.TypeSpec:
if !node.Name.IsExported() {
return false
}
x, ok := node.Type.(*ast.StructType)
if !ok {
return false
}
if x.Fields != nil && x.Fields.List != nil {
mkWrite(node)
mkRead(node)
}
}
return true
})
out, err := format.Source(output.Bytes())
if err != nil {
fmt.Println(string(output.Bytes()))
panic(err)
}
os.WriteFile("database_gen.go", out, 0o644)
}
func typeName(typ ast.Expr) string {
switch typ := typ.(type) {
case *ast.Ident:
return typ.Name
case *ast.ArrayType:
if typ.Len != nil {
panic("unexpected array type")
}
return "[]" + typeName(typ.Elt)
case *ast.SelectorExpr:
return typeName(typ.X) + "." + typ.Sel.Name
}
panic("unexpected type")
}
func mkWrite(node *ast.TypeSpec) {
typename := node.Name.String()
structType := node.Type.(*ast.StructType)
fprintf("func (w *writer) write%s(x *db.%s) {\n", typename, typename)
fprintf("if x == nil {\n")
fprintf("w.nil()\n")
fprintf("return\n")
fprintf("}\n")
fprintf("w.coder(coderStruct)\n")
for _, field := range structType.Fields.List {
switch typ := field.Type.(type) {
case *ast.Ident:
for _, name := range field.Names {
fprintf("w.%s(x.%s)\n", typ.Name, name.Name)
}
case *ast.ArrayType:
if typeName(typ) != "[]global.MSG" {
panic("unexpected array type")
}
for _, name := range field.Names {
fprintf("w.arrayMsg(x.%s)\n", name.Name)
}
case *ast.StarExpr:
for _, name := range field.Names {
fprintf("w.write%s(x.%s)\n", typeName(typ.X), name.Name)
}
}
}
fprintf("}\n\n")
}
func mkRead(node *ast.TypeSpec) {
typename := node.Name.String()
structType := node.Type.(*ast.StructType)
fprintf(`func (r *reader) read%s() *db.%s {
coder := r.coder()
if coder == coderNil {
return nil
}`+"\n", typename, typename)
fprintf("x := &db.%s{}\n", typename)
for _, field := range structType.Fields.List {
switch typ := field.Type.(type) {
case *ast.Ident:
for _, name := range field.Names {
fprintf("x.%s = r.%s()\n", name.Name, typ.Name)
}
case *ast.ArrayType:
if typeName(typ) != "[]global.MSG" {
panic("unexpected array type")
}
for _, name := range field.Names {
fprintf("x.%s = r.arrayMsg()\n", name.Name)
}
case *ast.StarExpr:
for _, name := range field.Names {
fprintf("x.%s = r.read%s()\n", name.Name, typeName(typ.X))
}
}
}
fprintf("return x\n")
fprintf("}\n\n")
}

View File

@ -1,5 +1,3 @@
// Code generated by mkrw.go; DO NOT EDIT.
package leveldb
import "github.com/Mrs4s/go-cqhttp/db"

View File

@ -2,7 +2,6 @@ package leveldb
import (
"bytes"
"io"
"github.com/Mrs4s/go-cqhttp/global"
)
@ -24,12 +23,25 @@ func (w *intWriter) uvarint(x uint64) {
}
// writer implements the index write.
//
// data format(use uvarint to encode integers):
// | version | string data length | index data length | string data | index data |
//
// - version
// - string data length
// - index data length
// - string data
// - index data
//
// for string data part, each string is encoded as:
// | string length | string |
// for index data part, each value is encoded as:
// | coder | value |
//
// - string length
// - string
//
// for index data part, each object value is encoded as:
//
// - coder
// - value
//
// * coder is the identifier of value's type.
// * specially for string, it's value is the offset in string data part.
type writer struct {
@ -125,7 +137,7 @@ func (w *writer) bytes() []byte {
out.uvarint(dataVersion)
out.uvarint(uint64(w.strings.Len()))
out.uvarint(uint64(w.data.Len()))
_, _ = io.Copy(&out, &w.strings)
_, _ = io.Copy(&out, &w.data)
_, _ = w.strings.WriteTo(&out)
_, _ = w.data.WriteTo(&out)
return out.Bytes()
}

View File

@ -2,26 +2,16 @@ package global
import (
"bytes"
"sync"
)
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
"github.com/Mrs4s/MiraiGo/binary" // 和 MiraiGo 共用同一 buffer 池
)
// NewBuffer 从池中获取新 bytes.Buffer
func NewBuffer() *bytes.Buffer {
return bufferPool.Get().(*bytes.Buffer)
return (*bytes.Buffer)(binary.SelectWriter())
}
// PutBuffer 将 Buffer放入池中
func PutBuffer(buf *bytes.Buffer) {
// See https://golang.org/issue/23199
const maxSize = 1 << 16
if buf != nil && buf.Cap() < maxSize { // 对于大Buffer直接丢弃
buf.Reset()
bufferPool.Put(buf)
}
binary.PutWriter((*binary.Writer)(buf))
}

View File

@ -43,6 +43,6 @@ func EncodeMP4(src string, dst string) error { // -y 覆盖文件
// ExtractCover 获取给定视频文件的Cover
func ExtractCover(src string, target string) error {
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-r", "1", "-f", "image2", target)
cmd := exec.Command("ffmpeg", "-i", src, "-y", "-ss", "0", "-frames:v", "1", target)
return errors.Wrap(cmd.Run(), "extract video cover failed")
}

View File

@ -5,15 +5,15 @@ import (
"crypto/md5"
"encoding/hex"
"errors"
"net"
"net/netip"
"net/url"
"os"
"path"
"runtime"
"strconv"
"strings"
"github.com/Mrs4s/MiraiGo/utils"
b14 "github.com/fumiama/go-base16384"
log "github.com/sirupsen/logrus"
"github.com/Mrs4s/go-cqhttp/internal/param"
@ -22,27 +22,18 @@ import (
const (
// ImagePath go-cqhttp使用的图片缓存目录
ImagePath = "data/images"
// ImagePathOld 兼容旧版go-cqhttp使用的图片缓存目录
ImagePathOld = "data/image"
// VoicePath go-cqhttp使用的语音缓存目录
VoicePath = "data/voices"
// VoicePathOld 兼容旧版go-cqhttp使用的语音缓存目录
VoicePathOld = "data/record"
// VideoPath go-cqhttp使用的视频缓存目录
VideoPath = "data/videos"
// CachePath go-cqhttp使用的缓存目录
CachePath = "data/cache"
// DumpsPath go-cqhttp使用错误转储目录
DumpsPath = "dumps"
)
var (
// ErrSyntax Path语法错误时返回的错误
ErrSyntax = errors.New("syntax error")
// HeaderAmr AMR文件头
HeaderAmr = []byte("#!AMR")
HeaderAmr = "#!AMR"
// HeaderSilk Silkv3文件头
HeaderSilk = []byte("\x02#!SILK_V3")
HeaderSilk = "\x02#!SILK_V3"
)
// PathExists 判断给定path是否存在
@ -78,13 +69,13 @@ func Check(err error, deleteSession bool) {
// IsAMRorSILK 判断给定文件是否为Amr或Silk格式
func IsAMRorSILK(b []byte) bool {
return bytes.HasPrefix(b, HeaderAmr) || bytes.HasPrefix(b, HeaderSilk)
return bytes.HasPrefix(b, []byte(HeaderAmr)) || bytes.HasPrefix(b, []byte(HeaderSilk))
}
// FindFile 从给定的File寻找文件并返回文件byte数组。File是一个合法的URL。p为文件寻找位置。
// 对于HTTP/HTTPS形式的URLCache为"1"或空时表示启用缓存
func FindFile(file, cache, p string) (data []byte, err error) {
data, err = nil, ErrSyntax
data, err = nil, os.ErrNotExist
switch {
case strings.HasPrefix(file, "http"): // https also has prefix http
hash := md5.Sum([]byte(file))
@ -102,6 +93,12 @@ func FindFile(file, cache, p string) (data []byte, err error) {
if err != nil {
return nil, err
}
case strings.HasPrefix(file, "base16384"):
data, err = b14.UTF82UTF16BE(utils.S2B(strings.TrimPrefix(file, "base16384://")))
if err != nil {
return nil, err
}
data = b14.Decode(data)
case strings.HasPrefix(file, "file"):
var fu *url.URL
fu, err = url.Parse(file)
@ -138,19 +135,18 @@ func DelFile(path string) bool {
}
// ReadAddrFile 从给定path中读取合法的IP地址与端口,每个IP地址以换行符"\n"作为分隔
func ReadAddrFile(path string) []*net.TCPAddr {
func ReadAddrFile(path string) []netip.AddrPort {
d, err := os.ReadFile(path)
if err != nil {
return nil
}
str := string(d)
lines := strings.Split(str, "\n")
var ret []*net.TCPAddr
var ret []netip.AddrPort
for _, l := range lines {
ip := strings.Split(strings.TrimSpace(l), ":")
if len(ip) == 2 {
port, _ := strconv.Atoi(ip[1])
ret = append(ret, &net.TCPAddr{IP: net.ParseIP(ip[0]), Port: port})
addr, err := netip.ParseAddrPort(l)
if err == nil {
ret = append(ret, addr)
}
}
return ret

View File

@ -195,7 +195,8 @@ func (f LogFormat) Format(entry *logrus.Entry) ([]byte, error) {
buf.WriteString(colorReset)
}
ret := append([]byte(nil), buf.Bytes()...) // copy buffer
ret := make([]byte, len(buf.Bytes()))
copy(ret, buf.Bytes()) // copy buffer
return ret, nil
}

View File

@ -79,7 +79,7 @@ func DownloadFile(url, path string, limit int64, headers map[string]string) erro
if limit > 0 && resp.ContentLength > limit {
return ErrOverSize
}
_, err = io.Copy(file, resp.Body)
_, err = file.ReadFrom(resp.Body)
if err != nil {
return err
}
@ -107,7 +107,7 @@ func DownloadFileMultiThreading(url, path string, limit int64, threadCount int,
return err
}
defer file.Close()
if _, err = io.Copy(file, s); err != nil {
if _, err = file.ReadFrom(s); err != nil {
return err
}
return errUnsupportedMultiThreading

15
go.mod
View File

@ -4,28 +4,29 @@ go 1.18
require (
github.com/Microsoft/go-winio v0.5.1
github.com/Mrs4s/MiraiGo v0.0.0-20220317085721-6d84141b8dd3
github.com/Mrs4s/MiraiGo v0.0.0-20220621083050-ae8c187aa59d
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc
github.com/fumiama/go-base16384 v1.5.2
github.com/fumiama/go-hide-param v0.1.4
github.com/gabriel-vasile/mimetype v1.4.0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/mattn/go-colorable v0.1.12
github.com/pkg/errors v0.9.1
github.com/segmentio/asm v1.1.3
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.1
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/tidwall/gjson v1.14.0
github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60
go.mongodb.org/mongo-driver v1.8.3
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/RomiChan/protobuf v0.0.0-20220227114948-643565fff248 // indirect
github.com/RomiChan/protobuf v0.1.1-0.20220602121309-9e3b8cbefd7a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fumiama/imgsz v0.0.2 // indirect
github.com/go-stack/stack v1.8.0 // indirect
@ -48,7 +49,7 @@ require (
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/text v0.3.7 // indirect
modernc.org/libc v1.8.1 // indirect
modernc.org/mathutil v1.2.2 // indirect
modernc.org/memory v1.0.4 // indirect

33
go.sum
View File

@ -1,9 +1,11 @@
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Mrs4s/MiraiGo v0.0.0-20220317085721-6d84141b8dd3 h1:U3UumMt052Ii1gGrkKM1MbX1uxCzxKhlfuNQ42LtIRQ=
github.com/Mrs4s/MiraiGo v0.0.0-20220317085721-6d84141b8dd3/go.mod h1:qJWkRO5vry/sUHthX5kh6go2llYIVAJ+Mq8p+N/FW+8=
github.com/RomiChan/protobuf v0.0.0-20220227114948-643565fff248 h1:1jRB6xuBKwfgZrg0bA7XJin0VeNwG9iJKx9RXwDobt4=
github.com/RomiChan/protobuf v0.0.0-20220227114948-643565fff248/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE=
github.com/Mrs4s/MiraiGo v0.0.0-20220621083050-ae8c187aa59d h1:Cq8HMtyL3PRpvOynuwi9WSdek2+5UTOd0zJ+JTq5hPM=
github.com/Mrs4s/MiraiGo v0.0.0-20220621083050-ae8c187aa59d/go.mod h1:mZp8Lt7uqLCUwSLouB2yuiP467Cwl4mnG9IMAaXUKA0=
github.com/RomiChan/protobuf v0.1.1-0.20220602121309-9e3b8cbefd7a h1:WIfEWYj82oEuPtm5pqlyQmCJCoiw00C6ugZFqHA0cC8=
github.com/RomiChan/protobuf v0.1.1-0.20220602121309-9e3b8cbefd7a/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c h1:cNPOdTNiVwxLpROLjXCgbIPvdkE+BwvxDvgmdYmWx6Q=
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c/go.mod h1:KqZzu7slNKROh3TSYEH/IUMG6f4M+1qubZ5e52QypsE=
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc h1:AAx50/fb/xS4lvsdQg+bFbGvqSDhyV1MF+p2PLCamZ0=
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc/go.mod h1:OMmITAib6POA37xCichWM0aRnoVpSMZO1rB/G01wrr0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -12,12 +14,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fumiama/go-base16384 v1.5.2 h1:cbxXTcDH92PNgG7bEBwiCEoWb5O+nwZKxKOG94ilFo8=
github.com/fumiama/go-base16384 v1.5.2/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-hide-param v0.1.4 h1:y7TRTzZMdCH9GOXnIzU3B+1BSkcmvejVGmGsz4t0DGU=
github.com/fumiama/go-hide-param v0.1.4/go.mod h1:vJkQlJIEI56nIyp7tCQu1/2QOyKtZpudsnJkGk9U1aY=
github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -85,8 +87,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
@ -96,8 +99,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea h1:sl1pYm1kHtIndckTY8YDt+QFt77vI0JnKHP0U8rZtKc=
github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea/go.mod h1:ecFKZPX81BaB70I6ruUgEwYcDOtuNgJGnjdK+MIl5ko=
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60 h1:lRKf10iIOW0VsH5WDF621ihzR+R2wEBZVtNRHuLLCb4=
github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60/go.mod h1:ecFKZPX81BaB70I6ruUgEwYcDOtuNgJGnjdK+MIl5ko=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
@ -120,7 +123,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -139,24 +141,21 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -180,8 +179,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/libc v1.8.1 h1:y9oPIhwcaFXxX7kMp6Qb2ZLKzr0mDkikWN3CV5GS63o=
modernc.org/libc v1.8.1/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=

View File

@ -188,7 +188,7 @@ func fromStream(updateWith io.Reader) (err error, errRecover error) {
}
// We won't log this error, because it's always going to happen.
defer func() { _ = fp.Close() }()
if _, err = io.Copy(fp, bufio.NewReader(updateWith)); err != nil {
if _, err = bufio.NewReader(updateWith).WriteTo(fp); err != nil {
logrus.Errorf("Unable to copy data: %v\n", err)
}

View File

@ -21,12 +21,12 @@ func (c *Caller) call(action string, p Getter) global.MSG {
case ".ocr_image", "ocr_image":
p0 := p.Get("image").String()
return c.bot.CQOcrImage(p0)
case "_get_group_notice":
p0 := p.Get("group_id").Int()
return c.bot.CQGetGroupMemo(p0)
case "_get_model_show":
p0 := p.Get("model").String()
return c.bot.CQGetModelShow(p0)
case "_get_vip_info":
p0 := p.Get("user_id").Int()
return c.bot.CQGetVipInfo(p0)
case "_send_group_notice":
p0 := p.Get("group_id").Int()
p1 := p.Get("content").String()
@ -195,6 +195,12 @@ func (c *Caller) call(action string, p Getter) global.MSG {
case "reload_event_filter":
p0 := p.Get("file").String()
return c.bot.CQReloadEventFilter(p0)
case "send_forward_msg":
p0 := p.Get("group_id").Int()
p1 := p.Get("user_id").Int()
p2 := p.Get("messages")
p3 := p.Get("message_type").String()
return c.bot.CQSendForwardMessage(p0, p1, p2, p3)
case "send_group_forward_msg":
p0 := p.Get("group_id").Int()
p1 := p.Get("messages")
@ -204,6 +210,9 @@ func (c *Caller) call(action string, p Getter) global.MSG {
p1 := p.Get("message")
p2 := p.Get("auto_escape").Bool()
return c.bot.CQSendGroupMessage(p0, p1, p2)
case "send_group_sign":
p0 := p.Get("group_id").Int()
return c.bot.CQSendGroupSign(p0)
case "send_guild_channel_msg":
p0 := p.Get("guild_id").Uint()
p1 := p.Get("channel_id").Uint()
@ -217,6 +226,10 @@ func (c *Caller) call(action string, p Getter) global.MSG {
p3 := p.Get("message_type").String()
p4 := p.Get("auto_escape").Bool()
return c.bot.CQSendMessage(p0, p1, p2, p3, p4)
case "send_private_forward_msg":
p0 := p.Get("user_id").Int()
p1 := p.Get("messages")
return c.bot.CQSendPrivateForwardMessage(p0, p1)
case "send_private_msg":
p0 := p.Get("user_id").Int()
p1 := p.Get("group_id").Int()
@ -324,5 +337,10 @@ func (c *Caller) call(action string, p Getter) global.MSG {
p2 := p.Get("name").String()
p3 := p.Get("folder").String()
return c.bot.CQUploadGroupFile(p0, p1, p2, p3)
case "upload_private_file":
p0 := p.Get("user_id").Int()
p1 := p.Get("file").String()
p2 := p.Get("name").String()
return c.bot.CQUploadPrivateFile(p0, p1, p2)
}
}

View File

@ -14,6 +14,7 @@ import (
)
// defaultConfig 默认配置文件
//
//go:embed default_config.yml
var defaultConfig string
@ -137,8 +138,7 @@ func expand(s string, mapping func(string) string) string {
r := regexp.MustCompile(`\${([a-zA-Z_]+[a-zA-Z0-9_:/.]*)}`)
return r.ReplaceAllStringFunc(s, func(s string) string {
s = strings.Trim(s, "${}")
// todo: use strings.Cut once go1.18 is released
before, after, ok := cut(s, ":")
before, after, ok := strings.Cut(s, ":")
m := mapping(before)
if ok && m == "" {
return after
@ -146,10 +146,3 @@ func expand(s string, mapping func(string) string) string {
return m
})
}
func cut(s, sep string) (before, after string, found bool) {
if i := strings.Index(s, sep); i >= 0 {
return s[:i], s[i+len(sep):], true
}
return s, "", false
}

View File

@ -3,68 +3,54 @@ package mime
import (
"io"
"github.com/gabriel-vasile/mimetype"
"github.com/sirupsen/logrus"
"net/http"
"strings"
"github.com/Mrs4s/go-cqhttp/internal/base"
)
func init() {
base.IsLawfulAudio = checkImage
base.IsLawfulImage = checkImage
base.IsLawfulAudio = checkAudio
}
// keep sync with /docs/file.md#MINE
var lawfulImage = [...]string{
"image/bmp",
"image/gif",
"image/jpeg",
"image/png",
"image/webp",
}
const limit = 4 * 1024
var lawfulAudio = [...]string{
"audio/aac",
"audio/aiff",
"audio/amr",
"audio/ape",
"audio/flac",
"audio/midi",
"audio/mp4",
"audio/mpeg",
"audio/ogg",
"audio/wav",
"audio/x-m4a",
}
func check(r io.ReadSeeker, list []string) (bool, string) {
if base.SkipMimeScan {
return true, ""
}
func scan(r io.ReadSeeker) string {
_, _ = r.Seek(0, io.SeekStart)
defer r.Seek(0, io.SeekStart)
t, err := mimetype.DetectReader(r)
if err != nil {
logrus.Debugf("扫描 Mime 时出现问题: %v", err)
return false, ""
}
for _, lt := range list {
if t.Is(lt) {
return true, t.String()
}
}
return false, t.String()
in := make([]byte, limit)
_, _ = r.Read(in)
return http.DetectContentType(in)
}
// checkImage 判断给定流是否为合法图片
// 返回 是否合法, 实际Mime
// 判断后会自动将 Stream Seek 至 0
func checkImage(r io.ReadSeeker) (bool, string) {
return check(r, lawfulImage[:])
func checkImage(r io.ReadSeeker) (ok bool, t string) {
if base.SkipMimeScan {
return true, ""
}
if r == nil {
return false, "image/nil-stream"
}
t = scan(r)
switch t {
case "image/bmp", "image/gif", "image/jpeg", "image/png", "image/webp":
ok = true
}
return
}
// checkImage 判断给定流是否为合法音频
func checkAudio(r io.ReadSeeker) (bool, string) {
return check(r, lawfulAudio[:])
if base.SkipMimeScan {
return true, ""
}
t := scan(r)
// std mime type detection is not full supported for audio
if strings.Contains(t, "text") || strings.Contains(t, "image") {
return false, t
}
return true, t
}

View File

@ -3,9 +3,9 @@ package server
// daemon 功能写在这,目前仅支持了-d 作为后台运行参数stopstartrestart这些功能目前看起来并不需要可以通过api控制后续需要的话再补全。
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"github.com/Mrs4s/go-cqhttp/global"
@ -36,7 +36,7 @@ func Daemon() {
log.Info("[PID] ", proc.Process.Pid)
// pid写入到pid文件中方便后续stop的时候kill
pidErr := savePid("go-cqhttp.pid", fmt.Sprintf("%d", proc.Process.Pid))
pidErr := savePid("go-cqhttp.pid", strconv.FormatInt(int64(proc.Process.Pid), 10))
if pidErr != nil {
log.Errorf("save pid file error: %v", pidErr)
}

View File

@ -2,12 +2,15 @@ package server
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
@ -31,6 +34,7 @@ import (
// HTTPServer HTTP通信相关配置
type HTTPServer struct {
Disabled bool `yaml:"disabled"`
Address string `yaml:"address"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Timeout int32 `yaml:"timeout"`
@ -63,6 +67,7 @@ type HTTPClient struct {
filter string
apiPort int
timeout int32
client *http.Client
MaxRetries uint64
RetriesInterval uint64
}
@ -75,8 +80,7 @@ type httpCtx struct {
const httpDefault = `
- http: # HTTP 通信设置
host: 127.0.0.1 # 服务端监听地址
port: 5700 # 服务端监听端口
address: 0.0.0.0:5700 # HTTP监听地址
timeout: 5 # 反向 HTTP 超时时间, 单位秒,<5 时将被忽略
long-polling: # 长轮询拓展
enabled: false # 是否开启
@ -208,9 +212,9 @@ func checkAuth(req *http.Request, token string) int {
if auth == "" {
auth = req.URL.Query().Get("access_token")
} else {
authN := strings.SplitN(auth, " ", 2)
if len(authN) == 2 {
auth = authN[1]
_, after, ok := strings.Cut(auth, " ")
if ok {
auth = after
}
}
@ -242,12 +246,21 @@ func runHTTP(bot *coolq.CQBot, node yaml.Node) {
return
}
var addr string
network, addr := "tcp", conf.Address
s := &httpServer{accessToken: conf.AccessToken}
if conf.Host == "" || conf.Port == 0 {
switch {
case conf.Address != "":
uri, err := url.Parse(conf.Address)
if err == nil && uri.Scheme != "" {
network = uri.Scheme
addr = uri.Host + uri.Path
}
case conf.Host != "" || conf.Port != 0:
addr = fmt.Sprintf("%s:%d", conf.Host, conf.Port)
log.Warnln("HTTP 服务器使用了过时的配置格式,请更新配置文件!")
default:
goto client
}
addr = fmt.Sprintf("%s:%d", conf.Host, conf.Port)
s.api = api.NewCaller(bot)
if conf.RateLimit.Enabled {
s.api.Use(rateLimit(conf.RateLimit.Frequency, conf.RateLimit.Bucket))
@ -255,20 +268,16 @@ func runHTTP(bot *coolq.CQBot, node yaml.Node) {
if conf.LongPolling.Enabled {
s.api.Use(longPolling(bot, conf.LongPolling.MaxQueueSize))
}
go func() {
log.Infof("CQ HTTP 服务器已启动: %v", addr)
server := &http.Server{
Addr: addr,
Handler: s,
}
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Error(err)
log.Infof("HTTP 服务启动失败, 请检查端口是否被占用.")
listener, err := net.Listen(network, addr)
if err != nil {
log.Infof("HTTP 服务启动失败, 请检查端口是否被占用: %v", err)
log.Warnf("将在五秒后退出.")
time.Sleep(time.Second * 5)
os.Exit(1)
}
log.Infof("CQ HTTP 服务器已启动: %v", listener.Addr())
log.Fatal(http.Serve(listener, s))
}()
client:
for _, c := range conf.Post {
@ -293,8 +302,30 @@ func (c HTTPClient) Run() {
if c.timeout < 5 {
c.timeout = 5
}
rawAddress := c.addr
network, address := resolveURI(c.addr)
client := &http.Client{
Timeout: time.Second * time.Duration(c.timeout),
Transport: &http.Transport{
DialContext: func(_ context.Context, _, addr string) (net.Conn, error) {
if network == "unix" {
host, _, err := net.SplitHostPort(addr)
if err != nil {
host = addr
}
filepath, err := base64.RawURLEncoding.DecodeString(host)
if err == nil {
addr = string(filepath)
}
}
return net.Dial(network, addr)
},
},
}
c.addr = address // clean path
c.client = client
log.Infof("HTTP POST上报器已启动: %v", rawAddress)
c.bot.OnEventPush(c.onBotPushEvent)
log.Infof("HTTP POST上报器已启动: %v", c.addr)
}
func (c *HTTPClient) onBotPushEvent(e *coolq.Event) {
@ -306,7 +337,6 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) {
}
}
client := http.Client{Timeout: time.Second * time.Duration(c.timeout)}
header := make(http.Header)
header.Set("X-Self-ID", strconv.FormatInt(c.bot.Client.Uin, 10))
header.Set("User-Agent", "CQHttp/4.15.0")
@ -320,36 +350,33 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) {
header.Set("X-API-Port", strconv.FormatInt(int64(c.apiPort), 10))
}
var req *http.Request
var res *http.Response
var err error
for i := uint64(0); i <= c.MaxRetries; i++ {
// see https://stackoverflow.com/questions/31337891/net-http-http-contentlength-222-with-body-length-0
// we should create a new request for every single post trial
req, err := http.NewRequest("POST", c.addr, bytes.NewReader(e.JSONBytes()))
req, err = http.NewRequest("POST", c.addr, bytes.NewReader(e.JSONBytes()))
if err != nil {
log.Warnf("上报 Event 数据到 %v 时创建请求失败: %v", c.addr, err)
return
}
req.Header = header
res, err = client.Do(req)
if res != nil {
//goland:noinspection GoDeferInLoop
defer res.Body.Close()
}
res, err = c.client.Do(req)
if err == nil {
break
}
if i < c.MaxRetries {
log.Warnf("上报 Event 数据到 %v 失败: %v 将进行第 %d 次重试", c.addr, err, i+1)
} else {
log.Warnf("上报 Event 数据 %s 到 %v 失败: %v 停止上报:已达重试上线", e.JSONBytes(), c.addr, err)
log.Warnf("上报 Event 数据 %s 到 %v 失败: %v 停止上报:已达重试上", e.JSONBytes(), c.addr, err)
return
}
time.Sleep(time.Millisecond * time.Duration(c.RetriesInterval))
}
defer res.Body.Close()
log.Debugf("上报Event数据 %s 到 %v", e.JSONBytes(), c.addr)
r, err := io.ReadAll(res.Body)
if err != nil {
return

View File

@ -39,7 +39,7 @@ func longPolling(bot *coolq.CQBot, maxSize int) api.Handler {
bot.OnEventPush(func(event *coolq.Event) {
mutex.Lock()
defer mutex.Unlock()
queue.PushBack(event.RawMsg)
queue.PushBack(event.Raw)
for maxSize != 0 && queue.Len() > maxSize {
queue.Remove(queue.Front())
}
@ -50,33 +50,37 @@ func longPolling(bot *coolq.CQBot, maxSize int) api.Handler {
return nil
}
var (
once sync.Once
ch = make(chan []interface{}, 1)
ch = make(chan []interface{})
timeout = time.Duration(p.Get("timeout").Int()) * time.Second
)
defer close(ch)
go func() {
mutex.Lock()
defer mutex.Unlock()
if queue.Len() == 0 {
for queue.Len() == 0 {
cond.Wait()
}
once.Do(func() {
limit := int(p.Get("limit").Int())
if limit <= 0 || queue.Len() < limit {
limit = queue.Len()
limit := int(p.Get("limit").Int())
if limit <= 0 || queue.Len() < limit {
limit = queue.Len()
}
ret := make([]interface{}, limit)
elem := queue.Front()
for i := 0; i < limit; i++ {
ret[i] = elem.Value
elem = elem.Next()
}
select {
case ch <- ret:
for i := 0; i < limit; i++ { // remove sent msg
queue.Remove(queue.Front())
}
ret := make([]interface{}, limit)
for i := 0; i < limit; i++ {
ret[i] = queue.Remove(queue.Front())
}
ch <- ret
})
default:
// don't block if parent already return due to timeout
}
}()
if timeout != 0 {
select {
case <-time.After(timeout):
once.Do(func() {})
return coolq.OK([]interface{}{})
case ret := <-ch:
return coolq.OK(ret)

View File

@ -54,7 +54,7 @@ func (l *lambdaResponseWriter) flush() error {
buffer := global.NewBuffer()
defer global.PutBuffer(buffer)
body := utils.B2S(l.buf.Bytes())
header := make(map[string]string)
header := make(map[string]string, len(l.header))
for k, v := range l.header {
header[k] = v[0]
}

View File

@ -2,9 +2,12 @@ package server
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"runtime/debug"
"strconv"
"strings"
@ -75,9 +78,7 @@ var upgrader = websocket.Upgrader{
const wsDefault = ` # 正向WS设置
- ws:
# 正向WS服务器监听地址
host: 127.0.0.1
# 正向WS服务器监听端口
port: 6700
address: 0.0.0.0:8080
middlewares:
<<: *default # 引用默认中间件
`
@ -100,6 +101,7 @@ const wsReverseDefault = ` # 反向WS设置
// WebsocketServer 正向WS相关配置
type WebsocketServer struct {
Disabled bool `yaml:"disabled"`
Address string `yaml:"address"`
Host string `yaml:"host"`
Port int `yaml:"port"`
@ -139,6 +141,17 @@ func runWSServer(b *coolq.CQBot, node yaml.Node) {
return
}
network, address := "tcp", conf.Address
if conf.Address == "" && (conf.Host != "" || conf.Port != 0) {
log.Warn("正向 Websocket 使用了过时的配置格式,请更新配置文件")
address = fmt.Sprintf("%s:%d", conf.Host, conf.Port)
} else {
uri, err := url.Parse(conf.Address)
if err == nil && uri.Scheme != "" {
network = uri.Scheme
address = uri.Host + uri.Path
}
}
s := &webSocketServer{
bot: b,
conf: &conf,
@ -146,7 +159,6 @@ func runWSServer(b *coolq.CQBot, node yaml.Node) {
filter: conf.Filter,
}
filter.Add(s.filter)
addr := fmt.Sprintf("%s:%d", conf.Host, conf.Port)
s.handshake = fmt.Sprintf(`{"_post_method":2,"meta_event_type":"lifecycle","post_type":"meta_event","self_id":%d,"sub_type":"connect","time":%d}`,
b.Client.Uin, time.Now().Unix())
b.OnEventPush(s.onBotPushEvent)
@ -154,8 +166,12 @@ func runWSServer(b *coolq.CQBot, node yaml.Node) {
mux.HandleFunc("/event", s.event)
mux.HandleFunc("/api", s.api)
mux.HandleFunc("/", s.any)
log.Infof("CQ WebSocket 服务器已启动: %v", addr)
log.Fatal(http.ListenAndServe(addr, &mux))
listener, err := net.Listen(network, address)
if err != nil {
log.Fatal(err)
}
log.Infof("CQ WebSocket 服务器已启动: %v", listener.Addr())
log.Fatal(http.Serve(listener, &mux))
}
// runWSClient 运行一个反向向WS client
@ -196,8 +212,26 @@ func runWSClient(b *coolq.CQBot, node yaml.Node) {
}
}
func (c *websocketClient) connect(typ, url string, conptr **wsConn) {
log.Infof("开始尝试连接到反向WebSocket %s服务器: %v", typ, url)
func resolveURI(addr string) (network, address string) {
network, address = "tcp", addr
uri, err := url.Parse(addr)
if err == nil && uri.Scheme != "" {
scheme, ext, _ := strings.Cut(uri.Scheme, "+")
if ext != "" {
network = ext
uri.Scheme = scheme // remove `+unix`/`+tcp4`
if ext == "unix" {
uri.Host, uri.Path, _ = strings.Cut(uri.Path, ":")
uri.Host = base64.StdEncoding.EncodeToString([]byte(uri.Host))
}
address = uri.String()
}
}
return
}
func (c *websocketClient) connect(typ, addr string, conptr **wsConn) {
log.Infof("开始尝试连接到反向WebSocket %s服务器: %v", typ, addr)
header := http.Header{
"X-Client-Role": []string{typ},
"X-Self-ID": []string{strconv.FormatInt(c.bot.Client.Uin, 10)},
@ -206,12 +240,30 @@ func (c *websocketClient) connect(typ, url string, conptr **wsConn) {
if c.token != "" {
header["Authorization"] = []string{"Token " + c.token}
}
conn, _, err := websocket.DefaultDialer.Dial(url, header) // nolint
network, address := resolveURI(addr)
dialer := websocket.Dialer{
NetDial: func(_, addr string) (net.Conn, error) {
if network == "unix" {
host, _, err := net.SplitHostPort(addr)
if err != nil {
host = addr
}
filepath, err := base64.RawURLEncoding.DecodeString(host)
if err == nil {
addr = string(filepath)
}
}
return net.Dial(network, addr) // support unix socket transport
},
}
conn, _, err := dialer.Dial(address, header) // nolint
if err != nil {
log.Warnf("连接到反向WebSocket %s服务器 %v 时出现错误: %v", typ, url, err)
log.Warnf("连接到反向WebSocket %s服务器 %v 时出现错误: %v", typ, addr, err)
if c.reconnectInterval != 0 {
time.Sleep(c.reconnectInterval)
c.connect(typ, url, conptr)
c.connect(typ, addr, conptr)
}
return
}
@ -225,7 +277,7 @@ func (c *websocketClient) connect(typ, url string, conptr **wsConn) {
}
}
log.Infof("已连接到反向WebSocket %s服务器 %v", typ, url)
log.Infof("已连接到反向WebSocket %s服务器 %v", typ, addr)
var wrappedConn *wsConn
if conptr != nil && *conptr != nil {
@ -244,7 +296,7 @@ func (c *websocketClient) connect(typ, url string, conptr **wsConn) {
}
if typ != "Event" {
go c.listenAPI(typ, url, wrappedConn)
go c.listenAPI(typ, addr, wrappedConn)
}
}