mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-05-04 19:17:37 +08:00
Merge branch 'dev' into master
This commit is contained in:
commit
837e163ef6
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@ -12,7 +12,8 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
run: |
|
run: |
|
||||||
git version
|
git version
|
||||||
git clone https://github.com/Mrs4s/go-cqhttp.git /home/runner/work/go-cqhttp/go-cqhttp
|
git clone "${{ github.event.repository.html_url }}" /home/runner/work/go-cqhttp/go-cqhttp
|
||||||
|
git checkout "${{ github.ref }}"
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
|
@ -8,13 +8,16 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/client"
|
"github.com/Mrs4s/MiraiGo/client"
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
"github.com/Mrs4s/MiraiGo/utils"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
|
||||||
"github.com/mattn/go-colorable"
|
"github.com/mattn/go-colorable"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -22,6 +25,7 @@ import (
|
|||||||
"gopkg.ilharper.com/x/isatty"
|
"gopkg.ilharper.com/x/isatty"
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/global"
|
"github.com/Mrs4s/go-cqhttp/global"
|
||||||
|
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/download"
|
"github.com/Mrs4s/go-cqhttp/internal/download"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -265,15 +269,26 @@ func fetchCaptcha(id string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func energy(uin uint64, id string, appVersion string, salt []byte) ([]byte, error) {
|
func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) {
|
||||||
signServer := base.SignServer
|
signServer := base.SignServer
|
||||||
if !strings.HasSuffix(signServer, "/") {
|
if !strings.HasSuffix(signServer, "/") {
|
||||||
signServer += "/"
|
signServer += "/"
|
||||||
}
|
}
|
||||||
response, err := download.Request{
|
headers := make(map[string]string)
|
||||||
|
signServerBearer := base.SignServerBearer
|
||||||
|
if signServerBearer != "-" && signServerBearer != "" {
|
||||||
|
headers["Authorization"] = "Bearer " + signServerBearer
|
||||||
|
}
|
||||||
|
req := download.Request{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: signServer + "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt)),
|
Header: headers,
|
||||||
}.Bytes()
|
URL: signServer + "custom_energy" + fmt.Sprintf("?data=%v&salt=%v&uin=%v&android_id=%v&guid=%v",
|
||||||
|
id, hex.EncodeToString(salt), uin, utils.B2S(device.AndroidId), hex.EncodeToString(device.Guid)),
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second)
|
||||||
|
if base.IsBelow110 {
|
||||||
|
req.URL = signServer + "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt))
|
||||||
|
}
|
||||||
|
response, err := req.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("获取T544 sign时出现错误: %v server: %v", err, signServer)
|
log.Warnf("获取T544 sign时出现错误: %v server: %v", err, signServer)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -290,23 +305,242 @@ func energy(uin uint64, id string, appVersion string, salt []byte) ([]byte, erro
|
|||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) {
|
// signSubmit 提交的操作类型
|
||||||
|
func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t string) {
|
||||||
signServer := base.SignServer
|
signServer := base.SignServer
|
||||||
if !strings.HasSuffix(signServer, "/") {
|
if !strings.HasSuffix(signServer, "/") {
|
||||||
signServer += "/"
|
signServer += "/"
|
||||||
}
|
}
|
||||||
|
buffStr := hex.EncodeToString(buffer)
|
||||||
|
log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer-end=%v", t, uin, cmd, callbackID,
|
||||||
|
buffStr[len(buffStr)-10:])
|
||||||
|
_, err := download.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: signServer + "submit" + fmt.Sprintf("?uin=%v&cmd=%v&callback_id=%v&buffer=%v",
|
||||||
|
uin, cmd, callbackID, buffStr),
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("提交 callback 时出现错误: %v server: %v", err, signServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// signCallback request token 和签名的回调
|
||||||
|
func signCallback(uin string, results []gjson.Result, t string) {
|
||||||
|
for _, result := range results {
|
||||||
|
cmd := result.Get("cmd").String()
|
||||||
|
callbackID := result.Get("callbackId").Int()
|
||||||
|
body, _ := hex.DecodeString(result.Get("body").String())
|
||||||
|
ret, err := cli.SendSsoPacket(cmd, body)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("callback error: %v", err)
|
||||||
|
}
|
||||||
|
signSubmit(uin, cmd, callbackID, ret, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signRequset(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) {
|
||||||
|
signServer := base.SignServer
|
||||||
|
if !strings.HasSuffix(signServer, "/") {
|
||||||
|
signServer += "/"
|
||||||
|
}
|
||||||
|
headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
|
signServerBearer := base.SignServerBearer
|
||||||
|
if signServerBearer != "-" && signServerBearer != "" {
|
||||||
|
headers["Authorization"] = "Bearer " + signServerBearer
|
||||||
|
}
|
||||||
response, err := download.Request{
|
response, err := download.Request{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
URL: signServer + "sign",
|
URL: signServer + "sign",
|
||||||
Header: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
|
Header: headers,
|
||||||
Body: bytes.NewReader([]byte(fmt.Sprintf("uin=%v&qua=%s&cmd=%s&seq=%v&buffer=%v", uin, qua, cmd, seq, hex.EncodeToString(buff)))),
|
Body: bytes.NewReader([]byte(fmt.Sprintf("uin=%v&qua=%s&cmd=%s&seq=%v&buffer=%v&android_id=%v&guid=%v",
|
||||||
}.Bytes()
|
uin, qua, cmd, seq, hex.EncodeToString(buff), utils.B2S(device.AndroidId), hex.EncodeToString(device.Guid)))),
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("获取sso sign时出现错误: %v server: %v", err, signServer)
|
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
sign, _ = hex.DecodeString(gjson.GetBytes(response, "data.sign").String())
|
sign, _ = hex.DecodeString(gjson.GetBytes(response, "data.sign").String())
|
||||||
extra, _ = hex.DecodeString(gjson.GetBytes(response, "data.extra").String())
|
extra, _ = hex.DecodeString(gjson.GetBytes(response, "data.extra").String())
|
||||||
token, _ = hex.DecodeString(gjson.GetBytes(response, "data.token").String())
|
token, _ = hex.DecodeString(gjson.GetBytes(response, "data.token").String())
|
||||||
|
if !base.IsBelow110 {
|
||||||
|
go signCallback(uin, gjson.GetBytes(response, "data.requestCallback").Array(), "sign")
|
||||||
|
}
|
||||||
return sign, extra, token, nil
|
return sign, extra, token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var registerLock sync.Mutex
|
||||||
|
|
||||||
|
func signRegister(uin int64, androidID, guid []byte, qimei36, key string) {
|
||||||
|
if base.IsBelow110 {
|
||||||
|
log.Warn("签名服务器版本低于1.1.0, 跳过实例注册")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signServer := base.SignServer
|
||||||
|
if !strings.HasSuffix(signServer, "/") {
|
||||||
|
signServer += "/"
|
||||||
|
}
|
||||||
|
resp, err := download.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: signServer + "register" + fmt.Sprintf("?uin=%v&android_id=%v&guid=%v&qimei36=%v&key=%s",
|
||||||
|
uin, utils.B2S(androidID), hex.EncodeToString(guid), qimei36, key),
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("注册QQ实例时出现错误: %v server: %v", err, signServer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := gjson.GetBytes(resp, "msg")
|
||||||
|
if gjson.GetBytes(resp, "code").Int() != 0 {
|
||||||
|
log.Warnf("注册QQ实例时出现错误: %v server: %v", msg, signServer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("注册QQ实例 %v 成功: %v", uin, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func signRefreshToken(uin string) error {
|
||||||
|
signServer := base.SignServer
|
||||||
|
if !strings.HasSuffix(signServer, "/") {
|
||||||
|
signServer += "/"
|
||||||
|
}
|
||||||
|
log.Info("正在刷新 token")
|
||||||
|
resp, err := download.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: signServer + "request_token" + fmt.Sprintf("?uin=%v", uin),
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg := gjson.GetBytes(resp, "msg")
|
||||||
|
if gjson.GetBytes(resp, "code").Int() != 0 {
|
||||||
|
return errors.New(msg.String())
|
||||||
|
}
|
||||||
|
go signCallback(uin, gjson.GetBytes(resp, "data").Array(), "request token")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var missTokenCount = uint64(0)
|
||||||
|
|
||||||
|
func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) {
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
sign, extra, token, err = signRequset(seq, uin, cmd, qua, buff)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("获取sso sign时出现错误: %v server: %v", err, base.SignServer)
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if (!base.IsBelow110) && base.Account.AutoRegister && err == nil && len(sign) == 0 {
|
||||||
|
if registerLock.TryLock() { // 避免并发时多处同时销毁并重新注册
|
||||||
|
log.Warn("获取签名为空,实例可能丢失,正在尝试重新注册")
|
||||||
|
defer registerLock.Unlock()
|
||||||
|
err := signServerDestroy(uin)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln(err)
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, base.Key)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!base.IsBelow110) && base.Account.AutoRefreshToken && len(token) == 0 {
|
||||||
|
log.Warnf("token 已过期, 总丢失 token 次数为 %v", atomic.AddUint64(&missTokenCount, 1))
|
||||||
|
if registerLock.TryLock() {
|
||||||
|
defer registerLock.Unlock()
|
||||||
|
if err := signRefreshToken(uin); err != nil {
|
||||||
|
log.Warnf("刷新 token 出现错误: %v server: %v", err, base.SignServer)
|
||||||
|
} else {
|
||||||
|
log.Info("刷新 token 成功")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return sign, extra, token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func signServerDestroy(uin string) error {
|
||||||
|
signServer := base.SignServer
|
||||||
|
if !strings.HasSuffix(signServer, "/") {
|
||||||
|
signServer += "/"
|
||||||
|
}
|
||||||
|
signVersion, err := signVersion()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "获取签名服务版本出现错误, server: %v", signServer)
|
||||||
|
}
|
||||||
|
if global.VersionNameCompare("v"+signVersion, "v1.1.6") {
|
||||||
|
return errors.Errorf("当前签名服务器版本 %v 低于 1.1.6,无法使用 destroy 接口", signVersion)
|
||||||
|
}
|
||||||
|
resp, err := download.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: signServer + "destroy" + fmt.Sprintf("?uin=%v&key=%v", uin, base.Key),
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
||||||
|
if err != nil || gjson.GetBytes(resp, "code").Int() != 0 {
|
||||||
|
return errors.Wrapf(err, "destroy 实例出现错误, server: %v", signServer)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func signVersion() (version string, err error) {
|
||||||
|
signServer := base.SignServer
|
||||||
|
resp, err := download.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: signServer,
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if gjson.GetBytes(resp, "code").Int() == 0 {
|
||||||
|
return gjson.GetBytes(resp, "data.version").String(), nil
|
||||||
|
}
|
||||||
|
return "", errors.New("empty version")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时刷新 token, interval 为间隔时间(分钟)
|
||||||
|
func signStartRefreshToken(interval int64) {
|
||||||
|
if interval <= 0 {
|
||||||
|
log.Warn("定时刷新 token 已关闭")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Infof("每 %v 分钟将刷新一次签名 token", interval)
|
||||||
|
if interval < 10 {
|
||||||
|
log.Warnf("间隔时间 %v 分钟较短,推荐 30~40 分钟", interval)
|
||||||
|
}
|
||||||
|
if interval > 60 {
|
||||||
|
log.Warn("间隔时间不能超过 60 分钟,已自动设置为 60 分钟")
|
||||||
|
interval = 60
|
||||||
|
}
|
||||||
|
t := time.NewTicker(time.Duration(interval) * time.Minute)
|
||||||
|
defer t.Stop()
|
||||||
|
for range t.C {
|
||||||
|
err := signRefreshToken(strconv.FormatInt(base.Account.Uin, 10))
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("刷新 token 出现错误: %v server: %v", err, base.SignServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signWaitServer() bool {
|
||||||
|
t := time.NewTicker(time.Second * 5)
|
||||||
|
defer t.Stop()
|
||||||
|
i := 0
|
||||||
|
for range t.C {
|
||||||
|
if i > 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
u, err := url.Parse(base.SignServer)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("连接到签名服务器出现错误: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r := utils.RunTCPPingLoop(u.Host, 4)
|
||||||
|
if r.PacketsLoss > 0 {
|
||||||
|
log.Warnf("连接到签名服务器出现错误: 丢包%d/%d 时延%dms", r.PacketsLoss, r.PacketsSent, r.AvgTimeMill)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Infof("连接至签名服务器: %s", base.SignServer)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -166,8 +166,27 @@ func LoginInteract() {
|
|||||||
|
|
||||||
if base.SignServer != "-" && base.SignServer != "" {
|
if base.SignServer != "-" && base.SignServer != "" {
|
||||||
log.Infof("使用服务器 %s 进行数据包签名", base.SignServer)
|
log.Infof("使用服务器 %s 进行数据包签名", base.SignServer)
|
||||||
|
if base.SignServerBearer != "-" && base.SignServerBearer != "" {
|
||||||
|
log.Infof("使用 Bearer %s 认证签名服务器 %s ", base.SignServerBearer, base.SignServer)
|
||||||
|
}
|
||||||
|
// 等待签名服务器直到连接成功
|
||||||
|
if !signWaitServer() {
|
||||||
|
log.Fatalf("连接签名服务器失败")
|
||||||
|
}
|
||||||
|
signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, base.Key)
|
||||||
|
go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token
|
||||||
wrapper.DandelionEnergy = energy
|
wrapper.DandelionEnergy = energy
|
||||||
wrapper.FekitGetSign = sign
|
wrapper.FekitGetSign = sign
|
||||||
|
if !base.IsBelow110 {
|
||||||
|
if !base.Account.AutoRegister {
|
||||||
|
log.Warn("自动注册实例已关闭,若未配置 sign-server 端自动注册实例则实例丢失时需要重启 go-cqhttp 以正常签名")
|
||||||
|
}
|
||||||
|
if !base.Account.AutoRefreshToken {
|
||||||
|
log.Warn("自动刷新 token 已关闭,token 过期后获取签名时将不会立即尝试刷新获取新 token")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warn("签名服务器版本 <= 1.1.0 ,无法使用刷新 token 等操作,建议使用 1.1.6 版本及以上签名服务器")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("警告: 未配置签名服务器, 这可能会导致登录 45 错误码或发送消息被风控")
|
log.Warnf("警告: 未配置签名服务器, 这可能会导致登录 45 错误码或发送消息被风控")
|
||||||
}
|
}
|
||||||
@ -287,6 +306,7 @@ func LoginInteract() {
|
|||||||
cli.Uin = base.Account.Uin
|
cli.Uin = base.Account.Uin
|
||||||
cli.PasswordMd5 = base.PasswordHash
|
cli.PasswordMd5 = base.PasswordHash
|
||||||
}
|
}
|
||||||
|
download.SetTimeout(time.Duration(base.HTTPTimeout) * time.Second)
|
||||||
if !base.FastStart {
|
if !base.FastStart {
|
||||||
log.Infof("正在检查协议更新...")
|
log.Infof("正在检查协议更新...")
|
||||||
currentVersionName := device.Protocol.Version().SortVersionName
|
currentVersionName := device.Protocol.Version().SortVersionName
|
||||||
@ -373,7 +393,6 @@ func LoginInteract() {
|
|||||||
})
|
})
|
||||||
saveToken()
|
saveToken()
|
||||||
cli.AllowSlider = true
|
cli.AllowSlider = true
|
||||||
download.SetTimeout(time.Duration(base.HTTPTimeout) * time.Second) // 在登录完成后设置, 防止在堵塞协议更新
|
|
||||||
log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
|
log.Infof("登录成功 欢迎使用: %v", cli.Nickname)
|
||||||
log.Info("开始加载好友列表...")
|
log.Info("开始加载好友列表...")
|
||||||
global.Check(cli.ReloadFriendList(), true)
|
global.Check(cli.ReloadFriendList(), true)
|
||||||
|
66
coolq/api.go
66
coolq/api.go
@ -896,7 +896,7 @@ func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType
|
|||||||
SenderId: m.GetAttribute().SenderUin,
|
SenderId: m.GetAttribute().SenderUin,
|
||||||
SenderName: m.GetAttribute().SenderName,
|
SenderName: m.GetAttribute().SenderName,
|
||||||
Time: int32(msgTime),
|
Time: int32(msgTime),
|
||||||
Message: resolveElement(bot.ConvertContentMessage(m.GetContent(), mSource)),
|
Message: resolveElement(bot.ConvertContentMessage(m.GetContent(), mSource, false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
|
log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
|
||||||
@ -971,7 +971,10 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa
|
|||||||
if m.Type != gjson.JSON {
|
if m.Type != gjson.JSON {
|
||||||
return Failed(100)
|
return Failed(100)
|
||||||
}
|
}
|
||||||
|
source := message.Source{
|
||||||
|
SourceType: message.SourcePrivate,
|
||||||
|
PrimaryID: 0,
|
||||||
|
}
|
||||||
fe := bot.uploadForwardElement(m, groupID, message.SourceGroup)
|
fe := bot.uploadForwardElement(m, groupID, message.SourceGroup)
|
||||||
if fe == nil {
|
if fe == nil {
|
||||||
return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
|
return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
|
||||||
@ -981,7 +984,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa
|
|||||||
log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.")
|
log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.")
|
||||||
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
|
return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
|
||||||
}
|
}
|
||||||
mid := bot.InsertGroupMessage(ret)
|
mid := bot.InsertGroupMessage(ret, source)
|
||||||
log.Infof("发送群 %v(%v) 的合并转发消息: %v (%v)", groupID, groupID, limitedString(m.String()), mid)
|
log.Infof("发送群 %v(%v) 的合并转发消息: %v (%v)", groupID, groupID, limitedString(m.String()), mid)
|
||||||
return OK(global.MSG{
|
return OK(global.MSG{
|
||||||
"message_id": mid,
|
"message_id": mid,
|
||||||
@ -1110,17 +1113,18 @@ func (bot *CQBot) CQSetGroupMemo(groupID int64, msg, img string) global.MSG {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return Failed(100, "IMAGE_NOT_FOUND", "图片未找到")
|
return Failed(100, "IMAGE_NOT_FOUND", "图片未找到")
|
||||||
}
|
}
|
||||||
_, err = bot.Client.AddGroupNoticeWithPic(groupID, msg, data)
|
noticeID, err := bot.Client.AddGroupNoticeWithPic(groupID, msg, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failed(100, "SEND_NOTICE_ERROR", err.Error())
|
return Failed(100, "SEND_NOTICE_ERROR", err.Error())
|
||||||
}
|
}
|
||||||
|
return OK(global.MSG{"notice_id": noticeID})
|
||||||
} else {
|
} else {
|
||||||
_, err := bot.Client.AddGroupNoticeSimple(groupID, msg)
|
noticeID, err := bot.Client.AddGroupNoticeSimple(groupID, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Failed(100, "SEND_NOTICE_ERROR", err.Error())
|
return Failed(100, "SEND_NOTICE_ERROR", err.Error())
|
||||||
}
|
}
|
||||||
|
return OK(global.MSG{"notice_id": noticeID})
|
||||||
}
|
}
|
||||||
return OK(nil)
|
|
||||||
}
|
}
|
||||||
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
||||||
}
|
}
|
||||||
@ -1149,15 +1153,15 @@ func (bot *CQBot) CQDelGroupMemo(groupID int64, fid string) global.MSG {
|
|||||||
// @rename(msg->message, block->reject_add_request)
|
// @rename(msg->message, block->reject_add_request)
|
||||||
func (bot *CQBot) CQSetGroupKick(groupID int64, userID int64, msg string, block bool) global.MSG {
|
func (bot *CQBot) CQSetGroupKick(groupID int64, userID int64, msg string, block bool) global.MSG {
|
||||||
if g := bot.Client.FindGroup(groupID); g != nil {
|
if g := bot.Client.FindGroup(groupID); g != nil {
|
||||||
if m := g.FindMember(userID); m == nil {
|
m := g.FindMember(userID)
|
||||||
return Failed(100, "MEMBER_IS_NOT_IN_GROUP", "人员不存在")
|
if m == nil {
|
||||||
} else {
|
return Failed(100, "MEMBER_NOT_FOUND", "人员不存在")
|
||||||
err := m.Kick(msg, block)
|
|
||||||
if err != nil {
|
|
||||||
return Failed(100, "NOT_MANAGEABLE", "机器人权限不足")
|
|
||||||
}
|
|
||||||
return OK(nil)
|
|
||||||
}
|
}
|
||||||
|
err := m.Kick(msg, block)
|
||||||
|
if err != nil {
|
||||||
|
return Failed(100, "NOT_MANAGEABLE", "机器人权限不足")
|
||||||
|
}
|
||||||
|
return OK(nil)
|
||||||
}
|
}
|
||||||
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
|
||||||
}
|
}
|
||||||
@ -1390,7 +1394,7 @@ func (bot *CQBot) CQGetGroupHonorInfo(groupID int64, t string) global.MSG {
|
|||||||
|
|
||||||
if t == "performer" || t == "all" {
|
if t == "performer" || t == "all" {
|
||||||
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Performer); err == nil {
|
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Performer); err == nil {
|
||||||
msg["performer_lis"] = convertMem(honor.ActorList)
|
msg["performer_list"] = convertMem(honor.ActorList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1686,9 +1690,27 @@ func (bot *CQBot) CQGetMessage(messageID int32) global.MSG {
|
|||||||
switch o := msg.(type) {
|
switch o := msg.(type) {
|
||||||
case *db.StoredGroupMessage:
|
case *db.StoredGroupMessage:
|
||||||
m["group_id"] = o.GroupCode
|
m["group_id"] = o.GroupCode
|
||||||
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourceGroup), message.Source{SourceType: message.SourceGroup, PrimaryID: o.GroupCode})
|
if o.QuotedInfo != nil {
|
||||||
|
elem := global.MSG{
|
||||||
|
"type": "reply",
|
||||||
|
"data": global.MSG{
|
||||||
|
"id": strconv.FormatInt(int64(o.QuotedInfo.PrevGlobalID), 10),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
o.Content = append(o.Content, elem)
|
||||||
|
}
|
||||||
|
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourceGroup, false), message.Source{SourceType: message.SourceGroup, PrimaryID: o.GroupCode})
|
||||||
case *db.StoredPrivateMessage:
|
case *db.StoredPrivateMessage:
|
||||||
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourcePrivate), message.Source{SourceType: message.SourcePrivate})
|
if o.QuotedInfo != nil {
|
||||||
|
elem := global.MSG{
|
||||||
|
"type": "reply",
|
||||||
|
"data": global.MSG{
|
||||||
|
"id": strconv.FormatInt(int64(o.QuotedInfo.PrevGlobalID), 10),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
o.Content = append(o.Content, elem)
|
||||||
|
}
|
||||||
|
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourcePrivate, false), message.Source{SourceType: message.SourcePrivate})
|
||||||
}
|
}
|
||||||
return OK(m)
|
return OK(m)
|
||||||
}
|
}
|
||||||
@ -1748,7 +1770,7 @@ func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
|
|||||||
"tiny_id": fU64(channelMsgByDB.Attribute.SenderTinyID),
|
"tiny_id": fU64(channelMsgByDB.Attribute.SenderTinyID),
|
||||||
"nickname": channelMsgByDB.Attribute.SenderName,
|
"nickname": channelMsgByDB.Attribute.SenderName,
|
||||||
}
|
}
|
||||||
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel), source)
|
m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel, false), source)
|
||||||
}
|
}
|
||||||
case message.SourceGuildDirect:
|
case message.SourceGuildDirect:
|
||||||
// todo(mrs4s): 支持 direct 消息
|
// todo(mrs4s): 支持 direct 消息
|
||||||
@ -1791,10 +1813,14 @@ func (bot *CQBot) CQGetGroupMessageHistory(groupID int64, seq int64) global.MSG
|
|||||||
log.Warnf("获取群历史消息失败: %v", err)
|
log.Warnf("获取群历史消息失败: %v", err)
|
||||||
return Failed(100, "MESSAGES_API_ERROR", err.Error())
|
return Failed(100, "MESSAGES_API_ERROR", err.Error())
|
||||||
}
|
}
|
||||||
|
source := message.Source{
|
||||||
|
SourceType: message.SourcePrivate,
|
||||||
|
PrimaryID: 0,
|
||||||
|
}
|
||||||
ms := make([]*event, 0, len(msg))
|
ms := make([]*event, 0, len(msg))
|
||||||
for _, m := range msg {
|
for _, m := range msg {
|
||||||
bot.checkMedia(m.Elements, groupID)
|
bot.checkMedia(m.Elements, groupID)
|
||||||
id := bot.InsertGroupMessage(m)
|
id := bot.InsertGroupMessage(m, source)
|
||||||
t := bot.formatGroupMessage(m)
|
t := bot.formatGroupMessage(m)
|
||||||
t.Others["message_id"] = id
|
t.Others["message_id"] = id
|
||||||
ms = append(ms, t)
|
ms = append(ms, t)
|
||||||
@ -2014,7 +2040,7 @@ func (bot *CQBot) CQGetVersionInfo() global.MSG {
|
|||||||
"protocol_version": "v11",
|
"protocol_version": "v11",
|
||||||
"coolq_directory": wd,
|
"coolq_directory": wd,
|
||||||
"coolq_edition": "pro",
|
"coolq_edition": "pro",
|
||||||
"go-cqhttp": true,
|
"go_cqhttp": true,
|
||||||
"plugin_version": "4.15.0",
|
"plugin_version": "4.15.0",
|
||||||
"plugin_build_number": 99,
|
"plugin_build_number": 99,
|
||||||
"plugin_build_configuration": "release",
|
"plugin_build_configuration": "release",
|
||||||
|
26
coolq/bot.go
26
coolq/bot.go
@ -288,7 +288,7 @@ func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) (in
|
|||||||
log.Warnf("警告: 群 %v 富文本消息发送失败: %v", groupID, err)
|
log.Warnf("警告: 群 %v 富文本消息发送失败: %v", groupID, err)
|
||||||
return -1, errors.Wrap(err, "send group music share error")
|
return -1, errors.Wrap(err, "send group music share error")
|
||||||
}
|
}
|
||||||
return bot.InsertGroupMessage(ret), nil
|
return bot.InsertGroupMessage(ret, source), nil
|
||||||
case *message.AtElement:
|
case *message.AtElement:
|
||||||
if i.Target == 0 && group.SelfPermission() == client.Member {
|
if i.Target == 0 && group.SelfPermission() == client.Member {
|
||||||
e = message.NewText("@全体成员")
|
e = message.NewText("@全体成员")
|
||||||
@ -307,7 +307,7 @@ func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) (in
|
|||||||
log.Warnf("群消息发送失败: 账号可能被风控.")
|
log.Warnf("群消息发送失败: 账号可能被风控.")
|
||||||
return -1, errors.New("send group message failed: blocked by server")
|
return -1, errors.New("send group message failed: blocked by server")
|
||||||
}
|
}
|
||||||
return bot.InsertGroupMessage(ret), nil
|
return bot.InsertGroupMessage(ret, source), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendPrivateMessage 发送私聊消息
|
// SendPrivateMessage 发送私聊消息
|
||||||
@ -357,7 +357,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
|
|||||||
case bot.Client.FindFriend(target) != nil: // 双向好友
|
case bot.Client.FindFriend(target) != nil: // 双向好友
|
||||||
msg := bot.Client.SendPrivateMessage(target, m)
|
msg := bot.Client.SendPrivateMessage(target, m)
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
id = bot.InsertPrivateMessage(msg)
|
id = bot.InsertPrivateMessage(msg, source)
|
||||||
}
|
}
|
||||||
case ok || groupID != 0: // 临时会话
|
case ok || groupID != 0: // 临时会话
|
||||||
if !base.AllowTempSession {
|
if !base.AllowTempSession {
|
||||||
@ -395,7 +395,7 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen
|
|||||||
case unidirectionalFriendExists(): // 单向好友
|
case unidirectionalFriendExists(): // 单向好友
|
||||||
msg := bot.Client.SendPrivateMessage(target, m)
|
msg := bot.Client.SendPrivateMessage(target, m)
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
id = bot.InsertPrivateMessage(msg)
|
id = bot.InsertPrivateMessage(msg, source)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
nickname := "Unknown"
|
nickname := "Unknown"
|
||||||
@ -444,7 +444,7 @@ func (bot *CQBot) SendGuildChannelMessage(guildID, channelID uint64, m *message.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InsertGroupMessage 群聊消息入数据库
|
// InsertGroupMessage 群聊消息入数据库
|
||||||
func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
|
func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage, source message.Source) int32 {
|
||||||
t := &message.SendingMessage{Elements: m.Elements}
|
t := &message.SendingMessage{Elements: m.Elements}
|
||||||
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
|
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
|
||||||
_, ok := e.(*message.ReplyElement)
|
_, ok := e.(*message.ReplyElement)
|
||||||
@ -468,7 +468,7 @@ func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}(),
|
}(),
|
||||||
Content: ToMessageContent(m.Elements),
|
Content: ToMessageContent(m.Elements, source),
|
||||||
}
|
}
|
||||||
if replyElem != nil {
|
if replyElem != nil {
|
||||||
reply := replyElem.(*message.ReplyElement)
|
reply := replyElem.(*message.ReplyElement)
|
||||||
@ -476,7 +476,7 @@ func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
|
|||||||
msg.QuotedInfo = &db.QuotedInfo{
|
msg.QuotedInfo = &db.QuotedInfo{
|
||||||
PrevID: encodeMessageID(m.GroupCode, reply.ReplySeq),
|
PrevID: encodeMessageID(m.GroupCode, reply.ReplySeq),
|
||||||
PrevGlobalID: db.ToGlobalID(m.GroupCode, reply.ReplySeq),
|
PrevGlobalID: db.ToGlobalID(m.GroupCode, reply.ReplySeq),
|
||||||
QuotedContent: ToMessageContent(reply.Elements),
|
QuotedContent: ToMessageContent(reply.Elements, source),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := db.InsertGroupMessage(msg); err != nil {
|
if err := db.InsertGroupMessage(msg); err != nil {
|
||||||
@ -487,7 +487,7 @@ func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InsertPrivateMessage 私聊消息入数据库
|
// InsertPrivateMessage 私聊消息入数据库
|
||||||
func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 {
|
func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage, source message.Source) int32 {
|
||||||
t := &message.SendingMessage{Elements: m.Elements}
|
t := &message.SendingMessage{Elements: m.Elements}
|
||||||
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
|
replyElem := t.FirstOrNil(func(e message.IMessageElement) bool {
|
||||||
_, ok := e.(*message.ReplyElement)
|
_, ok := e.(*message.ReplyElement)
|
||||||
@ -511,7 +511,7 @@ func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 {
|
|||||||
return m.Sender.Uin
|
return m.Sender.Uin
|
||||||
}(),
|
}(),
|
||||||
TargetUin: m.Target,
|
TargetUin: m.Target,
|
||||||
Content: ToMessageContent(m.Elements),
|
Content: ToMessageContent(m.Elements, source),
|
||||||
}
|
}
|
||||||
if replyElem != nil {
|
if replyElem != nil {
|
||||||
reply := replyElem.(*message.ReplyElement)
|
reply := replyElem.(*message.ReplyElement)
|
||||||
@ -519,7 +519,7 @@ func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage) int32 {
|
|||||||
msg.QuotedInfo = &db.QuotedInfo{
|
msg.QuotedInfo = &db.QuotedInfo{
|
||||||
PrevID: encodeMessageID(reply.Sender, reply.ReplySeq),
|
PrevID: encodeMessageID(reply.Sender, reply.ReplySeq),
|
||||||
PrevGlobalID: db.ToGlobalID(reply.Sender, reply.ReplySeq),
|
PrevGlobalID: db.ToGlobalID(reply.Sender, reply.ReplySeq),
|
||||||
QuotedContent: ToMessageContent(reply.Elements),
|
QuotedContent: ToMessageContent(reply.Elements, source),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := db.InsertPrivateMessage(msg); err != nil {
|
if err := db.InsertPrivateMessage(msg); err != nil {
|
||||||
@ -562,6 +562,10 @@ func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32
|
|||||||
// InsertGuildChannelMessage 频道消息入数据库
|
// InsertGuildChannelMessage 频道消息入数据库
|
||||||
func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) string {
|
func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) string {
|
||||||
id := encodeGuildMessageID(m.GuildId, m.ChannelId, m.Id, message.SourceGuildChannel)
|
id := encodeGuildMessageID(m.GuildId, m.ChannelId, m.Id, message.SourceGuildChannel)
|
||||||
|
source := message.Source{
|
||||||
|
SourceType: message.SourceGuildChannel,
|
||||||
|
PrimaryID: int64(m.Sender.TinyId),
|
||||||
|
}
|
||||||
msg := &db.StoredGuildChannelMessage{
|
msg := &db.StoredGuildChannelMessage{
|
||||||
ID: id,
|
ID: id,
|
||||||
Attribute: &db.StoredGuildMessageAttribute{
|
Attribute: &db.StoredGuildMessageAttribute{
|
||||||
@ -573,7 +577,7 @@ func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) stri
|
|||||||
},
|
},
|
||||||
GuildID: m.GuildId,
|
GuildID: m.GuildId,
|
||||||
ChannelID: m.ChannelId,
|
ChannelID: m.ChannelId,
|
||||||
Content: ToMessageContent(m.Elements),
|
Content: ToMessageContent(m.Elements, source),
|
||||||
}
|
}
|
||||||
if err := db.InsertGuildChannelMessage(msg); err != nil {
|
if err := db.InsertGuildChannelMessage(msg); err != nil {
|
||||||
log.Warnf("记录聊天数据时出现错误: %v", err)
|
log.Warnf("记录聊天数据时出现错误: %v", err)
|
||||||
|
@ -51,7 +51,7 @@ func replyID(r *message.ReplyElement, source message.Source) int32 {
|
|||||||
}
|
}
|
||||||
// 私聊时,部分(不确定)的账号会在 ReplyElement 中带有 GroupID 字段。
|
// 私聊时,部分(不确定)的账号会在 ReplyElement 中带有 GroupID 字段。
|
||||||
// 这里需要判断是由于 “直接回复” 功能,GroupID 为触发直接回复的来源那个群。
|
// 这里需要判断是由于 “直接回复” 功能,GroupID 为触发直接回复的来源那个群。
|
||||||
if source.SourceType == message.SourcePrivate && (r.Sender == source.PrimaryID || r.GroupID == source.PrimaryID) {
|
if source.SourceType == message.SourcePrivate && (r.Sender == source.PrimaryID || r.GroupID == source.PrimaryID || r.GroupID == 0) {
|
||||||
// 私聊似乎腾讯服务器有bug?
|
// 私聊似乎腾讯服务器有bug?
|
||||||
seq = int32(uint16(seq))
|
seq = int32(uint16(seq))
|
||||||
id = r.Sender
|
id = r.Sender
|
||||||
@ -266,10 +266,15 @@ func toElements(e []message.IMessageElement, source message.Source) (r []msg.Ele
|
|||||||
// ToMessageContent 将消息转换成 Content. 忽略 Reply
|
// ToMessageContent 将消息转换成 Content. 忽略 Reply
|
||||||
// 不同于 onebot 的 Array Message, 此函数转换出来的 Content 的 data 段为实际类型
|
// 不同于 onebot 的 Array Message, 此函数转换出来的 Content 的 data 段为实际类型
|
||||||
// 方便数据库查询
|
// 方便数据库查询
|
||||||
func ToMessageContent(e []message.IMessageElement) (r []global.MSG) {
|
func ToMessageContent(e []message.IMessageElement, source message.Source) (r []global.MSG) {
|
||||||
for _, elem := range e {
|
for _, elem := range e {
|
||||||
var m global.MSG
|
var m global.MSG
|
||||||
switch o := elem.(type) {
|
switch o := elem.(type) {
|
||||||
|
case *message.ReplyElement:
|
||||||
|
m = global.MSG{
|
||||||
|
"type": "reply",
|
||||||
|
"data": global.MSG{"id": replyID(o, source)},
|
||||||
|
}
|
||||||
case *message.TextElement:
|
case *message.TextElement:
|
||||||
m = global.MSG{
|
m = global.MSG{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@ -384,7 +389,7 @@ func ToMessageContent(e []message.IMessageElement) (r []global.MSG) {
|
|||||||
// ConvertStringMessage 将消息字符串转为消息元素数组
|
// ConvertStringMessage 将消息字符串转为消息元素数组
|
||||||
func (bot *CQBot) ConvertStringMessage(spec *onebot.Spec, raw string, sourceType message.SourceType) (r []message.IMessageElement) {
|
func (bot *CQBot) ConvertStringMessage(spec *onebot.Spec, raw string, sourceType message.SourceType) (r []message.IMessageElement) {
|
||||||
elems := msg.ParseString(raw)
|
elems := msg.ParseString(raw)
|
||||||
return bot.ConvertElements(spec, elems, sourceType)
|
return bot.ConvertElements(spec, elems, sourceType, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertObjectMessage 将消息JSON对象转为消息元素数组
|
// ConvertObjectMessage 将消息JSON对象转为消息元素数组
|
||||||
@ -393,11 +398,11 @@ func (bot *CQBot) ConvertObjectMessage(spec *onebot.Spec, m gjson.Result, source
|
|||||||
return bot.ConvertStringMessage(spec, m.Str, sourceType)
|
return bot.ConvertStringMessage(spec, m.Str, sourceType)
|
||||||
}
|
}
|
||||||
elems := msg.ParseObject(m)
|
elems := msg.ParseObject(m)
|
||||||
return bot.ConvertElements(spec, elems, sourceType)
|
return bot.ConvertElements(spec, elems, sourceType, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertContentMessage 将数据库用的 content 转换为消息元素数组
|
// ConvertContentMessage 将数据库用的 content 转换为消息元素数组
|
||||||
func (bot *CQBot) ConvertContentMessage(content []global.MSG, sourceType message.SourceType) (r []message.IMessageElement) {
|
func (bot *CQBot) ConvertContentMessage(content []global.MSG, sourceType message.SourceType, noReply bool) (r []message.IMessageElement) {
|
||||||
elems := make([]msg.Element, len(content))
|
elems := make([]msg.Element, len(content))
|
||||||
for i, v := range content {
|
for i, v := range content {
|
||||||
elem := msg.Element{Type: v["type"].(string)}
|
elem := msg.Element{Type: v["type"].(string)}
|
||||||
@ -407,13 +412,16 @@ func (bot *CQBot) ConvertContentMessage(content []global.MSG, sourceType message
|
|||||||
}
|
}
|
||||||
elems[i] = elem
|
elems[i] = elem
|
||||||
}
|
}
|
||||||
return bot.ConvertElements(onebot.V11, elems, sourceType)
|
return bot.ConvertElements(onebot.V11, elems, sourceType, noReply)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertElements 将解码后的消息数组转换为MiraiGo表示
|
// ConvertElements 将解码后的消息数组转换为MiraiGo表示
|
||||||
func (bot *CQBot) ConvertElements(spec *onebot.Spec, elems []msg.Element, sourceType message.SourceType) (r []message.IMessageElement) {
|
func (bot *CQBot) ConvertElements(spec *onebot.Spec, elems []msg.Element, sourceType message.SourceType, noReply bool) (r []message.IMessageElement) {
|
||||||
var replyCount int
|
var replyCount int
|
||||||
for _, elem := range elems {
|
for _, elem := range elems {
|
||||||
|
if noReply && elem.Type == "reply" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
me, err := bot.ConvertElement(spec, elem, sourceType)
|
me, err := bot.ConvertElement(spec, elem, sourceType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: don't use cqcode format
|
// TODO: don't use cqcode format
|
||||||
@ -497,7 +505,7 @@ func (bot *CQBot) reply(spec *onebot.Spec, elem msg.Element, sourceType message.
|
|||||||
ReplySeq: org.GetAttribute().MessageSeq,
|
ReplySeq: org.GetAttribute().MessageSeq,
|
||||||
Sender: org.GetAttribute().SenderUin,
|
Sender: org.GetAttribute().SenderUin,
|
||||||
Time: int32(org.GetAttribute().Timestamp),
|
Time: int32(org.GetAttribute().Timestamp),
|
||||||
Elements: bot.ConvertContentMessage(org.GetContent(), sourceType),
|
Elements: bot.ConvertContentMessage(org.GetContent(), sourceType, true),
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -79,7 +79,7 @@ func (bot *CQBot) privateMessageEvent(_ *client.QQClient, m *message.PrivateMess
|
|||||||
PrimaryID: m.Sender.Uin,
|
PrimaryID: m.Sender.Uin,
|
||||||
}
|
}
|
||||||
cqm := toStringMessage(m.Elements, source)
|
cqm := toStringMessage(m.Elements, source)
|
||||||
id := bot.InsertPrivateMessage(m)
|
id := bot.InsertPrivateMessage(m, source)
|
||||||
log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
|
log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
|
||||||
typ := "message/private/friend"
|
typ := "message/private/friend"
|
||||||
if m.Sender.Uin == bot.Client.Uin {
|
if m.Sender.Uin == bot.Client.Uin {
|
||||||
@ -126,7 +126,7 @@ func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage)
|
|||||||
PrimaryID: m.GroupCode,
|
PrimaryID: m.GroupCode,
|
||||||
}
|
}
|
||||||
cqm := toStringMessage(m.Elements, source)
|
cqm := toStringMessage(m.Elements, source)
|
||||||
id := bot.InsertGroupMessage(m)
|
id := bot.InsertGroupMessage(m, source)
|
||||||
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
|
log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
|
||||||
gm := bot.formatGroupMessage(m)
|
gm := bot.formatGroupMessage(m)
|
||||||
if gm == nil {
|
if gm == nil {
|
||||||
|
2
go.mod
2
go.mod
@ -5,7 +5,7 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/FloatTech/sqlite v1.5.7
|
github.com/FloatTech/sqlite v1.5.7
|
||||||
github.com/Microsoft/go-winio v0.6.0
|
github.com/Microsoft/go-winio v0.6.0
|
||||||
github.com/Mrs4s/MiraiGo v0.0.0-20230627090859-19e3d172596e
|
github.com/Mrs4s/MiraiGo v0.0.0-20230730133947-d344e0f318ab
|
||||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
|
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
|
||||||
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc
|
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc
|
||||||
github.com/fumiama/go-base16384 v1.6.1
|
github.com/fumiama/go-base16384 v1.6.1
|
||||||
|
4
go.sum
4
go.sum
@ -4,8 +4,8 @@ github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJG
|
|||||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
|
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
|
||||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||||
github.com/Mrs4s/MiraiGo v0.0.0-20230627090859-19e3d172596e h1:99itMjI//+KaFF0+0QCBg/uHhGMJ99jG2lP6z/UnOsU=
|
github.com/Mrs4s/MiraiGo v0.0.0-20230730133947-d344e0f318ab h1:SLciJTlC5YiG3qqvGJf4sHJDHDXUdH+v4rjqVhE5SIQ=
|
||||||
github.com/Mrs4s/MiraiGo v0.0.0-20230627090859-19e3d172596e/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
|
github.com/Mrs4s/MiraiGo v0.0.0-20230730133947-d344e0f318ab/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
|
||||||
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d h1:/Xuj3fIiMY2ls1TwvPKmaqQrtJsPY+c9s+0lOScVHd8=
|
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d h1:/Xuj3fIiMY2ls1TwvPKmaqQrtJsPY+c9s+0lOScVHd8=
|
||||||
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
|
github.com/RomiChan/protobuf v0.1.1-0.20230204044148-2ed269a2e54d/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA=
|
||||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
|
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
|
||||||
|
@ -39,7 +39,11 @@ var (
|
|||||||
AllowTempSession bool // 是否允许发送临时会话信息
|
AllowTempSession bool // 是否允许发送临时会话信息
|
||||||
UpdateProtocol bool // 是否更新协议
|
UpdateProtocol bool // 是否更新协议
|
||||||
SignServer string // 使用特定的服务器进行签名
|
SignServer string // 使用特定的服务器进行签名
|
||||||
HTTPTimeout int
|
SignServerBearer string // 认证签名服务器的 Bearer Token
|
||||||
|
Key string // 签名服务器密钥
|
||||||
|
IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下
|
||||||
|
HTTPTimeout int // download 超时时间
|
||||||
|
SignServerTimeout int // 签名服务器超时时间
|
||||||
|
|
||||||
PostFormat string // 上报格式 string or array
|
PostFormat string // 上报格式 string or array
|
||||||
Proxy string // 存储 proxy_rewrite,用于设置代理
|
Proxy string // 存储 proxy_rewrite,用于设置代理
|
||||||
@ -89,7 +93,11 @@ func Init() {
|
|||||||
UseSSOAddress = conf.Account.UseSSOAddress
|
UseSSOAddress = conf.Account.UseSSOAddress
|
||||||
AllowTempSession = conf.Account.AllowTempSession
|
AllowTempSession = conf.Account.AllowTempSession
|
||||||
SignServer = conf.Account.SignServer
|
SignServer = conf.Account.SignServer
|
||||||
|
SignServerBearer = conf.Account.SignServerBearer
|
||||||
|
Key = conf.Account.Key
|
||||||
|
IsBelow110 = conf.Account.IsBelow110
|
||||||
HTTPTimeout = conf.Message.HTTPTimeout
|
HTTPTimeout = conf.Message.HTTPTimeout
|
||||||
|
SignServerTimeout = conf.Message.SignServerTimeout
|
||||||
}
|
}
|
||||||
{ // others
|
{ // others
|
||||||
Proxy = conf.Message.ProxyRewrite
|
Proxy = conf.Message.ProxyRewrite
|
||||||
|
@ -21,20 +21,7 @@ import (
|
|||||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var client = &http.Client{
|
var client = newcli(time.Second * 15)
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: func(request *http.Request) (*url.URL, error) {
|
|
||||||
if base.Proxy == "" {
|
|
||||||
return http.ProxyFromEnvironment(request)
|
|
||||||
}
|
|
||||||
return url.Parse(base.Proxy)
|
|
||||||
},
|
|
||||||
// Disable http2
|
|
||||||
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
|
|
||||||
MaxIdleConnsPerHost: 999,
|
|
||||||
},
|
|
||||||
Timeout: time.Second * 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
var clienth2 = &http.Client{
|
var clienth2 = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
@ -47,7 +34,24 @@ var clienth2 = &http.Client{
|
|||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
MaxIdleConnsPerHost: 999,
|
MaxIdleConnsPerHost: 999,
|
||||||
},
|
},
|
||||||
Timeout: time.Second * 5,
|
Timeout: time.Second * 15,
|
||||||
|
}
|
||||||
|
|
||||||
|
func newcli(t time.Duration) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: func(request *http.Request) (*url.URL, error) {
|
||||||
|
if base.Proxy == "" {
|
||||||
|
return http.ProxyFromEnvironment(request)
|
||||||
|
}
|
||||||
|
return url.Parse(base.Proxy)
|
||||||
|
},
|
||||||
|
// Disable http2
|
||||||
|
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
|
||||||
|
MaxIdleConnsPerHost: 999,
|
||||||
|
},
|
||||||
|
Timeout: t,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrOverSize 响应主体过大时返回此错误
|
// ErrOverSize 响应主体过大时返回此错误
|
||||||
@ -56,6 +60,12 @@ var ErrOverSize = errors.New("oversize")
|
|||||||
// UserAgent HTTP请求时使用的UA
|
// UserAgent HTTP请求时使用的UA
|
||||||
const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
|
const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
|
||||||
|
|
||||||
|
// WithTimeout get a download instance with timeout t
|
||||||
|
func (r Request) WithTimeout(t time.Duration) *Request {
|
||||||
|
r.custcli = newcli(t)
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
// SetTimeout set internal/download client timeout
|
// SetTimeout set internal/download client timeout
|
||||||
func SetTimeout(t time.Duration) {
|
func SetTimeout(t time.Duration) {
|
||||||
if t == 0 {
|
if t == 0 {
|
||||||
@ -67,14 +77,18 @@ func SetTimeout(t time.Duration) {
|
|||||||
|
|
||||||
// Request is a file download request
|
// Request is a file download request
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Method string
|
Method string
|
||||||
URL string
|
URL string
|
||||||
Header map[string]string
|
Header map[string]string
|
||||||
Limit int64
|
Limit int64
|
||||||
Body io.Reader
|
Body io.Reader
|
||||||
|
custcli *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Request) client() *http.Client {
|
func (r Request) client() *http.Client {
|
||||||
|
if r.custcli != nil {
|
||||||
|
return r.custcli
|
||||||
|
}
|
||||||
if strings.Contains(r.URL, "go-cqhttp.org") {
|
if strings.Contains(r.URL, "go-cqhttp.org") {
|
||||||
return clienth2
|
return clienth2
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,12 @@ type Account struct {
|
|||||||
UseSSOAddress bool `yaml:"use-sso-address"`
|
UseSSOAddress bool `yaml:"use-sso-address"`
|
||||||
AllowTempSession bool `yaml:"allow-temp-session"`
|
AllowTempSession bool `yaml:"allow-temp-session"`
|
||||||
SignServer string `yaml:"sign-server"`
|
SignServer string `yaml:"sign-server"`
|
||||||
|
SignServerBearer string `yaml:"sign-server-bearer"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
IsBelow110 bool `yaml:"is-below-110"`
|
||||||
|
AutoRegister bool `yaml:"auto-register"`
|
||||||
|
AutoRefreshToken bool `yaml:"auto-refresh-token"`
|
||||||
|
RefreshInterval int64 `yaml:"refresh-interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config 总配置文件
|
// Config 总配置文件
|
||||||
@ -58,6 +64,7 @@ type Config struct {
|
|||||||
SkipMimeScan bool `yaml:"skip-mime-scan"`
|
SkipMimeScan bool `yaml:"skip-mime-scan"`
|
||||||
ConvertWebpImage bool `yaml:"convert-webp-image"`
|
ConvertWebpImage bool `yaml:"convert-webp-image"`
|
||||||
HTTPTimeout int `yaml:"http-timeout"`
|
HTTPTimeout int `yaml:"http-timeout"`
|
||||||
|
SignServerTimeout int `yaml:"sign-server-timeout"`
|
||||||
} `yaml:"message"`
|
} `yaml:"message"`
|
||||||
|
|
||||||
Output struct {
|
Output struct {
|
||||||
|
@ -24,6 +24,25 @@ account: # 账号相关
|
|||||||
# sign-server: 'https://signserver.example.com' # 线上签名服务器
|
# sign-server: 'https://signserver.example.com' # 线上签名服务器
|
||||||
# 服务器可使用docker在本地搭建或者使用他人开放的服务
|
# 服务器可使用docker在本地搭建或者使用他人开放的服务
|
||||||
sign-server: '-'
|
sign-server: '-'
|
||||||
|
# 签名服务器认证 Bearer Token
|
||||||
|
# 使用开放的服务可能需要提供此 Token 进行认证
|
||||||
|
sign-server-bearer: '-'
|
||||||
|
# 如果签名服务器的版本在1.1.0及以下, 请将下面的参数改成true
|
||||||
|
is-below-110: false
|
||||||
|
# 签名服务器所需要的apikey, 如果签名服务器的版本在1.1.0及以下则此项无效
|
||||||
|
# 本地部署的默认为114514
|
||||||
|
key: '114514'
|
||||||
|
# 在实例可能丢失(获取到的签名为空)时是否尝试重新注册
|
||||||
|
# 为 true 时,在签名服务不可用时可能每次发消息都会尝试重新注册并签名。
|
||||||
|
# 为 false 时,将不会自动注册实例,在签名服务器重启或实例被销毁后需要重启 go-cqhttp 以获取实例
|
||||||
|
# 否则后续消息将不会正常签名。关闭此项后可以考虑开启签名服务器端 auto_register 避免需要重启
|
||||||
|
auto-register: false
|
||||||
|
# 是否在 token 过期后立即自动刷新签名 token(在需要签名时才会检测到,主要防止 token 意外丢失)
|
||||||
|
# 独立于定时刷新
|
||||||
|
auto-refresh-token: false
|
||||||
|
# 定时刷新 token 间隔时间,单位为分钟, 建议 30~40 分钟, 不可超过 60 分钟
|
||||||
|
# 目前丢失token也不会有太大影响,可设置为 0 以关闭,推荐开启
|
||||||
|
refresh-interval: 40
|
||||||
|
|
||||||
heartbeat:
|
heartbeat:
|
||||||
# 心跳频率, 单位秒
|
# 心跳频率, 单位秒
|
||||||
@ -54,8 +73,10 @@ message:
|
|||||||
skip-mime-scan: false
|
skip-mime-scan: false
|
||||||
# 是否自动转换 WebP 图片
|
# 是否自动转换 WebP 图片
|
||||||
convert-webp-image: false
|
convert-webp-image: false
|
||||||
# http超时时间
|
# download 超时时间(s)
|
||||||
http-timeout: 0
|
http-timeout: 15
|
||||||
|
# 签名服务超时时间(s)
|
||||||
|
sign-server-timeout: 60
|
||||||
|
|
||||||
output:
|
output:
|
||||||
# 日志等级 trace,debug,info,warn,error
|
# 日志等级 trace,debug,info,warn,error
|
||||||
|
Loading…
x
Reference in New Issue
Block a user