mirror of
https://github.com/Mrs4s/go-cqhttp.git
synced 2025-05-04 19:17:37 +08:00
修复TCP缓冲区不足问题;重构 qsign 签名服务对接部分;支持配置多个签名服务器 (#2389)
* fix: skip callback error * update: update comment * change the logic of callback and auto-register * add token update prompt. * fix log buffer string * fix #2368 增加对 client 的利用,避免创建过多 clients * refactor: wrap sign request * feat: impl additional sign servers configuration * fix error in using configurations. * fix lint error * 支持切换回主签名服务器 * feat: support different key and auth * optimize: find avaliable sign-server * fix: register instance after server is changed * fix lint error * update: add config 'sync-check-servers' * update: first check master sign-server, or wait 3s * add checking log & optimize wait for checking done * fix wrong judge * add config: rule for changing sign server * optimize registration logic after changing server * add some log * fix #2390 * resolve requested changes in #2389 * update dependency * fix lint error 'idx is unused' * refactor: extract sync check and async check logic * delete async check sign-server
This commit is contained in:
parent
d85d697fc2
commit
f8354ec082
@ -3,18 +3,11 @@ package gocq
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/png"
|
"image/png"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/client"
|
"github.com/Mrs4s/MiraiGo/client"
|
||||||
@ -22,11 +15,9 @@ import (
|
|||||||
"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"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -269,297 +260,3 @@ func fetchCaptcha(id string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) {
|
|
||||||
signServer := base.SignServer
|
|
||||||
if !strings.HasSuffix(signServer, "/") {
|
|
||||||
signServer += "/"
|
|
||||||
}
|
|
||||||
headers := make(map[string]string)
|
|
||||||
signServerBearer := base.SignServerBearer
|
|
||||||
if signServerBearer != "-" && signServerBearer != "" {
|
|
||||||
headers["Authorization"] = "Bearer " + signServerBearer
|
|
||||||
}
|
|
||||||
req := download.Request{
|
|
||||||
Method: http.MethodGet,
|
|
||||||
Header: headers,
|
|
||||||
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 {
|
|
||||||
log.Warnf("获取T544 sign时出现错误: %v. server: %v", err, signServer)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data, err := hex.DecodeString(gjson.GetBytes(response, "data").String())
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("获取T544 sign时出现错误: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(data) == 0 {
|
|
||||||
log.Warnf("获取T544 sign时出现错误: %v.", "data is empty")
|
|
||||||
return nil, errors.New("data is empty")
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// signSubmit 提交的操作类型
|
|
||||||
func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t string) {
|
|
||||||
signServer := base.SignServer
|
|
||||||
if !strings.HasSuffix(signServer, "/") {
|
|
||||||
signServer += "/"
|
|
||||||
}
|
|
||||||
buffStr := hex.EncodeToString(buffer)
|
|
||||||
tail := 64
|
|
||||||
endl := "..."
|
|
||||||
if len(buffStr) < tail {
|
|
||||||
tail = len(buffStr)
|
|
||||||
endl = "."
|
|
||||||
}
|
|
||||||
log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffer[:tail], endl)
|
|
||||||
_, 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{
|
|
||||||
Method: http.MethodPost,
|
|
||||||
URL: signServer + "sign",
|
|
||||||
Header: headers,
|
|
||||||
Body: bytes.NewReader([]byte(fmt.Sprintf("uin=%v&qua=%s&cmd=%s&seq=%v&buffer=%v&android_id=%v&guid=%v",
|
|
||||||
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 {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
sign, _ = hex.DecodeString(gjson.GetBytes(response, "data.sign").String())
|
|
||||||
extra, _ = hex.DecodeString(gjson.GetBytes(response, "data.extra").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
|
|
||||||
}
|
|
||||||
|
|
||||||
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?uin=" + uin,
|
|
||||||
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
msg := gjson.GetBytes(resp, "msg")
|
|
||||||
code := gjson.GetBytes(resp, "code")
|
|
||||||
if code.Int() != 0 {
|
|
||||||
return errors.New("code=" + code.String() + ", msg: " + 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)
|
|
||||||
qqstr := strconv.FormatInt(base.Account.Uin, 10)
|
|
||||||
defer t.Stop()
|
|
||||||
for range t.C {
|
|
||||||
err := signRefreshToken(qqstr)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
host := u.Hostname()
|
|
||||||
port := u.Port()
|
|
||||||
if port == "" {
|
|
||||||
switch u.Scheme {
|
|
||||||
case "https":
|
|
||||||
port = "443"
|
|
||||||
case "http":
|
|
||||||
port = "80"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hostPort := net.JoinHostPort(host, port)
|
|
||||||
r := utils.RunTCPPingLoop(hostPort, 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
|
|
||||||
}
|
|
||||||
|
@ -163,32 +163,28 @@ func LoginInteract() {
|
|||||||
log.Fatalf("加载设备信息失败: %v", err)
|
log.Fatalf("加载设备信息失败: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
initSignServersConfig()
|
||||||
if base.SignServer != "-" && base.SignServer != "" {
|
signServer, err := getAvaliableSignServer() // 获取可用签名服务器
|
||||||
log.Infof("使用服务器 %s 进行数据包签名", base.SignServer)
|
if err != nil {
|
||||||
if base.SignServerBearer != "-" && base.SignServerBearer != "" {
|
log.Warn(err)
|
||||||
log.Infof("使用 Bearer %s 认证签名服务器 %s ", base.SignServerBearer, base.SignServer)
|
|
||||||
}
|
}
|
||||||
// 等待签名服务器直到连接成功
|
if len(signServer.URL) > 1 {
|
||||||
if !signWaitServer() {
|
log.Infof("使用签名服务器:%v", signServer.URL)
|
||||||
log.Fatalf("连接签名服务器失败")
|
|
||||||
}
|
|
||||||
signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, base.Key)
|
|
||||||
go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token
|
go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token
|
||||||
wrapper.DandelionEnergy = energy
|
wrapper.DandelionEnergy = energy
|
||||||
wrapper.FekitGetSign = sign
|
wrapper.FekitGetSign = sign
|
||||||
if !base.IsBelow110 {
|
if !base.IsBelow110 {
|
||||||
if !base.Account.AutoRegister {
|
if !base.Account.AutoRegister {
|
||||||
log.Warn("自动注册实例已关闭,若未配置 sign-server 端自动注册实例则实例丢失时需要重启 go-cqhttp 以正常签名")
|
log.Warn("自动注册实例已关闭,请配置 sign-server 端自动注册实例以保持正常签名")
|
||||||
}
|
}
|
||||||
if !base.Account.AutoRefreshToken {
|
if !base.Account.AutoRefreshToken {
|
||||||
log.Warn("自动刷新 token 已关闭,token 过期后获取签名时将不会立即尝试刷新获取新 token")
|
log.Info("自动刷新 token 已关闭,token 过期后获取签名时将不会立即尝试刷新获取新 token")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn("签名服务器版本 <= 1.1.0 ,无法使用刷新 token 等操作,建议使用 1.1.6 版本及以上签名服务器")
|
log.Warn("签名服务器版本 <= 1.1.0 ,无法使用刷新 token 等操作,建议使用 1.1.6 版本及以上签名服务器")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("警告: 未配置签名服务器, 这可能会导致登录 45 错误码或发送消息被风控")
|
log.Warnf("警告: 未配置签名服务器或签名服务器不可用, 这可能会导致登录 45 错误码或发送消息被风控")
|
||||||
}
|
}
|
||||||
|
|
||||||
if base.Account.Encrypt {
|
if base.Account.Encrypt {
|
||||||
|
409
cmd/gocq/qsign.go
Normal file
409
cmd/gocq/qsign.go
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
package gocq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
|
"github.com/Mrs4s/MiraiGo/utils"
|
||||||
|
|
||||||
|
"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/modules/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type currentSignServer struct {
|
||||||
|
server *config.SignServer
|
||||||
|
ok bool
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *currentSignServer) getServer() *config.SignServer {
|
||||||
|
c.lock.RLock()
|
||||||
|
defer c.lock.RUnlock()
|
||||||
|
return c.server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *currentSignServer) isOK() bool {
|
||||||
|
c.lock.RLock()
|
||||||
|
defer c.lock.RUnlock()
|
||||||
|
return c.ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *currentSignServer) setServer(server *config.SignServer, status bool) {
|
||||||
|
c.lock.Lock()
|
||||||
|
defer c.lock.Unlock()
|
||||||
|
if server != nil {
|
||||||
|
c.server = server
|
||||||
|
}
|
||||||
|
c.ok = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前签名服务器
|
||||||
|
var curSignServer *currentSignServer
|
||||||
|
var errorCount = uintptr(0)
|
||||||
|
var checkLock sync.Mutex
|
||||||
|
|
||||||
|
func initSignServersConfig() {
|
||||||
|
if len(base.SignServers) == 0 {
|
||||||
|
log.Warn("no configured sign-server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(base.SignServers) > 5 {
|
||||||
|
base.SignServers = base.SignServers[:5]
|
||||||
|
log.Warn("签名服务器数量配置过多,可能导致不可用时检查时间过久,已取前 5 个")
|
||||||
|
}
|
||||||
|
curSignServer = ¤tSignServer{
|
||||||
|
server: &base.SignServers[0],
|
||||||
|
ok: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误
|
||||||
|
func getAvaliableSignServer() (config.SignServer, error) {
|
||||||
|
if curSignServer.isOK() {
|
||||||
|
return *curSignServer.getServer(), nil
|
||||||
|
}
|
||||||
|
maxCount := base.Account.MaxCheckCount
|
||||||
|
if maxCount == 0 && atomic.LoadUintptr(&errorCount) >= 3 {
|
||||||
|
log.Warn("已连续 3 次获取不到可用签名服务器,将固定使用主签名服务器")
|
||||||
|
curSignServer.setServer(&base.SignServers[0], true)
|
||||||
|
return *curSignServer.getServer(), nil
|
||||||
|
}
|
||||||
|
if maxCount > 0 && int(atomic.LoadUintptr(&errorCount)) >= maxCount {
|
||||||
|
log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount)
|
||||||
|
}
|
||||||
|
if checkLock.TryLock() {
|
||||||
|
defer checkLock.Unlock()
|
||||||
|
cs := curSignServer.getServer()
|
||||||
|
if len(cs.URL) > 1 {
|
||||||
|
log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", cs.URL)
|
||||||
|
}
|
||||||
|
return syncCheckServer(base.SignServers)
|
||||||
|
}
|
||||||
|
return config.SignServer{}, errors.New("checking sign-servers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func isServerAvaliable(signServer string) bool {
|
||||||
|
resp, err := download.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: signServer,
|
||||||
|
}.WithTimeout(3 * time.Second).Bytes()
|
||||||
|
if err == nil && gjson.GetBytes(resp, "code").Int() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
log.Warnf("签名服务器 %v 可能不可用,请求出现错误:%v", signServer, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncCheckServer 按同步顺序检查所有签名服务器直到找到可用的
|
||||||
|
func syncCheckServer(servers []config.SignServer) (config.SignServer, error) {
|
||||||
|
for i, server := range servers {
|
||||||
|
log.Infof("检查签名服务器:%v (%v/%v)", server.URL, i+1, len(servers))
|
||||||
|
if len(server.URL) < 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isServerAvaliable(server.URL) {
|
||||||
|
atomic.StoreUintptr(&errorCount, 0)
|
||||||
|
curSignServer.setServer(&server, true)
|
||||||
|
log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", server.URL, server.Key, server.Authorization)
|
||||||
|
if base.Account.AutoRegister {
|
||||||
|
// 若配置了自动注册实例则在切换后注册实例,否则不需要注册,签名时由qsign自动注册
|
||||||
|
signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, server.Key)
|
||||||
|
}
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.SignServer{}, errors.New("no avaliable sign-server")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
请求签名服务器
|
||||||
|
|
||||||
|
url: api + params 组合的字符串,无须包含签名服务器地址
|
||||||
|
return: signServer, response, error
|
||||||
|
*/
|
||||||
|
func requestSignServer(method string, url string, headers map[string]string, body io.Reader) (string, []byte, error) {
|
||||||
|
signServer, e := getAvaliableSignServer()
|
||||||
|
if e != nil && len(signServer.URL) <= 1 { // 没有可用的
|
||||||
|
log.Warnf("获取可用签名服务器出错:%v, 将使用主签名服务器进行签名", e)
|
||||||
|
atomic.AddUintptr(&errorCount, 1)
|
||||||
|
signServer = base.SignServers[0] // 没有获取到时使用第一个
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(url, signServer.URL) {
|
||||||
|
url = strings.TrimSuffix(signServer.URL, "/") + "/" + strings.TrimPrefix(url, "/")
|
||||||
|
}
|
||||||
|
if headers == nil {
|
||||||
|
headers = map[string]string{}
|
||||||
|
}
|
||||||
|
auth := signServer.Authorization
|
||||||
|
if auth != "-" && auth != "" {
|
||||||
|
headers["Authorization"] = auth
|
||||||
|
}
|
||||||
|
req := download.Request{
|
||||||
|
Method: method,
|
||||||
|
Header: headers,
|
||||||
|
URL: url,
|
||||||
|
Body: body,
|
||||||
|
}.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second)
|
||||||
|
resp, err := req.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
curSignServer.setServer(nil, false) // 标记为不可用
|
||||||
|
}
|
||||||
|
return signServer.URL, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) {
|
||||||
|
url := "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))
|
||||||
|
if base.IsBelow110 {
|
||||||
|
url = "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt))
|
||||||
|
}
|
||||||
|
signServer, response, err := requestSignServer(http.MethodGet, url, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("获取T544 sign时出现错误: %v. server: %v", err, signServer)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := hex.DecodeString(gjson.GetBytes(response, "data").String())
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("获取T544 sign时出现错误: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
log.Warnf("获取T544 sign时出现错误: %v.", "data is empty")
|
||||||
|
return nil, errors.New("data is empty")
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// signSubmit
|
||||||
|
// 提交回调 buffer
|
||||||
|
func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t string) {
|
||||||
|
buffStr := hex.EncodeToString(buffer)
|
||||||
|
tail := 64
|
||||||
|
endl := "..."
|
||||||
|
if len(buffStr) < tail {
|
||||||
|
tail = len(buffStr)
|
||||||
|
endl = "."
|
||||||
|
}
|
||||||
|
log.Infof("submit (%v): uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffStr[:tail], endl)
|
||||||
|
|
||||||
|
signServer, _, err := requestSignServer(
|
||||||
|
http.MethodGet,
|
||||||
|
"submit"+fmt.Sprintf("?uin=%v&cmd=%v&callback_id=%v&buffer=%v",
|
||||||
|
uin, cmd, callbackID, buffStr),
|
||||||
|
nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("提交 callback 时出现错误: %v. server: %v", err, signServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// signCallback
|
||||||
|
// 刷新 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 || len(ret) == 0 {
|
||||||
|
log.Warnf("Callback error: %v, Or response data is empty", err)
|
||||||
|
continue // 发送 SsoPacket 出错或返回数据为空时跳过
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
|
_, response, err := requestSignServer(
|
||||||
|
http.MethodPost,
|
||||||
|
"sign",
|
||||||
|
headers,
|
||||||
|
bytes.NewReader([]byte(fmt.Sprintf("uin=%v&qua=%s&cmd=%s&seq=%v&buffer=%v&android_id=%v&guid=%v",
|
||||||
|
uin, qua, cmd, seq, hex.EncodeToString(buff), utils.B2S(device.AndroidId), hex.EncodeToString(device.Guid)))),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
sign, _ = hex.DecodeString(gjson.GetBytes(response, "data.sign").String())
|
||||||
|
extra, _ = hex.DecodeString(gjson.GetBytes(response, "data.extra").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
|
||||||
|
}
|
||||||
|
|
||||||
|
var registerLock sync.Mutex
|
||||||
|
|
||||||
|
func signRegister(uin int64, androidID, guid []byte, qimei36, key string) {
|
||||||
|
if base.IsBelow110 {
|
||||||
|
log.Warn("签名服务器版本低于1.1.0, 跳过实例注册")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
signServer, resp, err := requestSignServer(
|
||||||
|
http.MethodGet,
|
||||||
|
"register"+fmt.Sprintf("?uin=%v&android_id=%v&guid=%v&qimei36=%v&key=%s",
|
||||||
|
uin, utils.B2S(androidID), hex.EncodeToString(guid), qimei36, key),
|
||||||
|
nil, nil,
|
||||||
|
)
|
||||||
|
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 {
|
||||||
|
log.Info("正在刷新 token")
|
||||||
|
_, resp, err := requestSignServer(
|
||||||
|
http.MethodGet,
|
||||||
|
"request_token?uin="+uin,
|
||||||
|
nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg := gjson.GetBytes(resp, "msg")
|
||||||
|
code := gjson.GetBytes(resp, "code")
|
||||||
|
if code.Int() != 0 {
|
||||||
|
return errors.New("code=" + code.String() + ", msg: " + msg.String())
|
||||||
|
}
|
||||||
|
go signCallback(uin, gjson.GetBytes(resp, "data").Array(), "request token")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var missTokenCount = uint64(0)
|
||||||
|
var lastToken = ""
|
||||||
|
|
||||||
|
func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) {
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
cs := curSignServer.getServer()
|
||||||
|
sign, extra, token, err = signRequset(seq, uin, cmd, qua, buff)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("获取sso sign时出现错误: %v. server: %v", err, cs.URL)
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if (!base.IsBelow110) && base.Account.AutoRegister && err == nil && len(sign) == 0 {
|
||||||
|
if registerLock.TryLock() { // 避免并发时多处同时销毁并重新注册
|
||||||
|
log.Debugf("请求签名:cmd=%v, qua=%v, buff=%v", seq, cmd, hex.EncodeToString(buff))
|
||||||
|
log.Debugf("返回结果:sign=%v, extra=%v, token=%v",
|
||||||
|
hex.EncodeToString(sign), hex.EncodeToString(extra), hex.EncodeToString(token))
|
||||||
|
log.Warn("获取签名为空,实例可能丢失,正在尝试重新注册")
|
||||||
|
defer registerLock.Unlock()
|
||||||
|
err := signServerDestroy(uin)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln(err) // 实例真的丢失时则必出错,或许应该不 return , 以重新获取本次签名
|
||||||
|
// return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, cs.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, cs.URL)
|
||||||
|
} else {
|
||||||
|
log.Info("刷新 token 成功")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tokenString := hex.EncodeToString(token); lastToken != tokenString {
|
||||||
|
log.Infof("token 已更新:%v -> %v", lastToken, tokenString)
|
||||||
|
lastToken = tokenString
|
||||||
|
}
|
||||||
|
rule := base.Account.RuleChangeSignServer
|
||||||
|
if (len(sign) == 0 && rule >= 1) || (len(token) == 0 && rule >= 2) {
|
||||||
|
curSignServer.setServer(nil, false)
|
||||||
|
}
|
||||||
|
return sign, extra, token, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func signServerDestroy(uin string) error {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
signServer, resp, err := requestSignServer(
|
||||||
|
http.MethodGet,
|
||||||
|
"destroy"+fmt.Sprintf("?uin=%v&key=%v", uin, curSignServer.getServer().Key),
|
||||||
|
nil, nil,
|
||||||
|
)
|
||||||
|
if err != nil || gjson.GetBytes(resp, "code").Int() != 0 {
|
||||||
|
return errors.Wrapf(err, "destroy 实例出现错误, server: %v", signServer)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func signVersion() (signServer string, version string, err error) {
|
||||||
|
signServer, resp, err := requestSignServer(http.MethodGet, "", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return signServer, "", err
|
||||||
|
}
|
||||||
|
if gjson.GetBytes(resp, "code").Int() == 0 {
|
||||||
|
return signServer, gjson.GetBytes(resp, "data.version").String(), nil
|
||||||
|
}
|
||||||
|
return signServer, "", 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)
|
||||||
|
qqstr := strconv.FormatInt(base.Account.Uin, 10)
|
||||||
|
defer t.Stop()
|
||||||
|
for range t.C {
|
||||||
|
cs, master := curSignServer.getServer(), base.SignServers[0]
|
||||||
|
if cs.URL != master.URL && isServerAvaliable(master.URL) {
|
||||||
|
curSignServer.setServer(&master, true)
|
||||||
|
log.Infof("主签名服务器可用,已切换至主签名服务器 %v", cs.URL)
|
||||||
|
}
|
||||||
|
err := signRefreshToken(qqstr)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("刷新 token 出现错误: %v. server: %v", err, cs.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
coolq/api.go
11
coolq/api.go
@ -1388,33 +1388,42 @@ func (bot *CQBot) CQGetGroupHonorInfo(groupID int64, t string) global.MSG {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg["talkative_list"] = convertMem(honor.TalkativeList)
|
msg["talkative_list"] = convertMem(honor.TalkativeList)
|
||||||
|
} else {
|
||||||
|
log.Infof("获取群龙王出错:%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_list"] = convertMem(honor.ActorList)
|
msg["performer_list"] = convertMem(honor.ActorList)
|
||||||
|
} else {
|
||||||
|
log.Infof("获取群聊之火出错:%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t == "legend" || t == "all" {
|
if t == "legend" || t == "all" {
|
||||||
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Legend); err == nil {
|
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Legend); err == nil {
|
||||||
msg["legend_list"] = convertMem(honor.LegendList)
|
msg["legend_list"] = convertMem(honor.LegendList)
|
||||||
|
} else {
|
||||||
|
log.Infof("获取群聊炽焰出错:%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t == "strong_newbie" || t == "all" {
|
if t == "strong_newbie" || t == "all" {
|
||||||
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.StrongNewbie); err == nil {
|
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.StrongNewbie); err == nil {
|
||||||
msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList)
|
msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList)
|
||||||
|
} else {
|
||||||
|
log.Infof("获取冒尖小春笋出错:%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if t == "emotion" || t == "all" {
|
if t == "emotion" || t == "all" {
|
||||||
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Emotion); err == nil {
|
if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Emotion); err == nil {
|
||||||
msg["emotion_list"] = convertMem(honor.EmotionList)
|
msg["emotion_list"] = convertMem(honor.EmotionList)
|
||||||
|
} else {
|
||||||
|
log.Infof("获取快乐之源出错:%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return OK(msg)
|
return OK(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -5,7 +5,7 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/FloatTech/sqlite v1.6.3
|
github.com/FloatTech/sqlite v1.6.3
|
||||||
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a
|
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a
|
||||||
github.com/Mrs4s/MiraiGo v0.0.0-20230801023408-b4cd7e8f2149
|
github.com/Mrs4s/MiraiGo v0.0.0-20230823050531-a8213e127b2b
|
||||||
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.20220227141055-9b2c6168c9c5
|
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
|
||||||
github.com/fumiama/go-base16384 v1.7.0
|
github.com/fumiama/go-base16384 v1.7.0
|
||||||
|
3
go.sum
3
go.sum
@ -6,6 +6,8 @@ github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a h1:aU1703IHxu
|
|||||||
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a/go.mod h1:OZqLNXdYJHmx7aqq/T6wAdFEdoGm5nmIfC4kU7M8P8o=
|
github.com/Microsoft/go-winio v0.6.2-0.20230724192519-b29bbd58a65a/go.mod h1:OZqLNXdYJHmx7aqq/T6wAdFEdoGm5nmIfC4kU7M8P8o=
|
||||||
github.com/Mrs4s/MiraiGo v0.0.0-20230801023408-b4cd7e8f2149 h1:q9w4m+ps0gTyUHLObX6avawN1Rfn0GQwbmEKCZ6WrBo=
|
github.com/Mrs4s/MiraiGo v0.0.0-20230801023408-b4cd7e8f2149 h1:q9w4m+ps0gTyUHLObX6avawN1Rfn0GQwbmEKCZ6WrBo=
|
||||||
github.com/Mrs4s/MiraiGo v0.0.0-20230801023408-b4cd7e8f2149/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
|
github.com/Mrs4s/MiraiGo v0.0.0-20230801023408-b4cd7e8f2149/go.mod h1:mU3fBFU+7eO0kaGes7YRKtzIDtwIU84nSSwTV7NK2b0=
|
||||||
|
github.com/Mrs4s/MiraiGo v0.0.0-20230823050531-a8213e127b2b h1:0GG6kDFgzie0HNdlkrgPwyX4WqUjckTP1xTM4cYaC2g=
|
||||||
|
github.com/Mrs4s/MiraiGo v0.0.0-20230823050531-a8213e127b2b/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=
|
||||||
@ -173,6 +175,7 @@ golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
|
|||||||
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.ilharper.com/x/isatty v1.1.1 h1:RAg32Pxq/nIK4AVtdm9RBqxsxZZX1uRKRSS21E5SHMk=
|
gopkg.ilharper.com/x/isatty v1.1.1 h1:RAg32Pxq/nIK4AVtdm9RBqxsxZZX1uRKRSS21E5SHMk=
|
||||||
gopkg.ilharper.com/x/isatty v1.1.1/go.mod h1:ofpv77Td5qQO6R1dmDd3oNt8TZdRo+l5gYAMxopRyS0=
|
gopkg.ilharper.com/x/isatty v1.1.1/go.mod h1:ofpv77Td5qQO6R1dmDd3oNt8TZdRo+l5gYAMxopRyS0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
@ -38,9 +38,7 @@ var (
|
|||||||
FastStart bool // 是否为快速启动
|
FastStart bool // 是否为快速启动
|
||||||
AllowTempSession bool // 是否允许发送临时会话信息
|
AllowTempSession bool // 是否允许发送临时会话信息
|
||||||
UpdateProtocol bool // 是否更新协议
|
UpdateProtocol bool // 是否更新协议
|
||||||
SignServer string // 使用特定的服务器进行签名
|
SignServers []config.SignServer // 使用特定的服务器进行签名
|
||||||
SignServerBearer string // 认证签名服务器的 Bearer Token
|
|
||||||
Key string // 签名服务器密钥
|
|
||||||
IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下
|
IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下
|
||||||
HTTPTimeout int // download 超时时间
|
HTTPTimeout int // download 超时时间
|
||||||
SignServerTimeout int // 签名服务器超时时间
|
SignServerTimeout int // 签名服务器超时时间
|
||||||
@ -92,12 +90,10 @@ func Init() {
|
|||||||
ReportSelfMessage = conf.Message.ReportSelfMessage
|
ReportSelfMessage = conf.Message.ReportSelfMessage
|
||||||
UseSSOAddress = conf.Account.UseSSOAddress
|
UseSSOAddress = conf.Account.UseSSOAddress
|
||||||
AllowTempSession = conf.Account.AllowTempSession
|
AllowTempSession = conf.Account.AllowTempSession
|
||||||
SignServer = conf.Account.SignServer
|
SignServers = conf.Account.SignServers
|
||||||
SignServerBearer = conf.Account.SignServerBearer
|
|
||||||
Key = conf.Account.Key
|
|
||||||
IsBelow110 = conf.Account.IsBelow110
|
IsBelow110 = conf.Account.IsBelow110
|
||||||
HTTPTimeout = conf.Message.HTTPTimeout
|
HTTPTimeout = conf.Message.HTTPTimeout
|
||||||
SignServerTimeout = conf.Message.SignServerTimeout
|
SignServerTimeout = conf.Account.SignServerTimeout
|
||||||
}
|
}
|
||||||
{ // others
|
{ // others
|
||||||
Proxy = conf.Message.ProxyRewrite
|
Proxy = conf.Message.ProxyRewrite
|
||||||
|
@ -15,13 +15,15 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/RomiChan/syncx"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
|
||||||
"github.com/Mrs4s/go-cqhttp/internal/base"
|
"github.com/Mrs4s/go-cqhttp/internal/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var client = newcli(time.Second * 15)
|
var client = newClient(time.Second * 15)
|
||||||
|
var clients syncx.Map[time.Duration, *http.Client]
|
||||||
|
|
||||||
var clienth2 = &http.Client{
|
var clienth2 = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
@ -37,7 +39,7 @@ var clienth2 = &http.Client{
|
|||||||
Timeout: time.Second * 15,
|
Timeout: time.Second * 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
func newcli(t time.Duration) *http.Client {
|
func newClient(t time.Duration) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: func(request *http.Request) (*url.URL, error) {
|
Proxy: func(request *http.Request) (*url.URL, error) {
|
||||||
@ -62,7 +64,13 @@ const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
|
|||||||
|
|
||||||
// WithTimeout get a download instance with timeout t
|
// WithTimeout get a download instance with timeout t
|
||||||
func (r Request) WithTimeout(t time.Duration) *Request {
|
func (r Request) WithTimeout(t time.Duration) *Request {
|
||||||
r.custcli = newcli(t)
|
if c, ok := clients.Load(t); ok {
|
||||||
|
r.custcli = c
|
||||||
|
} else {
|
||||||
|
c := newClient(t)
|
||||||
|
clients.Store(t, c)
|
||||||
|
r.custcli = c
|
||||||
|
}
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,15 +35,23 @@ type Account struct {
|
|||||||
ReLogin *Reconnect `yaml:"relogin"`
|
ReLogin *Reconnect `yaml:"relogin"`
|
||||||
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"`
|
SignServers []SignServer `yaml:"sign-servers"`
|
||||||
SignServerBearer string `yaml:"sign-server-bearer"`
|
RuleChangeSignServer int `yaml:"rule-change-sign-server"`
|
||||||
Key string `yaml:"key"`
|
MaxCheckCount int `yaml:"max-check-count"`
|
||||||
|
SignServerTimeout int `yaml:"sign-server-timeout"`
|
||||||
IsBelow110 bool `yaml:"is-below-110"`
|
IsBelow110 bool `yaml:"is-below-110"`
|
||||||
AutoRegister bool `yaml:"auto-register"`
|
AutoRegister bool `yaml:"auto-register"`
|
||||||
AutoRefreshToken bool `yaml:"auto-refresh-token"`
|
AutoRefreshToken bool `yaml:"auto-refresh-token"`
|
||||||
RefreshInterval int64 `yaml:"refresh-interval"`
|
RefreshInterval int64 `yaml:"refresh-interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignServer 签名服务器
|
||||||
|
type SignServer struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
Authorization string `yaml:"authorization"`
|
||||||
|
}
|
||||||
|
|
||||||
// Config 总配置文件
|
// Config 总配置文件
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Account *Account `yaml:"account"`
|
Account *Account `yaml:"account"`
|
||||||
@ -64,7 +72,6 @@ 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 {
|
||||||
|
@ -16,26 +16,49 @@ account: # 账号相关
|
|||||||
# 是否允许发送临时会话消息
|
# 是否允许发送临时会话消息
|
||||||
allow-temp-session: false
|
allow-temp-session: false
|
||||||
|
|
||||||
# 数据包的签名服务器
|
# 数据包的签名服务器列表,第一个作为主签名服务器,后续作为备用
|
||||||
# 兼容 https://github.com/fuqiuluo/unidbg-fetch-qsign
|
# 兼容 https://github.com/fuqiuluo/unidbg-fetch-qsign
|
||||||
# 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个服务器
|
# 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个或多个服务器
|
||||||
|
# 不建议设置过多,设置主备各一个即可,超过 5 个只会取前五个
|
||||||
# 示例:
|
# 示例:
|
||||||
# sign-server: 'http://127.0.0.1:8080' # 本地签名服务器
|
# sign-servers:
|
||||||
# sign-server: 'https://signserver.example.com' # 线上签名服务器
|
# - url: 'http://127.0.0.1:8080' # 本地签名服务器
|
||||||
|
# key: "114514" # 相应 key
|
||||||
|
# authorization: "-" # authorization 内容, 依服务端设置
|
||||||
|
# - url: 'https://signserver.example.com' # 线上签名服务器
|
||||||
|
# key: "114514"
|
||||||
|
# authorization: "-"
|
||||||
|
# ...
|
||||||
|
#
|
||||||
# 服务器可使用docker在本地搭建或者使用他人开放的服务
|
# 服务器可使用docker在本地搭建或者使用他人开放的服务
|
||||||
sign-server: '-'
|
sign-servers:
|
||||||
# 签名服务器认证 Bearer Token
|
- url: '-' # 主签名服务器地址, 必填
|
||||||
# 使用开放的服务可能需要提供此 Token 进行认证
|
key: '114514' # 签名服务器所需要的apikey, 如果签名服务器的版本在1.1.0及以下则此项无效
|
||||||
sign-server-bearer: '-'
|
authorization: '-' # authorization 内容, 依服务端设置,如 'Bearer xxxx'
|
||||||
# 如果签名服务器的版本在1.1.0及以下, 请将下面的参数改成true
|
- url: '-' # 备用
|
||||||
is-below-110: false
|
|
||||||
# 签名服务器所需要的apikey, 如果签名服务器的版本在1.1.0及以下则此项无效
|
|
||||||
# 本地部署的默认为114514
|
|
||||||
key: '114514'
|
key: '114514'
|
||||||
|
authorization: '-'
|
||||||
|
|
||||||
|
# 判断签名服务不可用(需要切换)的额外规则
|
||||||
|
# 0: 不设置 (此时仅在请求无法返回结果时判定为不可用)
|
||||||
|
# 1: 在获取到的 sign 为空 (若选此建议关闭 auto-register,一般为实例未注册但是请求签名的情况)
|
||||||
|
# 2: 在获取到的 sign 或 token 为空(若选此建议关闭 auto-refresh-token )
|
||||||
|
rule-change-sign-server: 1
|
||||||
|
|
||||||
|
# 连续寻找可用签名服务器最大尝试次数
|
||||||
|
# 为 0 时会在连续 3 次没有找到可用签名服务器后保持使用主签名服务器,不再尝试进行切换备用
|
||||||
|
# 否则会在达到指定次数后 **退出** 主程序
|
||||||
|
max-check-count: 0
|
||||||
|
# 签名服务请求超时时间(s)
|
||||||
|
sign-server-timeout: 60
|
||||||
|
# 如果签名服务器的版本在1.1.0及以下, 请将下面的参数改成true
|
||||||
|
# 建议使用 1.1.6 以上版本,低版本普遍半个月冻结一次
|
||||||
|
is-below-110: false
|
||||||
# 在实例可能丢失(获取到的签名为空)时是否尝试重新注册
|
# 在实例可能丢失(获取到的签名为空)时是否尝试重新注册
|
||||||
# 为 true 时,在签名服务不可用时可能每次发消息都会尝试重新注册并签名。
|
# 为 true 时,在签名服务不可用时可能每次发消息都会尝试重新注册并签名。
|
||||||
# 为 false 时,将不会自动注册实例,在签名服务器重启或实例被销毁后需要重启 go-cqhttp 以获取实例
|
# 为 false 时,将不会自动注册实例,在签名服务器重启或实例被销毁后需要重启 go-cqhttp 以获取实例
|
||||||
# 否则后续消息将不会正常签名。关闭此项后可以考虑开启签名服务器端 auto_register 避免需要重启
|
# 否则后续消息将不会正常签名。关闭此项后可以考虑开启签名服务器端 auto_register 避免需要重启
|
||||||
|
# 由于实现问题,当前建议关闭此项,推荐开启签名服务器的自动注册实例
|
||||||
auto-register: false
|
auto-register: false
|
||||||
# 是否在 token 过期后立即自动刷新签名 token(在需要签名时才会检测到,主要防止 token 意外丢失)
|
# 是否在 token 过期后立即自动刷新签名 token(在需要签名时才会检测到,主要防止 token 意外丢失)
|
||||||
# 独立于定时刷新
|
# 独立于定时刷新
|
||||||
@ -75,8 +98,6 @@ message:
|
|||||||
convert-webp-image: false
|
convert-webp-image: false
|
||||||
# download 超时时间(s)
|
# download 超时时间(s)
|
||||||
http-timeout: 15
|
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