From e0f3e417396426096e5f573f1a35552dd8e948cc Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Tue, 1 Aug 2023 16:43:10 +0800 Subject: [PATCH 01/28] fix: skip callback error --- cmd/gocq/login.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 0bc2d7a0b..a233f6600 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -312,8 +312,13 @@ func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t strin 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:]) + if len(buffStr) > 10 { + log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer-end=%v", t, uin, cmd, callbackID, + buffStr[len(buffStr)-10:]) + } else { + log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v", t, uin, cmd, callbackID, buffStr) + } + _, err := download.Request{ Method: http.MethodGet, URL: signServer + "submit" + fmt.Sprintf("?uin=%v&cmd=%v&callback_id=%v&buffer=%v", @@ -333,6 +338,7 @@ func signCallback(uin string, results []gjson.Result, t string) { ret, err := cli.SendSsoPacket(cmd, body) if err != nil { log.Warnf("callback error: %v", err) + continue } signSubmit(uin, cmd, callbackID, ret, t) } From 9acbb48be85bf9dcd68f2961b7ef97d1af74b068 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 3 Aug 2023 15:29:29 +0800 Subject: [PATCH 02/28] update: update comment --- modules/config/default_config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index 5acb845f4..c997e075e 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -36,6 +36,7 @@ account: # 账号相关 # 为 true 时,在签名服务不可用时可能每次发消息都会尝试重新注册并签名。 # 为 false 时,将不会自动注册实例,在签名服务器重启或实例被销毁后需要重启 go-cqhttp 以获取实例 # 否则后续消息将不会正常签名。关闭此项后可以考虑开启签名服务器端 auto_register 避免需要重启 + # 由于实现问题,当前建议关闭此项,推荐开启签名服务器的自动注册实例 auto-register: false # 是否在 token 过期后立即自动刷新签名 token(在需要签名时才会检测到,主要防止 token 意外丢失) # 独立于定时刷新 From cd4b47aefa1faf2fd52250d9bef9a44d97473c6f Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 3 Aug 2023 16:51:46 +0800 Subject: [PATCH 03/28] change the logic of callback and auto-register --- cmd/gocq/login.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index dbab1c955..7bc911055 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -305,7 +305,8 @@ func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) { return data, nil } -// signSubmit 提交的操作类型 +// signSubmit +// 提交回调 buffer func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t string) { signServer := base.SignServer if !strings.HasSuffix(signServer, "/") { @@ -313,10 +314,10 @@ func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t strin } buffStr := hex.EncodeToString(buffer) if len(buffStr) > 10 { - log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer-end=%v", t, uin, cmd, callbackID, + log.Infof("submit (%v): uin=%v, cmd=%v, callbackID=%v, buffer-end=%v", t, uin, cmd, callbackID, buffStr[len(buffStr)-10:]) } else { - log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v", t, uin, cmd, callbackID, buffStr) + log.Infof("submit (%v): uin=%v, cmd=%v, callbackID=%v, buffer=%v", t, uin, cmd, callbackID, buffStr) } _, err := download.Request{ @@ -329,20 +330,19 @@ func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t strin } } -// signCallback request token 和签名的回调 +// 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 { - log.Warnf("callback error: %v", err) - continue - } - if len(ret) > 10 { - signSubmit(uin, cmd, callbackID, ret, t) + 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) } } @@ -444,8 +444,8 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b defer registerLock.Unlock() err := signServerDestroy(uin) if err != nil { - log.Warnln(err) - return nil, nil, nil, err + log.Warnln(err) // 实例真的丢失时则必出错,或许应该不 return , 以重新获取本次签名 + // return nil, nil, nil, err } signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, base.Key) } From 97efc31c4ec4ace49f7d5042c3c34b4308ec3cb8 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 3 Aug 2023 17:09:34 +0800 Subject: [PATCH 04/28] add token update prompt. --- cmd/gocq/login.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 7bc911055..4394bbd83 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -426,6 +426,7 @@ func signRefreshToken(uin string) error { } 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 @@ -465,6 +466,10 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b } break } + if tokenString := hex.EncodeToString(token); lastToken != tokenString { + log.Infof("token 已更新:%v -> %v", lastToken, tokenString) + lastToken = tokenString + } return sign, extra, token, err } From 4b3232af282b9ba05d765ae58ac5efec2a2e3a9c Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Sun, 6 Aug 2023 17:18:28 +0800 Subject: [PATCH 05/28] fix log buffer string --- cmd/gocq/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 7dedd2187..d3067b085 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -319,7 +319,7 @@ func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t strin tail = len(buffStr) endl = "." } - log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffer[:tail], endl) + log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffStr[:tail], endl) _, err := download.Request{ Method: http.MethodGet, From fd71785989cea3aa2d1b33349cb735f0da0dfa2f Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Sat, 12 Aug 2023 20:47:01 +0800 Subject: [PATCH 06/28] fix #2368 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加对 client 的利用,避免创建过多 clients --- internal/download/download.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/download/download.go b/internal/download/download.go index 786658f32..2e94ccb80 100644 --- a/internal/download/download.go +++ b/internal/download/download.go @@ -21,7 +21,8 @@ import ( "github.com/Mrs4s/go-cqhttp/internal/base" ) -var client = newcli(time.Second * 15) +var client = newClient(time.Second * 15) +var clients sync.Map var clienth2 = &http.Client{ Transport: &http.Transport{ @@ -37,7 +38,7 @@ var clienth2 = &http.Client{ Timeout: time.Second * 15, } -func newcli(t time.Duration) *http.Client { +func newClient(t time.Duration) *http.Client { return &http.Client{ Transport: &http.Transport{ Proxy: func(request *http.Request) (*url.URL, error) { @@ -62,7 +63,13 @@ const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 // WithTimeout get a download instance with timeout t func (r Request) WithTimeout(t time.Duration) *Request { - r.custcli = newcli(t) + if c, ok := clients.Load(t); ok { + r.custcli = c.(*http.Client) + } else { + c := newClient(t) + clients.Store(t, c) + r.custcli = c + } return &r } From 1ddcf8a74f55e84e0edf92c60bc7ab87f6c695ab Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Mon, 14 Aug 2023 11:15:15 +0800 Subject: [PATCH 07/28] refactor: wrap sign request --- cmd/gocq/login.go | 122 ++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 65 deletions(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index d3067b085..0f2ba3133 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -7,6 +7,7 @@ import ( "fmt" "image" "image/png" + "io" "net/http" "net/url" "os" @@ -269,26 +270,41 @@ func fetchCaptcha(id string) string { return "" } -func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) { +/* +请求签名服务器 + + url: api + params 组合的字符串,无须包含签名服务器地址 + return: signServer, response, error +*/ +func requestSignServer(method string, url string, headers map[string]string, body io.Reader) (string, []byte, error) { signServer := base.SignServer - if !strings.HasSuffix(signServer, "/") { - signServer += "/" + if !strings.HasPrefix(url, signServer) { + url = strings.TrimSuffix(signServer, "/") + "/" + strings.TrimPrefix(url, "/") + } + if headers == nil { + headers = map[string]string{} } - headers := make(map[string]string) signServerBearer := base.SignServerBearer if signServerBearer != "-" && signServerBearer != "" { headers["Authorization"] = "Bearer " + signServerBearer } req := download.Request{ - Method: http.MethodGet, + Method: method, 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)), + URL: url, + Body: body, }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second) + resp, err := req.Bytes() + return signServer, 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 { - req.URL = signServer + "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt)) + url = "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt)) } - response, err := req.Bytes() + signServer, response, err := requestSignServer(http.MethodGet, url, nil, nil) if err != nil { log.Warnf("获取T544 sign时出现错误: %v. server: %v", err, signServer) return nil, err @@ -308,10 +324,6 @@ func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) { // signSubmit // 提交回调 buffer 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 := "..." @@ -321,11 +333,12 @@ func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t strin } log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffStr[:tail], endl) - _, err := download.Request{ - Method: http.MethodGet, - URL: signServer + "submit" + fmt.Sprintf("?uin=%v&cmd=%v&callback_id=%v&buffer=%v", + signServer, _, err := requestSignServer( + http.MethodGet, + "submit"+fmt.Sprintf("?uin=%v&cmd=%v&callback_id=%v&buffer=%v", uin, cmd, callbackID, buffStr), - }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes() + nil, nil, + ) if err != nil { log.Warnf("提交 callback 时出现错误: %v. server: %v", err, signServer) } @@ -348,22 +361,14 @@ func signCallback(uin string, results []gjson.Result, t string) { } 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", + _, 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)))), - }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes() + ) if err != nil { return nil, nil, nil, err } @@ -383,15 +388,12 @@ func signRegister(uin int64, androidID, guid []byte, qimei36, key string) { 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", + 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), - }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second).Bytes() + nil, nil, + ) if err != nil { log.Warnf("注册QQ实例时出现错误: %v. server: %v", err, signServer) return @@ -405,15 +407,12 @@ func signRegister(uin int64, androidID, guid []byte, qimei36, key string) { } 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() + _, resp, err := requestSignServer( + http.MethodGet, + "request_token?uin="+uin, + nil, nil, + ) if err != nil { return err } @@ -475,40 +474,33 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b } func signServerDestroy(uin string) error { - signServer := base.SignServer - if !strings.HasSuffix(signServer, "/") { - signServer += "/" - } - signVersion, err := signVersion() + 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() + signServer, resp, err := requestSignServer( + http.MethodGet, + "destroy"+fmt.Sprintf("?uin=%v&key=%v", uin, base.Key), + nil, nil, + ) 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() +func signVersion() (signServer string, version string, err error) { + signServer, resp, err := requestSignServer(http.MethodGet, "", nil, nil) if err != nil { - return "", err + return signServer, "", err } if gjson.GetBytes(resp, "code").Int() == 0 { - return gjson.GetBytes(resp, "data.version").String(), nil + return signServer, gjson.GetBytes(resp, "data.version").String(), nil } - return "", errors.New("empty version") + return signServer, "", errors.New("empty version") } // 定时刷新 token, interval 为间隔时间(分钟) From 44762b444757cfeeceef95dd108d388bfffb413b Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Mon, 14 Aug 2023 11:22:48 +0800 Subject: [PATCH 08/28] feat: impl additional sign servers configuration --- cmd/gocq/login.go | 81 ++++++++++++++++++++++++++++--- cmd/gocq/main.go | 8 +-- internal/base/flag.go | 44 ++++++++--------- modules/config/config.go | 3 +- modules/config/default_config.yml | 18 +++++-- 5 files changed, 116 insertions(+), 38 deletions(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 0f2ba3133..15c3a1d40 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -270,6 +270,64 @@ func fetchCaptcha(id string) string { return "" } +// 配置的所有签名服务器 +var signServers = base.SignServers +var checkLock sync.Mutex + +// 当前签名服务器 +var currentSignServer = signServers[0] +var currentOK atomic.Bool + +var errorCount = int64(0) + +// 获取可用的签名服务器,没有则返回空和相应错误 +func GetAvaliableSignServer() (string, error) { + if currentOK.Load() { + return currentSignServer, nil + } + maxCount := base.Account.MaxCheckCount + if maxCount == 0 && atomic.LoadInt64(&errorCount) > 3 { + currentSignServer = signServers[0] + currentOK.Store(true) + return currentSignServer, nil + } + if maxCount > 0 && atomic.LoadInt64(&errorCount) > int64(maxCount) { + log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) + } + log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer) + if checkLock.TryLock() { + defer checkLock.Unlock() + + for i, server := range signServers { + if server == "-" || server == "" { + continue + } + log.Infof("正在检查签名服务器 %v (%v/%v)", server, i, len(signServers)) + if isServerAvaliable(server) { + errorCount = 0 + currentSignServer = server + currentOK.Store(true) + log.Warnf("签名服务器已切换至 %v", server) + return server, nil + } + } + return "", errors.New("没有可用的签名服务器") + } else { + return "-", errors.New("检查正在进行中...") + } +} + +func isServerAvaliable(signServer string) bool { + resp, err := download.Request{ + Method: http.MethodGet, + URL: signServer, + }.WithTimeout(5 * time.Second).Bytes() + if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { + return true + } + return false +} + /* 请求签名服务器 @@ -277,7 +335,12 @@ func fetchCaptcha(id string) string { return: signServer, response, error */ func requestSignServer(method string, url string, headers map[string]string, body io.Reader) (string, []byte, error) { - signServer := base.SignServer + signServer, e := GetAvaliableSignServer() + if e != nil && signServer == "" { // 没有可用的 + log.Warnf("获取可用签名服务器出错:%v", e) + atomic.AddInt64(&errorCount, 1) + signServer = signServers[0] // 没有获取到时使用第一个 + } if !strings.HasPrefix(url, signServer) { url = strings.TrimSuffix(signServer, "/") + "/" + strings.TrimPrefix(url, "/") } @@ -295,6 +358,9 @@ func requestSignServer(method string, url string, headers map[string]string, bod Body: body, }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second) resp, err := req.Bytes() + if err != nil { + currentOK.Store(false) + } return signServer, resp, err } @@ -433,7 +499,7 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b for { sign, extra, token, err = signRequset(seq, uin, cmd, qua, buff) if err != nil { - log.Warnf("获取sso sign时出现错误: %v. server: %v", err, base.SignServer) + log.Warnf("获取sso sign时出现错误: %v. server: %v", err, currentSignServer) } if i > 0 { break @@ -457,7 +523,7 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b if registerLock.TryLock() { defer registerLock.Unlock() if err := signRefreshToken(uin); err != nil { - log.Warnf("刷新 token 出现错误: %v. server: %v", err, base.SignServer) + log.Warnf("刷新 token 出现错误: %v. server: %v", err, currentSignServer) } else { log.Info("刷新 token 成功") } @@ -470,6 +536,9 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b log.Infof("token 已更新:%v -> %v", lastToken, tokenString) lastToken = tokenString } + if len(sign) == 0 { + currentOK.Store(false) + } return sign, extra, token, err } @@ -523,7 +592,7 @@ func signStartRefreshToken(interval int64) { for range t.C { err := signRefreshToken(qqstr) if err != nil { - log.Warnf("刷新 token 出现错误: %v. server: %v", err, base.SignServer) + log.Warnf("刷新 token 出现错误: %v. server: %v", err, currentSignServer) } } } @@ -537,7 +606,7 @@ func signWaitServer() bool { return false } i++ - u, err := url.Parse(base.SignServer) + u, err := url.Parse(currentSignServer) if err != nil { log.Warnf("连接到签名服务器出现错误: %v", err) continue @@ -549,6 +618,6 @@ func signWaitServer() bool { } break } - log.Infof("连接至签名服务器: %s", base.SignServer) + log.Infof("连接至签名服务器: %s", currentSignServer) return true } diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index 4e71431e8..be79d0d60 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -163,11 +163,11 @@ func LoginInteract() { log.Fatalf("加载设备信息失败: %v", err) } } - - if base.SignServer != "-" && base.SignServer != "" { - log.Infof("使用服务器 %s 进行数据包签名", base.SignServer) + signServer, _ := GetAvaliableSignServer() + if signServer != "-" && signServer != "" { + log.Infof("使用服务器 %s 进行数据包签名", signServer) if base.SignServerBearer != "-" && base.SignServerBearer != "" { - log.Infof("使用 Bearer %s 认证签名服务器 %s ", base.SignServerBearer, base.SignServer) + log.Infof("使用 Bearer %s 认证签名服务器 %s ", base.SignServerBearer, signServer) } // 等待签名服务器直到连接成功 if !signWaitServer() { diff --git a/internal/base/flag.go b/internal/base/flag.go index 8e0c37ac6..0e8f826da 100644 --- a/internal/base/flag.go +++ b/internal/base/flag.go @@ -23,27 +23,27 @@ var ( // config file flags var ( - Debug bool // 是否开启 debug 模式 - RemoveReplyAt bool // 是否删除reply后的at - ExtraReplyData bool // 是否上报额外reply信息 - IgnoreInvalidCQCode bool // 是否忽略无效CQ码 - SplitURL bool // 是否分割URL - ForceFragmented bool // 是否启用强制分片 - SkipMimeScan bool // 是否跳过Mime扫描 - ConvertWebpImage bool // 是否转换Webp图片 - ReportSelfMessage bool // 是否上报自身消息 - UseSSOAddress bool // 是否使用服务器下发的新地址进行重连 - LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志 - LogColorful bool // 是否启用日志颜色 - FastStart bool // 是否为快速启动 - AllowTempSession bool // 是否允许发送临时会话信息 - UpdateProtocol bool // 是否更新协议 - SignServer string // 使用特定的服务器进行签名 - SignServerBearer string // 认证签名服务器的 Bearer Token - Key string // 签名服务器密钥 - IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下 - HTTPTimeout int // download 超时时间 - SignServerTimeout int // 签名服务器超时时间 + Debug bool // 是否开启 debug 模式 + RemoveReplyAt bool // 是否删除reply后的at + ExtraReplyData bool // 是否上报额外reply信息 + IgnoreInvalidCQCode bool // 是否忽略无效CQ码 + SplitURL bool // 是否分割URL + ForceFragmented bool // 是否启用强制分片 + SkipMimeScan bool // 是否跳过Mime扫描 + ConvertWebpImage bool // 是否转换Webp图片 + ReportSelfMessage bool // 是否上报自身消息 + UseSSOAddress bool // 是否使用服务器下发的新地址进行重连 + LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志 + LogColorful bool // 是否启用日志颜色 + FastStart bool // 是否为快速启动 + AllowTempSession bool // 是否允许发送临时会话信息 + UpdateProtocol bool // 是否更新协议 + SignServers []string // 使用特定的服务器进行签名 + SignServerBearer string // 认证签名服务器的 Bearer Token + Key string // 签名服务器密钥 + IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下 + HTTPTimeout int // download 超时时间 + SignServerTimeout int // 签名服务器超时时间 PostFormat string // 上报格式 string or array Proxy string // 存储 proxy_rewrite,用于设置代理 @@ -92,7 +92,7 @@ func Init() { ReportSelfMessage = conf.Message.ReportSelfMessage UseSSOAddress = conf.Account.UseSSOAddress AllowTempSession = conf.Account.AllowTempSession - SignServer = conf.Account.SignServer + SignServers = conf.Account.SignServers SignServerBearer = conf.Account.SignServerBearer Key = conf.Account.Key IsBelow110 = conf.Account.IsBelow110 diff --git a/modules/config/config.go b/modules/config/config.go index 4bfbc18c7..e043c316a 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -35,7 +35,8 @@ type Account struct { ReLogin *Reconnect `yaml:"relogin"` UseSSOAddress bool `yaml:"use-sso-address"` AllowTempSession bool `yaml:"allow-temp-session"` - SignServer string `yaml:"sign-server"` + SignServers []string `yaml:"sign-servers"` + MaxCheckCount int `yaml:"max-check-count"` SignServerBearer string `yaml:"sign-server-bearer"` Key string `yaml:"key"` IsBelow110 bool `yaml:"is-below-110"` diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index c997e075e..fc129e5ef 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -16,14 +16,22 @@ account: # 账号相关 # 是否允许发送临时会话消息 allow-temp-session: false - # 数据包的签名服务器 + # 数据包的签名服务器列表,第一个作为主签名服务器,后续作为备用 # 兼容 https://github.com/fuqiuluo/unidbg-fetch-qsign - # 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个服务器 + # 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个或多个服务器 # 示例: - # sign-server: 'http://127.0.0.1:8080' # 本地签名服务器 - # sign-server: 'https://signserver.example.com' # 线上签名服务器 + # sign-servers: + # - 'http://127.0.0.1:8080' # 本地签名服务器 + # - 'https://signserver.example.com' # 线上签名服务器 + # ... # 服务器可使用docker在本地搭建或者使用他人开放的服务 - sign-server: '-' + sign-servers: + - "-" # 主签名服务器 + - "-" # 备用 + # 连续寻找可用签名服务器最大尝试次数 + # 为 0 时会在连续 3 次没有找到可用签名服务器后保持使用主签名服务器,不再尝试进行切换备用 + # 否则会在达到指定次数后 **退出** 主程序 + max-check-count: 0 # 签名服务器认证 Bearer Token # 使用开放的服务可能需要提供此 Token 进行认证 sign-server-bearer: '-' From d17b006cb7fa3462e0e49c51bd7245b8a089ff1a Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Mon, 14 Aug 2023 11:53:52 +0800 Subject: [PATCH 09/28] fix error in using configurations. --- cmd/gocq/login.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 15c3a1d40..92972f805 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -271,21 +271,22 @@ func fetchCaptcha(id string) string { } // 配置的所有签名服务器 -var signServers = base.SignServers +var signServers []string var checkLock sync.Mutex // 当前签名服务器 -var currentSignServer = signServers[0] +var currentSignServer string var currentOK atomic.Bool var errorCount = int64(0) -// 获取可用的签名服务器,没有则返回空和相应错误 +// GetAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 func GetAvaliableSignServer() (string, error) { if currentOK.Load() { return currentSignServer, nil } maxCount := base.Account.MaxCheckCount + signServers = base.SignServers if maxCount == 0 && atomic.LoadInt64(&errorCount) > 3 { currentSignServer = signServers[0] currentOK.Store(true) @@ -297,24 +298,22 @@ func GetAvaliableSignServer() (string, error) { log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer) if checkLock.TryLock() { defer checkLock.Unlock() - for i, server := range signServers { if server == "-" || server == "" { continue } - log.Infof("正在检查签名服务器 %v (%v/%v)", server, i, len(signServers)) + log.Infof("正在检查签名服务器 %v 是否可用.(%v/%v)", server, i, len(signServers)) if isServerAvaliable(server) { errorCount = 0 currentSignServer = server currentOK.Store(true) - log.Warnf("签名服务器已切换至 %v", server) + log.Infof("使用签名服务器 %v", server) return server, nil } } - return "", errors.New("没有可用的签名服务器") - } else { - return "-", errors.New("检查正在进行中...") + return "", errors.New("no avaliable sign-server") } + return "-", errors.New("checking sign-servers...") } func isServerAvaliable(signServer string) bool { From 0c3962b004b87d26917d3e9626ffaa11a71201db Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Mon, 14 Aug 2023 11:57:16 +0800 Subject: [PATCH 10/28] fix lint error --- cmd/gocq/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 92972f805..6868bfb97 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -313,7 +313,7 @@ func GetAvaliableSignServer() (string, error) { } return "", errors.New("no avaliable sign-server") } - return "-", errors.New("checking sign-servers...") + return "-", errors.New("checking sign-servers") } func isServerAvaliable(signServer string) bool { From d4a1ca0a5d588c670424c8a2a5871c9d6ec1f07f Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Mon, 14 Aug 2023 12:30:19 +0800 Subject: [PATCH 11/28] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E5=9B=9E=E4=B8=BB=E7=AD=BE=E5=90=8D=E6=9C=8D=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/gocq/login.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index 6868bfb97..b8b00cc2e 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -295,7 +295,9 @@ func GetAvaliableSignServer() (string, error) { if maxCount > 0 && atomic.LoadInt64(&errorCount) > int64(maxCount) { log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) } - log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer) + if len(currentSignServer) > 1 { + log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer) + } if checkLock.TryLock() { defer checkLock.Unlock() for i, server := range signServers { @@ -589,6 +591,11 @@ func signStartRefreshToken(interval int64) { qqstr := strconv.FormatInt(base.Account.Uin, 10) defer t.Stop() for range t.C { + if currentSignServer != signServers[0] && isServerAvaliable(signServers[0]) { + currentSignServer = signServers[0] + currentOK.Store(true) + log.Infof("主签名服务器可用,已切换至主签名服务器 %v", currentSignServer) + } err := signRefreshToken(qqstr) if err != nil { log.Warnf("刷新 token 出现错误: %v. server: %v", err, currentSignServer) From 515f51be4bf666526a0e2f5cfae56d9c574c0795 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 17 Aug 2023 10:42:46 +0800 Subject: [PATCH 12/28] feat: support different key and auth --- cmd/gocq/login.go | 367 ---------------------------- cmd/gocq/main.go | 14 +- cmd/gocq/qsign.go | 383 ++++++++++++++++++++++++++++++ internal/base/flag.go | 44 ++-- modules/config/config.go | 37 +-- modules/config/default_config.yml | 29 ++- 6 files changed, 446 insertions(+), 428 deletions(-) create mode 100644 cmd/gocq/qsign.go diff --git a/cmd/gocq/login.go b/cmd/gocq/login.go index b8b00cc2e..4feab4f78 100644 --- a/cmd/gocq/login.go +++ b/cmd/gocq/login.go @@ -3,18 +3,11 @@ package gocq import ( "bufio" "bytes" - "encoding/hex" "fmt" "image" "image/png" - "io" - "net/http" - "net/url" "os" - "strconv" "strings" - "sync" - "sync/atomic" "time" "github.com/Mrs4s/MiraiGo/client" @@ -22,11 +15,9 @@ import ( "github.com/mattn/go-colorable" "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" "gopkg.ilharper.com/x/isatty" "github.com/Mrs4s/go-cqhttp/global" - "github.com/Mrs4s/go-cqhttp/internal/base" "github.com/Mrs4s/go-cqhttp/internal/download" ) @@ -269,361 +260,3 @@ func fetchCaptcha(id string) string { } return "" } - -// 配置的所有签名服务器 -var signServers []string -var checkLock sync.Mutex - -// 当前签名服务器 -var currentSignServer string -var currentOK atomic.Bool - -var errorCount = int64(0) - -// GetAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 -func GetAvaliableSignServer() (string, error) { - if currentOK.Load() { - return currentSignServer, nil - } - maxCount := base.Account.MaxCheckCount - signServers = base.SignServers - if maxCount == 0 && atomic.LoadInt64(&errorCount) > 3 { - currentSignServer = signServers[0] - currentOK.Store(true) - return currentSignServer, nil - } - if maxCount > 0 && atomic.LoadInt64(&errorCount) > int64(maxCount) { - log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) - } - if len(currentSignServer) > 1 { - log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer) - } - if checkLock.TryLock() { - defer checkLock.Unlock() - for i, server := range signServers { - if server == "-" || server == "" { - continue - } - log.Infof("正在检查签名服务器 %v 是否可用.(%v/%v)", server, i, len(signServers)) - if isServerAvaliable(server) { - errorCount = 0 - currentSignServer = server - currentOK.Store(true) - log.Infof("使用签名服务器 %v", server) - return server, nil - } - } - return "", errors.New("no avaliable sign-server") - } - return "-", errors.New("checking sign-servers") -} - -func isServerAvaliable(signServer string) bool { - resp, err := download.Request{ - Method: http.MethodGet, - URL: signServer, - }.WithTimeout(5 * time.Second).Bytes() - if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { - return true - } - return false -} - -/* -请求签名服务器 - - 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 && signServer == "" { // 没有可用的 - log.Warnf("获取可用签名服务器出错:%v", e) - atomic.AddInt64(&errorCount, 1) - signServer = signServers[0] // 没有获取到时使用第一个 - } - if !strings.HasPrefix(url, signServer) { - url = strings.TrimSuffix(signServer, "/") + "/" + strings.TrimPrefix(url, "/") - } - if headers == nil { - headers = map[string]string{} - } - signServerBearer := base.SignServerBearer - if signServerBearer != "-" && signServerBearer != "" { - headers["Authorization"] = "Bearer " + signServerBearer - } - 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 { - currentOK.Store(false) - } - return signServer, 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 { - sign, extra, token, err = signRequset(seq, uin, cmd, qua, buff) - if err != nil { - log.Warnf("获取sso sign时出现错误: %v. server: %v", err, currentSignServer) - } - 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 , 以重新获取本次签名 - // 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, currentSignServer) - } else { - log.Info("刷新 token 成功") - } - } - continue - } - break - } - if tokenString := hex.EncodeToString(token); lastToken != tokenString { - log.Infof("token 已更新:%v -> %v", lastToken, tokenString) - lastToken = tokenString - } - if len(sign) == 0 { - currentOK.Store(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, base.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 { - if currentSignServer != signServers[0] && isServerAvaliable(signServers[0]) { - currentSignServer = signServers[0] - currentOK.Store(true) - log.Infof("主签名服务器可用,已切换至主签名服务器 %v", currentSignServer) - } - err := signRefreshToken(qqstr) - if err != nil { - log.Warnf("刷新 token 出现错误: %v. server: %v", err, currentSignServer) - } - } -} - -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(currentSignServer) - 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", currentSignServer) - return true -} diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index be79d0d60..9a70261fe 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -164,31 +164,27 @@ func LoginInteract() { } } signServer, _ := GetAvaliableSignServer() - if signServer != "-" && signServer != "" { - log.Infof("使用服务器 %s 进行数据包签名", signServer) - if base.SignServerBearer != "-" && base.SignServerBearer != "" { - log.Infof("使用 Bearer %s 认证签名服务器 %s ", base.SignServerBearer, signServer) - } + if len(signServer.URL) > 1 { // 等待签名服务器直到连接成功 if !signWaitServer() { log.Fatalf("连接签名服务器失败") } - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, base.Key) + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, signServer.Key) go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token wrapper.DandelionEnergy = energy wrapper.FekitGetSign = sign if !base.IsBelow110 { if !base.Account.AutoRegister { - log.Warn("自动注册实例已关闭,若未配置 sign-server 端自动注册实例则实例丢失时需要重启 go-cqhttp 以正常签名") + log.Warn("自动注册实例已关闭,请配置 sign-server 端自动注册实例以保持正常签名") } if !base.Account.AutoRefreshToken { - log.Warn("自动刷新 token 已关闭,token 过期后获取签名时将不会立即尝试刷新获取新 token") + log.Info("自动刷新 token 已关闭,token 过期后获取签名时将不会立即尝试刷新获取新 token") } } else { log.Warn("签名服务器版本 <= 1.1.0 ,无法使用刷新 token 等操作,建议使用 1.1.6 版本及以上签名服务器") } } else { - log.Warnf("警告: 未配置签名服务器, 这可能会导致登录 45 错误码或发送消息被风控") + log.Warnf("警告: 未配置签名服务器或签名服务器不可用, 这可能会导致登录 45 错误码或发送消息被风控") } if base.Account.Encrypt { diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go new file mode 100644 index 000000000..1abd74c71 --- /dev/null +++ b/cmd/gocq/qsign.go @@ -0,0 +1,383 @@ +package gocq + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "net/http" + "net/url" + "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" +) + +// 配置的所有签名服务器 +var signServers []config.SignServer +var checkLock sync.Mutex + +// 当前签名服务器 +var currentSignServer config.SignServer +var currentOK atomic.Bool + +var errorCount = int64(0) + +// GetAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 +func GetAvaliableSignServer() (config.SignServer, error) { + if currentOK.Load() { + return currentSignServer, nil + } + maxCount := base.Account.MaxCheckCount + signServers = base.SignServers + if maxCount == 0 && atomic.LoadInt64(&errorCount) > 3 { + currentSignServer = signServers[0] + currentOK.Store(true) + return currentSignServer, nil + } + if maxCount > 0 && atomic.LoadInt64(&errorCount) > int64(maxCount) { + log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) + } + if len(currentSignServer.URL) > 1 { + log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer.URL) + } + if checkLock.TryLock() { + defer checkLock.Unlock() + for i, server := range signServers { + if server.URL == "-" || server.URL == "" { + continue + } + log.Infof("正在检查签名服务器 %v 是否可用.(%v/%v)", server.URL, i+1, len(signServers)) + if isServerAvaliable(server.URL) { + errorCount = 0 + currentSignServer = server + currentOK.Store(true) + log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", server.URL, server.Key, server.Authorization) + return server, nil + } + } + return config.SignServer{}, errors.New("no avaliable sign-server") + } + return config.SignServer{}, errors.New("checking sign-servers") +} + +func isServerAvaliable(signServer string) bool { + resp, err := download.Request{ + Method: http.MethodGet, + URL: signServer, + }.WithTimeout(5 * time.Second).Bytes() + if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { + return true + } + return false +} + +/* +请求签名服务器 + + 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.AddInt64(&errorCount, 1) + signServer = 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 { + currentOK.Store(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 { + sign, extra, token, err = signRequset(seq, uin, cmd, qua, buff) + if err != nil { + log.Warnf("获取sso sign时出现错误: %v. server: %v", err, currentSignServer.URL) + } + 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 , 以重新获取本次签名 + // return nil, nil, nil, err + } + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, currentSignServer.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, currentSignServer.URL) + } else { + log.Info("刷新 token 成功") + } + } + continue + } + break + } + if tokenString := hex.EncodeToString(token); lastToken != tokenString { + log.Infof("token 已更新:%v -> %v", lastToken, tokenString) + lastToken = tokenString + } + if len(sign) == 0 { + currentOK.Store(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, currentSignServer.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 { + if currentSignServer != signServers[0] && isServerAvaliable(signServers[0].URL) { + currentSignServer = signServers[0] + currentOK.Store(true) + log.Infof("主签名服务器可用,已切换至主签名服务器 %v", currentSignServer.URL) + } + err := signRefreshToken(qqstr) + if err != nil { + log.Warnf("刷新 token 出现错误: %v. server: %v", err, currentSignServer.URL) + } + } +} + +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(currentSignServer.URL) + 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", currentSignServer.URL) + return true +} diff --git a/internal/base/flag.go b/internal/base/flag.go index 0e8f826da..1bb5d4bf2 100644 --- a/internal/base/flag.go +++ b/internal/base/flag.go @@ -23,27 +23,25 @@ var ( // config file flags var ( - Debug bool // 是否开启 debug 模式 - RemoveReplyAt bool // 是否删除reply后的at - ExtraReplyData bool // 是否上报额外reply信息 - IgnoreInvalidCQCode bool // 是否忽略无效CQ码 - SplitURL bool // 是否分割URL - ForceFragmented bool // 是否启用强制分片 - SkipMimeScan bool // 是否跳过Mime扫描 - ConvertWebpImage bool // 是否转换Webp图片 - ReportSelfMessage bool // 是否上报自身消息 - UseSSOAddress bool // 是否使用服务器下发的新地址进行重连 - LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志 - LogColorful bool // 是否启用日志颜色 - FastStart bool // 是否为快速启动 - AllowTempSession bool // 是否允许发送临时会话信息 - UpdateProtocol bool // 是否更新协议 - SignServers []string // 使用特定的服务器进行签名 - SignServerBearer string // 认证签名服务器的 Bearer Token - Key string // 签名服务器密钥 - IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下 - HTTPTimeout int // download 超时时间 - SignServerTimeout int // 签名服务器超时时间 + Debug bool // 是否开启 debug 模式 + RemoveReplyAt bool // 是否删除reply后的at + ExtraReplyData bool // 是否上报额外reply信息 + IgnoreInvalidCQCode bool // 是否忽略无效CQ码 + SplitURL bool // 是否分割URL + ForceFragmented bool // 是否启用强制分片 + SkipMimeScan bool // 是否跳过Mime扫描 + ConvertWebpImage bool // 是否转换Webp图片 + ReportSelfMessage bool // 是否上报自身消息 + UseSSOAddress bool // 是否使用服务器下发的新地址进行重连 + LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志 + LogColorful bool // 是否启用日志颜色 + FastStart bool // 是否为快速启动 + AllowTempSession bool // 是否允许发送临时会话信息 + UpdateProtocol bool // 是否更新协议 + SignServers []config.SignServer // 使用特定的服务器进行签名 + IsBelow110 bool // 签名服务器版本是否低于1.1.0及以下 + HTTPTimeout int // download 超时时间 + SignServerTimeout int // 签名服务器超时时间 PostFormat string // 上报格式 string or array Proxy string // 存储 proxy_rewrite,用于设置代理 @@ -93,11 +91,9 @@ func Init() { UseSSOAddress = conf.Account.UseSSOAddress AllowTempSession = conf.Account.AllowTempSession SignServers = conf.Account.SignServers - SignServerBearer = conf.Account.SignServerBearer - Key = conf.Account.Key IsBelow110 = conf.Account.IsBelow110 HTTPTimeout = conf.Message.HTTPTimeout - SignServerTimeout = conf.Message.SignServerTimeout + SignServerTimeout = conf.Account.SignServerTimeout } { // others Proxy = conf.Message.ProxyRewrite diff --git a/modules/config/config.go b/modules/config/config.go index e043c316a..c5d0e60b4 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -28,21 +28,27 @@ type Reconnect struct { // Account 账号配置 type Account struct { - Uin int64 `yaml:"uin"` - Password string `yaml:"password"` - Encrypt bool `yaml:"encrypt"` - Status int `yaml:"status"` - ReLogin *Reconnect `yaml:"relogin"` - UseSSOAddress bool `yaml:"use-sso-address"` - AllowTempSession bool `yaml:"allow-temp-session"` - SignServers []string `yaml:"sign-servers"` - MaxCheckCount int `yaml:"max-check-count"` - 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"` + Uin int64 `yaml:"uin"` + Password string `yaml:"password"` + Encrypt bool `yaml:"encrypt"` + Status int `yaml:"status"` + ReLogin *Reconnect `yaml:"relogin"` + UseSSOAddress bool `yaml:"use-sso-address"` + AllowTempSession bool `yaml:"allow-temp-session"` + SignServers []SignServer `yaml:"sign-servers"` + MaxCheckCount int `yaml:"max-check-count"` + SignServerTimeout int `yaml:"sign-server-timeout"` + IsBelow110 bool `yaml:"is-below-110"` + AutoRegister bool `yaml:"auto-register"` + AutoRefreshToken bool `yaml:"auto-refresh-token"` + RefreshInterval int64 `yaml:"refresh-interval"` +} + +// SignServer 签名服务器 +type SignServer struct { + URL string `yaml:"url"` + Key string `yaml:"key"` + Authorization string `yaml:"authorization"` } // Config 总配置文件 @@ -65,7 +71,6 @@ type Config struct { SkipMimeScan bool `yaml:"skip-mime-scan"` ConvertWebpImage bool `yaml:"convert-webp-image"` HTTPTimeout int `yaml:"http-timeout"` - SignServerTimeout int `yaml:"sign-server-timeout"` } `yaml:"message"` Output struct { diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index fc129e5ef..a859adb79 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -21,25 +21,32 @@ account: # 账号相关 # 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个或多个服务器 # 示例: # sign-servers: - # - 'http://127.0.0.1:8080' # 本地签名服务器 - # - '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在本地搭建或者使用他人开放的服务 sign-servers: - - "-" # 主签名服务器 - - "-" # 备用 + - url: '-' # 主签名服务器地址, 必填 + key: '114514' # 签名服务器所需要的apikey, 如果签名服务器的版本在1.1.0及以下则此项无效 + authorization: '-' # authorization 内容, 依服务端设置 + - url: '-' # 备用 + key: '114514' + authorization: '-' + # 连续寻找可用签名服务器最大尝试次数 # 为 0 时会在连续 3 次没有找到可用签名服务器后保持使用主签名服务器,不再尝试进行切换备用 # 否则会在达到指定次数后 **退出** 主程序 max-check-count: 0 - # 签名服务器认证 Bearer Token - # 使用开放的服务可能需要提供此 Token 进行认证 - sign-server-bearer: '-' + # 签名服务请求超时时间(s) + sign-server-timeout: 60 # 如果签名服务器的版本在1.1.0及以下, 请将下面的参数改成true + # 建议使用 1.1.6 以上版本,低版本普遍半个月冻结一次 is-below-110: false - # 签名服务器所需要的apikey, 如果签名服务器的版本在1.1.0及以下则此项无效 - # 本地部署的默认为114514 - key: '114514' # 在实例可能丢失(获取到的签名为空)时是否尝试重新注册 # 为 true 时,在签名服务不可用时可能每次发消息都会尝试重新注册并签名。 # 为 false 时,将不会自动注册实例,在签名服务器重启或实例被销毁后需要重启 go-cqhttp 以获取实例 @@ -84,8 +91,6 @@ message: convert-webp-image: false # download 超时时间(s) http-timeout: 15 - # 签名服务超时时间(s) - sign-server-timeout: 60 output: # 日志等级 trace,debug,info,warn,error From a864476e83102226a515dbbce813c9d31431c7c0 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 17 Aug 2023 11:55:29 +0800 Subject: [PATCH 13/28] optimize: find avaliable sign-server --- cmd/gocq/main.go | 9 ++++--- cmd/gocq/qsign.go | 60 +++++++++++++++++------------------------------ 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index 9a70261fe..1e1b96ddf 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -163,12 +163,11 @@ func LoginInteract() { log.Fatalf("加载设备信息失败: %v", err) } } - signServer, _ := GetAvaliableSignServer() + signServer, err := GetAvaliableSignServer() // 获取可用签名服务器 + if err != nil { + log.Warn(err) + } if len(signServer.URL) > 1 { - // 等待签名服务器直到连接成功 - if !signWaitServer() { - log.Fatalf("连接签名服务器失败") - } signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, signServer.Key) go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token wrapper.DandelionEnergy = energy diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 1abd74c71..fc008d4fd 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net/http" - "net/url" "strconv" "strings" "sync" @@ -54,20 +53,28 @@ func GetAvaliableSignServer() (config.SignServer, error) { } if checkLock.TryLock() { defer checkLock.Unlock() - for i, server := range signServers { - if server.URL == "-" || server.URL == "" { - continue - } - log.Infof("正在检查签名服务器 %v 是否可用.(%v/%v)", server.URL, i+1, len(signServers)) - if isServerAvaliable(server.URL) { - errorCount = 0 - currentSignServer = server - currentOK.Store(true) - log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", server.URL, server.Key, server.Authorization) - return server, nil - } + result := make(chan config.SignServer) + for _, server := range signServers { + go func(s config.SignServer, r chan config.SignServer) { + if s.URL == "-" || s.URL == "" { + return + } + if isServerAvaliable(s.URL) { + errorCount = 0 + result <- s + log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) + } + }(server, result) + } + select { + case res := <-result: + currentSignServer = res + currentOK.Store(true) + return currentSignServer, nil + case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): + return config.SignServer{}, errors.New( + fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout)) } - return config.SignServer{}, errors.New("no avaliable sign-server") } return config.SignServer{}, errors.New("checking sign-servers") } @@ -356,28 +363,3 @@ func signStartRefreshToken(interval int64) { } } } - -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(currentSignServer.URL) - 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", currentSignServer.URL) - return true -} From e276dbb951e8810713c58f77c72c9af62211fb0b Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 17 Aug 2023 12:07:39 +0800 Subject: [PATCH 14/28] fix: register instance after server is changed --- cmd/gocq/main.go | 1 - cmd/gocq/qsign.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index 1e1b96ddf..e2e9870d4 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -168,7 +168,6 @@ func LoginInteract() { log.Warn(err) } if len(signServer.URL) > 1 { - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, signServer.Key) go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token wrapper.DandelionEnergy = energy wrapper.FekitGetSign = sign diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index fc008d4fd..390dc2468 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -70,6 +70,7 @@ func GetAvaliableSignServer() (config.SignServer, error) { case res := <-result: currentSignServer = res currentOK.Store(true) + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) // 注册实例 return currentSignServer, nil case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): return config.SignServer{}, errors.New( From 06ec5d1a76a616941e6e8b92806ec80bad1114df Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 17 Aug 2023 12:37:24 +0800 Subject: [PATCH 15/28] fix lint error --- cmd/gocq/qsign.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 390dc2468..c06da091d 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -17,6 +17,7 @@ import ( "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" @@ -61,7 +62,7 @@ func GetAvaliableSignServer() (config.SignServer, error) { } if isServerAvaliable(s.URL) { errorCount = 0 - result <- s + r <- s log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) } }(server, result) @@ -73,8 +74,8 @@ func GetAvaliableSignServer() (config.SignServer, error) { signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) // 注册实例 return currentSignServer, nil case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): - return config.SignServer{}, errors.New( - fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout)) + errMsg := fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout) + return config.SignServer{}, errors.New(errMsg) } } return config.SignServer{}, errors.New("checking sign-servers") From 52515e4271e2690aa67e5471aee4fb181af59d04 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 17 Aug 2023 19:43:29 +0800 Subject: [PATCH 16/28] update: add config 'sync-check-servers' --- cmd/gocq/qsign.go | 23 ++++++++++++++++++++--- modules/config/config.go | 1 + modules/config/default_config.yml | 6 +++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index c06da091d..c79c7f474 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -41,12 +41,12 @@ func GetAvaliableSignServer() (config.SignServer, error) { } maxCount := base.Account.MaxCheckCount signServers = base.SignServers - if maxCount == 0 && atomic.LoadInt64(&errorCount) > 3 { + if maxCount == 0 && atomic.LoadInt64(&errorCount) >= 3 { currentSignServer = signServers[0] currentOK.Store(true) return currentSignServer, nil } - if maxCount > 0 && atomic.LoadInt64(&errorCount) > int64(maxCount) { + if maxCount > 0 && atomic.LoadInt64(&errorCount) >= int64(maxCount) { log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) } if len(currentSignServer.URL) > 1 { @@ -54,10 +54,27 @@ func GetAvaliableSignServer() (config.SignServer, error) { } if checkLock.TryLock() { defer checkLock.Unlock() + if base.Account.SyncCheckServers { + for i, server := range signServers { + log.Infof("检查签名服务器:%v (%v/%v)", server.URL, i+1, len(signServers)) + if len(server.URL) < 4 { + continue + } + if isServerAvaliable(server.URL) { + errorCount = 0 + currentSignServer = server + currentOK.Store(true) + log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", server.URL, server.Key, server.Authorization) + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, server.Key) // 注册实例 + return currentSignServer, nil + } + } + return config.SignServer{}, errors.New("no avaliable sign-server") + } result := make(chan config.SignServer) for _, server := range signServers { go func(s config.SignServer, r chan config.SignServer) { - if s.URL == "-" || s.URL == "" { + if len(s.URL) < 4 { return } if isServerAvaliable(s.URL) { diff --git a/modules/config/config.go b/modules/config/config.go index c5d0e60b4..6a5506350 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -36,6 +36,7 @@ type Account struct { UseSSOAddress bool `yaml:"use-sso-address"` AllowTempSession bool `yaml:"allow-temp-session"` SignServers []SignServer `yaml:"sign-servers"` + SyncCheckServers bool `yaml:"sync-check-servers"` MaxCheckCount int `yaml:"max-check-count"` SignServerTimeout int `yaml:"sign-server-timeout"` IsBelow110 bool `yaml:"is-below-110"` diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index a859adb79..cc0cd9c2f 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -19,6 +19,7 @@ account: # 账号相关 # 数据包的签名服务器列表,第一个作为主签名服务器,后续作为备用 # 兼容 https://github.com/fuqiuluo/unidbg-fetch-qsign # 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个或多个服务器 + # 不建议设置过多,最好不超过4个 # 示例: # sign-servers: # - url: 'http://127.0.0.1:8080' # 本地签名服务器 @@ -33,11 +34,14 @@ account: # 账号相关 sign-servers: - url: '-' # 主签名服务器地址, 必填 key: '114514' # 签名服务器所需要的apikey, 如果签名服务器的版本在1.1.0及以下则此项无效 - authorization: '-' # authorization 内容, 依服务端设置 + authorization: '-' # authorization 内容, 依服务端设置,如 'Bearer xxxx' - url: '-' # 备用 key: '114514' authorization: '-' + # 是否以同步顺序方式查找签名服务器,找不到时可能会比较慢,默认并发查找 + sync-check-servers: false + # 连续寻找可用签名服务器最大尝试次数 # 为 0 时会在连续 3 次没有找到可用签名服务器后保持使用主签名服务器,不再尝试进行切换备用 # 否则会在达到指定次数后 **退出** 主程序 From f8700f6aaab6398b011e6955f49767cab04fc837 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Fri, 18 Aug 2023 07:17:34 +0800 Subject: [PATCH 17/28] update: first check master sign-server, or wait 3s --- cmd/gocq/qsign.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index c79c7f474..5068a0c84 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -77,6 +77,9 @@ func GetAvaliableSignServer() (config.SignServer, error) { if len(s.URL) < 4 { return } + if s.URL != signServers[0].URL { // 主服务器优先检查 + time.Sleep(3 * time.Second) + } if isServerAvaliable(s.URL) { errorCount = 0 r <- s @@ -106,6 +109,7 @@ func isServerAvaliable(signServer string) bool { if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { return true } + log.Warnf("签名服务器可能不可用,请求出现错误:%v", err) return false } From 61c538835c934562b009f6b2a53de85e3dc9d3e6 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Fri, 18 Aug 2023 17:18:52 +0800 Subject: [PATCH 18/28] add checking log & optimize wait for checking done --- cmd/gocq/qsign.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 5068a0c84..f2145535d 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -36,6 +36,9 @@ var errorCount = int64(0) // GetAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 func GetAvaliableSignServer() (config.SignServer, error) { + if len(signServers) == 0 { + return config.SignServer{}, errors.New("no configured sign-server") + } if currentOK.Load() { return currentSignServer, nil } @@ -72,8 +75,17 @@ func GetAvaliableSignServer() (config.SignServer, error) { return config.SignServer{}, errors.New("no avaliable sign-server") } result := make(chan config.SignServer) + allDone := make(chan bool) + i := int32(0) + log.Infof("正在检查各签名服务是否可用... (最多等待 %vs)", base.SignServerTimeout) for _, server := range signServers { go func(s config.SignServer, r chan config.SignServer) { + atomic.AddInt32(&i, 1) + defer func(count int) { + if len(signServers) == count { + allDone <- true + } + }(int(i)) if len(s.URL) < 4 { return } @@ -93,6 +105,8 @@ func GetAvaliableSignServer() (config.SignServer, error) { currentOK.Store(true) signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) // 注册实例 return currentSignServer, nil + case <-allDone: + return config.SignServer{}, errors.New("no avaliable sign-server") case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): errMsg := fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout) return config.SignServer{}, errors.New(errMsg) From e3e7843b61bb26a596847cb78d0fd49186d0eebb Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Fri, 18 Aug 2023 17:29:47 +0800 Subject: [PATCH 19/28] fix wrong judge --- cmd/gocq/qsign.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index f2145535d..3cb9885a5 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -36,7 +36,7 @@ var errorCount = int64(0) // GetAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 func GetAvaliableSignServer() (config.SignServer, error) { - if len(signServers) == 0 { + if len(base.SignServers) == 0 { return config.SignServer{}, errors.New("no configured sign-server") } if currentOK.Load() { From 81eb27258c1d8d7d4ed7d38caadb3aa26b0b2a23 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Fri, 18 Aug 2023 19:44:37 +0800 Subject: [PATCH 20/28] add config: rule for changing sign server --- cmd/gocq/qsign.go | 5 +++-- modules/config/config.go | 31 ++++++++++++++++--------------- modules/config/default_config.yml | 6 ++++++ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 3cb9885a5..466102c9d 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -123,7 +123,7 @@ func isServerAvaliable(signServer string) bool { if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { return true } - log.Warnf("签名服务器可能不可用,请求出现错误:%v", err) + log.Warnf("签名服务器 %v 可能不可用,请求出现错误:%v", signServer, err) return false } @@ -335,7 +335,8 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b log.Infof("token 已更新:%v -> %v", lastToken, tokenString) lastToken = tokenString } - if len(sign) == 0 { + rule := base.Account.RuleChangeSignServer + if (len(sign) == 0 && rule >= 1) || (len(token) == 0 && rule >= 2) { currentOK.Store(false) } return sign, extra, token, err diff --git a/modules/config/config.go b/modules/config/config.go index 6a5506350..a1dc1feb1 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -28,21 +28,22 @@ type Reconnect struct { // Account 账号配置 type Account struct { - Uin int64 `yaml:"uin"` - Password string `yaml:"password"` - Encrypt bool `yaml:"encrypt"` - Status int `yaml:"status"` - ReLogin *Reconnect `yaml:"relogin"` - UseSSOAddress bool `yaml:"use-sso-address"` - AllowTempSession bool `yaml:"allow-temp-session"` - SignServers []SignServer `yaml:"sign-servers"` - SyncCheckServers bool `yaml:"sync-check-servers"` - MaxCheckCount int `yaml:"max-check-count"` - SignServerTimeout int `yaml:"sign-server-timeout"` - IsBelow110 bool `yaml:"is-below-110"` - AutoRegister bool `yaml:"auto-register"` - AutoRefreshToken bool `yaml:"auto-refresh-token"` - RefreshInterval int64 `yaml:"refresh-interval"` + Uin int64 `yaml:"uin"` + Password string `yaml:"password"` + Encrypt bool `yaml:"encrypt"` + Status int `yaml:"status"` + ReLogin *Reconnect `yaml:"relogin"` + UseSSOAddress bool `yaml:"use-sso-address"` + AllowTempSession bool `yaml:"allow-temp-session"` + SignServers []SignServer `yaml:"sign-servers"` + SyncCheckServers bool `yaml:"sync-check-servers"` + RuleChangeSignServer int `yaml:"rule-change-sign-server"` + MaxCheckCount int `yaml:"max-check-count"` + SignServerTimeout int `yaml:"sign-server-timeout"` + IsBelow110 bool `yaml:"is-below-110"` + AutoRegister bool `yaml:"auto-register"` + AutoRefreshToken bool `yaml:"auto-refresh-token"` + RefreshInterval int64 `yaml:"refresh-interval"` } // SignServer 签名服务器 diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index cc0cd9c2f..8367f1c3a 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -42,6 +42,12 @@ account: # 账号相关 # 是否以同步顺序方式查找签名服务器,找不到时可能会比较慢,默认并发查找 sync-check-servers: false + # 判断签名服务不可用(需要切换)的额外规则 + # 0: 不设置 + # 1: 在获取到的 sign 为空 (若选此建议关闭 auto-register,一般为实例未注册但是请求签名的情况) + # 2: 在获取到的 sign 或 token 为空(若选此建议关闭 auto-refresh-token ) + rule-change-sign-server: 1 + # 连续寻找可用签名服务器最大尝试次数 # 为 0 时会在连续 3 次没有找到可用签名服务器后保持使用主签名服务器,不再尝试进行切换备用 # 否则会在达到指定次数后 **退出** 主程序 From 84850383912aef694095b75a4855678529936513 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Sat, 19 Aug 2023 09:18:47 +0800 Subject: [PATCH 21/28] optimize registration logic after changing server --- cmd/gocq/qsign.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 466102c9d..ec4b8eda3 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -68,7 +68,10 @@ func GetAvaliableSignServer() (config.SignServer, error) { currentSignServer = server currentOK.Store(true) log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", server.URL, server.Key, server.Authorization) - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, server.Key) // 注册实例 + if base.Account.AutoRegister { + // 若配置了自动注册实例则在切换后注册实例,否则不需要注册,签名时由qsign自动注册 + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, server.Key) + } return currentSignServer, nil } } @@ -103,7 +106,9 @@ func GetAvaliableSignServer() (config.SignServer, error) { case res := <-result: currentSignServer = res currentOK.Store(true) - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) // 注册实例 + if base.Account.AutoRegister { + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) + } return currentSignServer, nil case <-allDone: return config.SignServer{}, errors.New("no avaliable sign-server") From d40ffa37bb3daf05269e351ef45a5ca692e1199f Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Mon, 21 Aug 2023 19:03:27 +0800 Subject: [PATCH 22/28] add some log --- cmd/gocq/main.go | 3 +++ cmd/gocq/qsign.go | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index e2e9870d4..0cc095c10 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -163,6 +163,9 @@ func LoginInteract() { log.Fatalf("加载设备信息失败: %v", err) } } + if len(base.Account.SignServers) > 5 { + log.Warn("签名服务器数量配置过多,推荐不超过 5 个") + } signServer, err := GetAvaliableSignServer() // 获取可用签名服务器 if err != nil { log.Warn(err) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index ec4b8eda3..a3ab126bd 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -45,6 +45,7 @@ func GetAvaliableSignServer() (config.SignServer, error) { maxCount := base.Account.MaxCheckCount signServers = base.SignServers if maxCount == 0 && atomic.LoadInt64(&errorCount) >= 3 { + log.Warn("已连续 3 次获取不到可用签名服务器,将固定使用主签名服务器") currentSignServer = signServers[0] currentOK.Store(true) return currentSignServer, nil @@ -141,7 +142,7 @@ func isServerAvaliable(signServer string) bool { 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) + log.Warnf("获取可用签名服务器出错:%v, 将使用主签名服务器进行签名", e) atomic.AddInt64(&errorCount, 1) signServer = signServers[0] // 没有获取到时使用第一个 } @@ -311,6 +312,9 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b 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) From 117d980a72cf4a2c02f902da3d5fd5146862f556 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Tue, 22 Aug 2023 11:11:34 +0800 Subject: [PATCH 23/28] fix #2390 --- coolq/api.go | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/coolq/api.go b/coolq/api.go index 1f18ad1f0..7c9e47d64 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -1388,33 +1388,42 @@ func (bot *CQBot) CQGetGroupHonorInfo(groupID int64, t string) global.MSG { } } msg["talkative_list"] = convertMem(honor.TalkativeList) + } else { + log.Infof("获取群龙王出错:%v", err) } } if t == "performer" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Performer); err == nil { msg["performer_list"] = convertMem(honor.ActorList) + } else { + log.Infof("获取群聊之火出错:%v", err) } } if t == "legend" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Legend); err == nil { msg["legend_list"] = convertMem(honor.LegendList) + } else { + log.Infof("获取群聊炽焰出错:%v", err) } } if t == "strong_newbie" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.StrongNewbie); err == nil { msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList) + } else { + log.Infof("获取冒尖小春笋出错:%v", err) } } if t == "emotion" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Emotion); err == nil { msg["emotion_list"] = convertMem(honor.EmotionList) + } else { + log.Infof("获取快乐之源出错:%v", err) } } - return OK(msg) } @@ -1689,26 +1698,8 @@ func (bot *CQBot) CQGetMessage(messageID int32) global.MSG { switch o := msg.(type) { case *db.StoredGroupMessage: m["group_id"] = 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: - 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) From 72723fcc0a8068bf5aadd0abff2413786d3c71ea Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Wed, 23 Aug 2023 16:31:10 +0800 Subject: [PATCH 24/28] resolve requested changes in #2389 --- cmd/gocq/main.go | 4 +- cmd/gocq/qsign.go | 162 ++++++++++++++++++++++------------ internal/download/download.go | 5 +- 3 files changed, 110 insertions(+), 61 deletions(-) diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index 0cc095c10..45e40cc8e 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -166,11 +166,13 @@ func LoginInteract() { if len(base.Account.SignServers) > 5 { log.Warn("签名服务器数量配置过多,推荐不超过 5 个") } - signServer, err := GetAvaliableSignServer() // 获取可用签名服务器 + initSignServersConfig() + signServer, err := getAvaliableSignServer() // 获取可用签名服务器 if err != nil { log.Warn(err) } if len(signServer.URL) > 1 { + log.Infof("使用签名服务器:%v", signServer.URL) go signStartRefreshToken(base.Account.RefreshInterval) // 定时刷新 token wrapper.DandelionEnergy = energy wrapper.FekitGetSign = sign diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index a3ab126bd..295291b3f 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -24,98 +24,143 @@ import ( "github.com/Mrs4s/go-cqhttp/modules/config" ) +type signServers struct { + servers []config.SignServer + lock sync.RWMutex +} + +func (s *signServers) getServers() []config.SignServer { + s.lock.RLock() + defer s.lock.RUnlock() + return s.servers +} + +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 signServers []config.SignServer -var checkLock sync.Mutex +var allSignServers *signServers // 当前签名服务器 -var currentSignServer config.SignServer -var currentOK atomic.Bool - -var errorCount = int64(0) +var curSignServer *currentSignServer +var errorCount = uintptr(0) +var checkLock sync.Mutex -// GetAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 -func GetAvaliableSignServer() (config.SignServer, error) { +func initSignServersConfig() { if len(base.SignServers) == 0 { - return config.SignServer{}, errors.New("no configured sign-server") + log.Warn("no configured sign-server") + return } - if currentOK.Load() { - return currentSignServer, nil + allSignServers = &signServers{ + servers: base.SignServers, + } + curSignServer = ¤tSignServer{ + server: &allSignServers.getServers()[0], + ok: true, + } +} + +// getAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 +func getAvaliableSignServer() (config.SignServer, error) { + if curSignServer.isOK() { + return *curSignServer.getServer(), nil } maxCount := base.Account.MaxCheckCount - signServers = base.SignServers - if maxCount == 0 && atomic.LoadInt64(&errorCount) >= 3 { + if maxCount == 0 && atomic.LoadUintptr(&errorCount) >= 3 { log.Warn("已连续 3 次获取不到可用签名服务器,将固定使用主签名服务器") - currentSignServer = signServers[0] - currentOK.Store(true) - return currentSignServer, nil + curSignServer.setServer(&allSignServers.getServers()[0], true) + return *curSignServer.getServer(), nil } - if maxCount > 0 && atomic.LoadInt64(&errorCount) >= int64(maxCount) { + if maxCount > 0 && int(atomic.LoadUintptr(&errorCount)) >= maxCount { log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) } - if len(currentSignServer.URL) > 1 { - log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", currentSignServer.URL) + cs := curSignServer.getServer() + if len(cs.URL) > 1 { + log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", cs.URL) } + allServers := allSignServers.getServers() if checkLock.TryLock() { defer checkLock.Unlock() - if base.Account.SyncCheckServers { - for i, server := range signServers { - log.Infof("检查签名服务器:%v (%v/%v)", server.URL, i+1, len(signServers)) + if base.Account.SyncCheckServers { // 顺序检查 + for i, server := range allServers { + log.Infof("检查签名服务器:%v (%v/%v)", server.URL, i+1, len(allServers)) if len(server.URL) < 4 { continue } if isServerAvaliable(server.URL) { - errorCount = 0 - currentSignServer = server - currentOK.Store(true) + 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 currentSignServer, nil + return server, nil } } return config.SignServer{}, errors.New("no avaliable sign-server") } - result := make(chan config.SignServer) - allDone := make(chan bool) - i := int32(0) + result := make(chan *config.SignServer) + allDone := make(chan bool) // 标记检查是否全部完成 + t := int32(0) log.Infof("正在检查各签名服务是否可用... (最多等待 %vs)", base.SignServerTimeout) - for _, server := range signServers { - go func(s config.SignServer, r chan config.SignServer) { - atomic.AddInt32(&i, 1) - defer func(count int) { - if len(signServers) == count { - allDone <- true + for i, server := range allServers { + go func(idx int, s *config.SignServer, r chan *config.SignServer) { + defer func(count int32) { // 记录完成任务数 + if len(allServers) == int(count) { // 已经全部检查完毕 + allDone <- true // 终止,不用等待超时再返回 } - }(int(i)) + }(atomic.AddInt32(&t, 1)) + if len(s.URL) < 4 { return } - if s.URL != signServers[0].URL { // 主服务器优先检查 + if s.URL != allServers[0].URL { // 主服务器优先 3s 检查 time.Sleep(3 * time.Second) } if isServerAvaliable(s.URL) { - errorCount = 0 + atomic.StoreUintptr(&errorCount, 0) r <- s log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) } - }(server, result) + }(i, &server, result) } select { case res := <-result: - currentSignServer = res - currentOK.Store(true) + curSignServer.setServer(res, true) if base.Account.AutoRegister { signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) } - return currentSignServer, nil - case <-allDone: - return config.SignServer{}, errors.New("no avaliable sign-server") + return *res, nil case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): errMsg := fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout) return config.SignServer{}, errors.New(errMsg) + case <-allDone: + return config.SignServer{}, errors.New("no avaliable sign-server") } } return config.SignServer{}, errors.New("checking sign-servers") @@ -140,11 +185,11 @@ func isServerAvaliable(signServer string) bool { return: signServer, response, error */ func requestSignServer(method string, url string, headers map[string]string, body io.Reader) (string, []byte, error) { - signServer, e := GetAvaliableSignServer() + signServer, e := getAvaliableSignServer() if e != nil && len(signServer.URL) <= 1 { // 没有可用的 log.Warnf("获取可用签名服务器出错:%v, 将使用主签名服务器进行签名", e) - atomic.AddInt64(&errorCount, 1) - signServer = signServers[0] // 没有获取到时使用第一个 + atomic.AddUintptr(&errorCount, 1) + signServer = allSignServers.getServers()[0] // 没有获取到时使用第一个 } if !strings.HasPrefix(url, signServer.URL) { url = strings.TrimSuffix(signServer.URL, "/") + "/" + strings.TrimPrefix(url, "/") @@ -164,7 +209,7 @@ func requestSignServer(method string, url string, headers map[string]string, bod }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second) resp, err := req.Bytes() if err != nil { - currentOK.Store(false) + curSignServer.setServer(nil, false) // 标记为不可用 } return signServer.URL, resp, err } @@ -302,9 +347,10 @@ 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, currentSignServer.URL) + log.Warnf("获取sso sign时出现错误: %v. server: %v", err, cs.URL) } if i > 0 { break @@ -322,7 +368,7 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b log.Warnln(err) // 实例真的丢失时则必出错,或许应该不 return , 以重新获取本次签名 // return nil, nil, nil, err } - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, currentSignServer.Key) + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, cs.Key) } continue } @@ -331,7 +377,7 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b if registerLock.TryLock() { defer registerLock.Unlock() if err := signRefreshToken(uin); err != nil { - log.Warnf("刷新 token 出现错误: %v. server: %v", err, currentSignServer.URL) + log.Warnf("刷新 token 出现错误: %v. server: %v", err, cs.URL) } else { log.Info("刷新 token 成功") } @@ -346,7 +392,7 @@ func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []b } rule := base.Account.RuleChangeSignServer if (len(sign) == 0 && rule >= 1) || (len(token) == 0 && rule >= 2) { - currentOK.Store(false) + curSignServer.setServer(nil, false) } return sign, extra, token, err } @@ -361,7 +407,7 @@ func signServerDestroy(uin string) error { } signServer, resp, err := requestSignServer( http.MethodGet, - "destroy"+fmt.Sprintf("?uin=%v&key=%v", uin, currentSignServer.Key), + "destroy"+fmt.Sprintf("?uin=%v&key=%v", uin, curSignServer.getServer().Key), nil, nil, ) if err != nil || gjson.GetBytes(resp, "code").Int() != 0 { @@ -399,14 +445,14 @@ func signStartRefreshToken(interval int64) { qqstr := strconv.FormatInt(base.Account.Uin, 10) defer t.Stop() for range t.C { - if currentSignServer != signServers[0] && isServerAvaliable(signServers[0].URL) { - currentSignServer = signServers[0] - currentOK.Store(true) - log.Infof("主签名服务器可用,已切换至主签名服务器 %v", currentSignServer.URL) + cs, master := curSignServer.getServer(), allSignServers.getServers()[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, currentSignServer.URL) + log.Warnf("刷新 token 出现错误: %v. server: %v", err, cs.URL) } } } diff --git a/internal/download/download.go b/internal/download/download.go index 2e94ccb80..c7e706fe2 100644 --- a/internal/download/download.go +++ b/internal/download/download.go @@ -15,6 +15,7 @@ import ( "sync" "time" + "github.com/RomiChan/syncx" "github.com/pkg/errors" "github.com/tidwall/gjson" @@ -22,7 +23,7 @@ import ( ) var client = newClient(time.Second * 15) -var clients sync.Map +var clients syncx.Map[time.Duration, *http.Client] var clienth2 = &http.Client{ Transport: &http.Transport{ @@ -64,7 +65,7 @@ const UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 // WithTimeout get a download instance with timeout t func (r Request) WithTimeout(t time.Duration) *Request { if c, ok := clients.Load(t); ok { - r.custcli = c.(*http.Client) + r.custcli = c } else { c := newClient(t) clients.Store(t, c) From ba7fe22647dabf42b9708f732f30cd50cb3bee28 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Wed, 23 Aug 2023 16:31:58 +0800 Subject: [PATCH 25/28] update dependency --- go.mod | 2 +- go.sum | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e2760cf56..95c58d59f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/FloatTech/sqlite v1.6.3 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/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/fumiama/go-base16384 v1.7.0 diff --git a/go.sum b/go.sum index 0f9f21907..3d4873550 100644 --- a/go.sum +++ b/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/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-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/go.mod h1:2Ie+hdBFQpQFDHfeklgxoFmQRCE7O+KwFpISeXq7OwA= 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/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-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/go.mod h1:ofpv77Td5qQO6R1dmDd3oNt8TZdRo+l5gYAMxopRyS0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From f7a8e8e96a90ff4aeecb6b521430e9a8b72f72a1 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Wed, 23 Aug 2023 16:36:02 +0800 Subject: [PATCH 26/28] fix lint error 'idx is unused' --- cmd/gocq/qsign.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 295291b3f..bbaaa42b4 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -128,8 +128,8 @@ func getAvaliableSignServer() (config.SignServer, error) { allDone := make(chan bool) // 标记检查是否全部完成 t := int32(0) log.Infof("正在检查各签名服务是否可用... (最多等待 %vs)", base.SignServerTimeout) - for i, server := range allServers { - go func(idx int, s *config.SignServer, r chan *config.SignServer) { + for _, server := range allServers { + go func(s *config.SignServer, r chan *config.SignServer) { defer func(count int32) { // 记录完成任务数 if len(allServers) == int(count) { // 已经全部检查完毕 allDone <- true // 终止,不用等待超时再返回 @@ -147,7 +147,7 @@ func getAvaliableSignServer() (config.SignServer, error) { r <- s log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) } - }(i, &server, result) + }(&server, result) } select { case res := <-result: From 9d37deccf7b497ef7279956096922c87b8bfb370 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Thu, 24 Aug 2023 20:48:23 +0800 Subject: [PATCH 27/28] refactor: extract sync check and async check logic --- cmd/gocq/qsign.go | 123 +++++++++++++++++++++++++--------------------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index bbaaa42b4..0f5997eed 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -106,62 +106,9 @@ func getAvaliableSignServer() (config.SignServer, error) { if checkLock.TryLock() { defer checkLock.Unlock() if base.Account.SyncCheckServers { // 顺序检查 - for i, server := range allServers { - log.Infof("检查签名服务器:%v (%v/%v)", server.URL, i+1, len(allServers)) - 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") - } - result := make(chan *config.SignServer) - allDone := make(chan bool) // 标记检查是否全部完成 - t := int32(0) - log.Infof("正在检查各签名服务是否可用... (最多等待 %vs)", base.SignServerTimeout) - for _, server := range allServers { - go func(s *config.SignServer, r chan *config.SignServer) { - defer func(count int32) { // 记录完成任务数 - if len(allServers) == int(count) { // 已经全部检查完毕 - allDone <- true // 终止,不用等待超时再返回 - } - }(atomic.AddInt32(&t, 1)) - - if len(s.URL) < 4 { - return - } - if s.URL != allServers[0].URL { // 主服务器优先 3s 检查 - time.Sleep(3 * time.Second) - } - if isServerAvaliable(s.URL) { - atomic.StoreUintptr(&errorCount, 0) - r <- s - log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) - } - }(&server, result) - } - select { - case res := <-result: - curSignServer.setServer(res, true) - if base.Account.AutoRegister { - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) - } - return *res, nil - case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): - errMsg := fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout) - return config.SignServer{}, errors.New(errMsg) - case <-allDone: - return config.SignServer{}, errors.New("no avaliable sign-server") + return syncCheckServer(allServers) } + return asyncCheckServer(allServers) } return config.SignServer{}, errors.New("checking sign-servers") } @@ -178,6 +125,70 @@ func isServerAvaliable(signServer string) bool { 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") +} + +// asyncCheckServer 异步检查所有签名服务器直到找到可用的 +func asyncCheckServer(servers []config.SignServer) (config.SignServer, error) { + avaliableServer := make(chan *config.SignServer) + allDone := make(chan struct{}) // 是否全部完成 + var finishedCount atomic.Uintptr // 记录完成任务数 + finishedCount.Store(0) + log.Infof("正在检查各签名服务是否可用... (最多等待 %vs)", base.SignServerTimeout) + for _, server := range servers { + go func(s *config.SignServer) { + defer func(count uintptr) { + if len(servers) == int(count) { // 已经全部检查完毕 + allDone <- struct{}{} + } + }(finishedCount.Add(1)) // 完成任务数 + 1 + + if len(s.URL) < 4 { + return + } + if s.URL != servers[0].URL { // 主服务器优先 3s 检查 + time.Sleep(3 * time.Second) + } + if isServerAvaliable(s.URL) { + atomic.StoreUintptr(&errorCount, 0) + avaliableServer <- s + log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) + } + }(&server) + } + select { + case res := <-avaliableServer: + curSignServer.setServer(res, true) + if base.Account.AutoRegister { + signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) + } + return *res, nil + case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): + errMsg := fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout) + return config.SignServer{}, errors.New(errMsg) + case <-allDone: + return config.SignServer{}, errors.New("no avaliable sign-server") + } +} + /* 请求签名服务器 @@ -247,7 +258,7 @@ func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t strin tail = len(buffStr) endl = "." } - log.Infof("submit %v: uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffStr[:tail], 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, From c74430fe7a1fc558026fb730f2efd191635222b5 Mon Sep 17 00:00:00 2001 From: spring23 <1628420979@qq.com> Date: Fri, 25 Aug 2023 07:22:44 +0800 Subject: [PATCH 28/28] delete async check sign-server --- cmd/gocq/main.go | 3 -- cmd/gocq/qsign.go | 84 +++++-------------------------- modules/config/config.go | 1 - modules/config/default_config.yml | 7 +-- 4 files changed, 14 insertions(+), 81 deletions(-) diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index 45e40cc8e..d0cf1738d 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -163,9 +163,6 @@ func LoginInteract() { log.Fatalf("加载设备信息失败: %v", err) } } - if len(base.Account.SignServers) > 5 { - log.Warn("签名服务器数量配置过多,推荐不超过 5 个") - } initSignServersConfig() signServer, err := getAvaliableSignServer() // 获取可用签名服务器 if err != nil { diff --git a/cmd/gocq/qsign.go b/cmd/gocq/qsign.go index 0f5997eed..082217e86 100644 --- a/cmd/gocq/qsign.go +++ b/cmd/gocq/qsign.go @@ -24,17 +24,6 @@ import ( "github.com/Mrs4s/go-cqhttp/modules/config" ) -type signServers struct { - servers []config.SignServer - lock sync.RWMutex -} - -func (s *signServers) getServers() []config.SignServer { - s.lock.RLock() - defer s.lock.RUnlock() - return s.servers -} - type currentSignServer struct { server *config.SignServer ok bool @@ -62,9 +51,6 @@ func (c *currentSignServer) setServer(server *config.SignServer, status bool) { c.ok = status } -// 配置的所有签名服务器 -var allSignServers *signServers - // 当前签名服务器 var curSignServer *currentSignServer var errorCount = uintptr(0) @@ -75,11 +61,12 @@ func initSignServersConfig() { log.Warn("no configured sign-server") return } - allSignServers = &signServers{ - servers: base.SignServers, + if len(base.SignServers) > 5 { + base.SignServers = base.SignServers[:5] + log.Warn("签名服务器数量配置过多,可能导致不可用时检查时间过久,已取前 5 个") } curSignServer = ¤tSignServer{ - server: &allSignServers.getServers()[0], + server: &base.SignServers[0], ok: true, } } @@ -92,23 +79,19 @@ func getAvaliableSignServer() (config.SignServer, error) { maxCount := base.Account.MaxCheckCount if maxCount == 0 && atomic.LoadUintptr(&errorCount) >= 3 { log.Warn("已连续 3 次获取不到可用签名服务器,将固定使用主签名服务器") - curSignServer.setServer(&allSignServers.getServers()[0], true) + curSignServer.setServer(&base.SignServers[0], true) return *curSignServer.getServer(), nil } if maxCount > 0 && int(atomic.LoadUintptr(&errorCount)) >= maxCount { log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) } - cs := curSignServer.getServer() - if len(cs.URL) > 1 { - log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", cs.URL) - } - allServers := allSignServers.getServers() if checkLock.TryLock() { defer checkLock.Unlock() - if base.Account.SyncCheckServers { // 顺序检查 - return syncCheckServer(allServers) + cs := curSignServer.getServer() + if len(cs.URL) > 1 { + log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", cs.URL) } - return asyncCheckServer(allServers) + return syncCheckServer(base.SignServers) } return config.SignServer{}, errors.New("checking sign-servers") } @@ -117,7 +100,7 @@ func isServerAvaliable(signServer string) bool { resp, err := download.Request{ Method: http.MethodGet, URL: signServer, - }.WithTimeout(5 * time.Second).Bytes() + }.WithTimeout(3 * time.Second).Bytes() if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { return true } @@ -146,49 +129,6 @@ func syncCheckServer(servers []config.SignServer) (config.SignServer, error) { return config.SignServer{}, errors.New("no avaliable sign-server") } -// asyncCheckServer 异步检查所有签名服务器直到找到可用的 -func asyncCheckServer(servers []config.SignServer) (config.SignServer, error) { - avaliableServer := make(chan *config.SignServer) - allDone := make(chan struct{}) // 是否全部完成 - var finishedCount atomic.Uintptr // 记录完成任务数 - finishedCount.Store(0) - log.Infof("正在检查各签名服务是否可用... (最多等待 %vs)", base.SignServerTimeout) - for _, server := range servers { - go func(s *config.SignServer) { - defer func(count uintptr) { - if len(servers) == int(count) { // 已经全部检查完毕 - allDone <- struct{}{} - } - }(finishedCount.Add(1)) // 完成任务数 + 1 - - if len(s.URL) < 4 { - return - } - if s.URL != servers[0].URL { // 主服务器优先 3s 检查 - time.Sleep(3 * time.Second) - } - if isServerAvaliable(s.URL) { - atomic.StoreUintptr(&errorCount, 0) - avaliableServer <- s - log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", s.URL, s.Key, s.Authorization) - } - }(&server) - } - select { - case res := <-avaliableServer: - curSignServer.setServer(res, true) - if base.Account.AutoRegister { - signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, res.Key) - } - return *res, nil - case <-time.After(time.Duration(base.SignServerTimeout) * time.Second): - errMsg := fmt.Sprintf("no avaliable sign-server, timeout=%v", base.SignServerTimeout) - return config.SignServer{}, errors.New(errMsg) - case <-allDone: - return config.SignServer{}, errors.New("no avaliable sign-server") - } -} - /* 请求签名服务器 @@ -200,7 +140,7 @@ func requestSignServer(method string, url string, headers map[string]string, bod if e != nil && len(signServer.URL) <= 1 { // 没有可用的 log.Warnf("获取可用签名服务器出错:%v, 将使用主签名服务器进行签名", e) atomic.AddUintptr(&errorCount, 1) - signServer = allSignServers.getServers()[0] // 没有获取到时使用第一个 + signServer = base.SignServers[0] // 没有获取到时使用第一个 } if !strings.HasPrefix(url, signServer.URL) { url = strings.TrimSuffix(signServer.URL, "/") + "/" + strings.TrimPrefix(url, "/") @@ -456,7 +396,7 @@ func signStartRefreshToken(interval int64) { qqstr := strconv.FormatInt(base.Account.Uin, 10) defer t.Stop() for range t.C { - cs, master := curSignServer.getServer(), allSignServers.getServers()[0] + cs, master := curSignServer.getServer(), base.SignServers[0] if cs.URL != master.URL && isServerAvaliable(master.URL) { curSignServer.setServer(&master, true) log.Infof("主签名服务器可用,已切换至主签名服务器 %v", cs.URL) diff --git a/modules/config/config.go b/modules/config/config.go index a1dc1feb1..2224ec1d6 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -36,7 +36,6 @@ type Account struct { UseSSOAddress bool `yaml:"use-sso-address"` AllowTempSession bool `yaml:"allow-temp-session"` SignServers []SignServer `yaml:"sign-servers"` - SyncCheckServers bool `yaml:"sync-check-servers"` RuleChangeSignServer int `yaml:"rule-change-sign-server"` MaxCheckCount int `yaml:"max-check-count"` SignServerTimeout int `yaml:"sign-server-timeout"` diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index 8367f1c3a..66c25e6f6 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -19,7 +19,7 @@ account: # 账号相关 # 数据包的签名服务器列表,第一个作为主签名服务器,后续作为备用 # 兼容 https://github.com/fuqiuluo/unidbg-fetch-qsign # 如果遇到 登录 45 错误, 或者发送信息风控的话需要填入一个或多个服务器 - # 不建议设置过多,最好不超过4个 + # 不建议设置过多,设置主备各一个即可,超过 5 个只会取前五个 # 示例: # sign-servers: # - url: 'http://127.0.0.1:8080' # 本地签名服务器 @@ -39,11 +39,8 @@ account: # 账号相关 key: '114514' authorization: '-' - # 是否以同步顺序方式查找签名服务器,找不到时可能会比较慢,默认并发查找 - sync-check-servers: false - # 判断签名服务不可用(需要切换)的额外规则 - # 0: 不设置 + # 0: 不设置 (此时仅在请求无法返回结果时判定为不可用) # 1: 在获取到的 sign 为空 (若选此建议关闭 auto-register,一般为实例未注册但是请求签名的情况) # 2: 在获取到的 sign 或 token 为空(若选此建议关闭 auto-refresh-token ) rule-change-sign-server: 1