github.com/Mrs4s/go-cqhttp@v1.2.0/cmd/gocq/qsign.go (about) 1 package gocq 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "net/http" 9 "strconv" 10 "strings" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "github.com/pkg/errors" 16 log "github.com/sirupsen/logrus" 17 "github.com/tidwall/gjson" 18 19 "github.com/Mrs4s/MiraiGo/utils" 20 21 "github.com/Mrs4s/go-cqhttp/global" 22 "github.com/Mrs4s/go-cqhttp/internal/base" 23 "github.com/Mrs4s/go-cqhttp/internal/download" 24 "github.com/Mrs4s/go-cqhttp/modules/config" 25 ) 26 27 type currentSignServer atomic.Pointer[config.SignServer] 28 29 func (c *currentSignServer) get() *config.SignServer { 30 if len(base.SignServers) == 1 { 31 // 只配置了一个签名服务时不检查以及切换, 在get阶段返回,防止返回nil导致其他bug(可能) 32 return &base.SignServers[0] 33 } 34 return (*atomic.Pointer[config.SignServer])(c).Load() 35 } 36 37 func (c *currentSignServer) set(server *config.SignServer) { 38 (*atomic.Pointer[config.SignServer])(c).Store(server) 39 } 40 41 // 当前签名服务器 42 var ss currentSignServer 43 44 // 失败计数 45 type errconut atomic.Uintptr 46 47 func (ec *errconut) hasOver(count uintptr) bool { 48 return (*atomic.Uintptr)(ec).Load() > count 49 } 50 51 func (ec *errconut) inc() { 52 (*atomic.Uintptr)(ec).Add(1) 53 } 54 55 var errn errconut 56 57 // getAvaliableSignServer 获取可用的签名服务器,没有则返回空和相应错误 58 func getAvaliableSignServer() (*config.SignServer, error) { 59 cs := ss.get() 60 if cs != nil { 61 return cs, nil 62 } 63 if len(base.SignServers) == 0 { 64 return nil, errors.New("no sign server configured") 65 } 66 maxCount := base.Account.MaxCheckCount 67 if maxCount == 0 { 68 if errn.hasOver(3) { 69 log.Warn("已连续 3 次获取不到可用签名服务器,将固定使用主签名服务器") 70 ss.set(&base.SignServers[0]) 71 return ss.get(), nil 72 } 73 } else if errn.hasOver(uintptr(maxCount)) { 74 log.Fatalf("获取可用签名服务器失败次数超过 %v 次, 正在离线", maxCount) 75 } 76 if cs != nil && len(cs.URL) > 0 { 77 log.Warnf("当前签名服务器 %v 不可用,正在查找可用服务器", cs.URL) 78 } 79 cs = asyncCheckServer(base.SignServers) 80 if cs == nil { 81 return nil, errors.New("no usable sign server") 82 } 83 return cs, nil 84 } 85 86 func isServerAvaliable(signServer string) bool { 87 resp, err := download.Request{ 88 Method: http.MethodGet, 89 URL: signServer, 90 }.WithTimeout(3 * time.Second).Bytes() 91 if err == nil && gjson.GetBytes(resp, "code").Int() == 0 { 92 return true 93 } 94 log.Warnf("签名服务器 %v 可能不可用,请求出现错误:%v", signServer, err) 95 return false 96 } 97 98 // asyncCheckServer 按同步顺序检查所有签名服务器直到找到可用的 99 func asyncCheckServer(servers []config.SignServer) *config.SignServer { 100 doRegister := sync.Once{} 101 wg := sync.WaitGroup{} 102 wg.Add(len(servers)) 103 for i, s := range servers { 104 go func(i int, server config.SignServer) { 105 defer wg.Done() 106 log.Infof("检查签名服务器:%v (%v/%v)", server.URL, i+1, len(servers)) 107 if len(server.URL) < 4 { 108 return 109 } 110 if isServerAvaliable(server.URL) { 111 doRegister.Do(func() { 112 ss.set(&server) 113 log.Infof("使用签名服务器 url=%v, key=%v, auth=%v", server.URL, server.Key, server.Authorization) 114 if base.Account.AutoRegister { 115 // 若配置了自动注册实例则在切换后注册实例,否则不需要注册,签名时由qsign自动注册 116 signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, server.Key) 117 } 118 }) 119 } 120 }(i, s) 121 } 122 wg.Wait() 123 return ss.get() 124 } 125 126 /* 127 请求签名服务器 128 129 url: api + params 组合的字符串,无须包含签名服务器地址 130 return: signServer, response, error 131 */ 132 func requestSignServer(method string, url string, headers map[string]string, body io.Reader) (string, []byte, error) { 133 signServer, e := getAvaliableSignServer() 134 if e != nil && len(signServer.URL) == 0 { // 没有可用的 135 log.Warnf("获取可用签名服务器出错:%v, 将使用主签名服务器进行签名", e) 136 errn.inc() 137 signServer = &base.SignServers[0] // 没有获取到时使用第一个 138 } 139 if !strings.HasPrefix(url, signServer.URL) { 140 url = strings.TrimSuffix(signServer.URL, "/") + "/" + strings.TrimPrefix(url, "/") 141 } 142 if headers == nil { 143 headers = map[string]string{} 144 } 145 auth := signServer.Authorization 146 if auth != "-" && auth != "" { 147 headers["Authorization"] = auth 148 } 149 req := download.Request{ 150 Method: method, 151 Header: headers, 152 URL: url, 153 Body: body, 154 }.WithTimeout(time.Duration(base.SignServerTimeout) * time.Second) 155 resp, err := req.Bytes() 156 if err != nil { 157 ss.set(nil) // 标记为不可用 158 } 159 return signServer.URL, resp, err 160 } 161 162 func energy(uin uint64, id string, _ string, salt []byte) ([]byte, error) { 163 url := "custom_energy" + fmt.Sprintf("?data=%v&salt=%v&uin=%v&android_id=%v&guid=%v", 164 id, hex.EncodeToString(salt), uin, utils.B2S(device.AndroidId), hex.EncodeToString(device.Guid)) 165 if base.IsBelow110 { 166 url = "custom_energy" + fmt.Sprintf("?data=%v&salt=%v", id, hex.EncodeToString(salt)) 167 } 168 signServer, response, err := requestSignServer(http.MethodGet, url, nil, nil) 169 if err != nil { 170 log.Warnf("获取T544 sign时出现错误: %v. server: %v", err, signServer) 171 return nil, err 172 } 173 data, err := hex.DecodeString(gjson.GetBytes(response, "data").String()) 174 if err != nil { 175 log.Warnf("获取T544 sign时出现错误: %v (data: %v)", err, gjson.GetBytes(response, "data").String()) 176 return nil, err 177 } 178 if len(data) == 0 { 179 log.Warnf("获取T544 sign时出现错误: %v.", "data is empty") 180 return nil, errors.New("data is empty") 181 } 182 return data, nil 183 } 184 185 // signSubmit 186 // 提交回调 buffer 187 func signSubmit(uin string, cmd string, callbackID int64, buffer []byte, t string) { 188 buffStr := hex.EncodeToString(buffer) 189 if base.Debug { 190 tail := 64 191 endl := "..." 192 if len(buffStr) < tail { 193 tail = len(buffStr) 194 endl = "." 195 } 196 log.Debugf("submit (%v): uin=%v, cmd=%v, callbackID=%v, buffer=%v%s", t, uin, cmd, callbackID, buffStr[:tail], endl) 197 } 198 199 signServer, _, err := requestSignServer( 200 http.MethodGet, 201 "submit"+fmt.Sprintf("?uin=%v&cmd=%v&callback_id=%v&buffer=%v", 202 uin, cmd, callbackID, buffStr), 203 nil, nil, 204 ) 205 if err != nil { 206 log.Warnf("提交 callback 时出现错误: %v. server: %v", err, signServer) 207 } 208 } 209 210 // signCallback 211 // 刷新 token 和签名的回调 212 func signCallback(uin string, results []gjson.Result, t string) { 213 for { // 等待至在线 214 if cli.Online.Load() { 215 break 216 } 217 time.Sleep(1 * time.Second) 218 } 219 for _, result := range results { 220 cmd := result.Get("cmd").String() 221 callbackID := result.Get("callbackId").Int() 222 body, _ := hex.DecodeString(result.Get("body").String()) 223 ret, err := cli.SendSsoPacket(cmd, body) 224 if err != nil || len(ret) == 0 { 225 log.Warnf("Callback error: %v, or response data is empty", err) 226 continue // 发送 SsoPacket 出错或返回数据为空时跳过 227 } 228 signSubmit(uin, cmd, callbackID, ret, t) 229 } 230 } 231 232 func signRequset(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) { 233 headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"} 234 _, response, err := requestSignServer( 235 http.MethodPost, 236 "sign", 237 headers, 238 bytes.NewReader([]byte(fmt.Sprintf("uin=%v&qua=%s&cmd=%s&seq=%v&buffer=%v&android_id=%v&guid=%v", 239 uin, qua, cmd, seq, hex.EncodeToString(buff), utils.B2S(device.AndroidId), hex.EncodeToString(device.Guid)))), 240 ) 241 if err != nil { 242 return nil, nil, nil, err 243 } 244 sign, _ = hex.DecodeString(gjson.GetBytes(response, "data.sign").String()) 245 extra, _ = hex.DecodeString(gjson.GetBytes(response, "data.extra").String()) 246 token, _ = hex.DecodeString(gjson.GetBytes(response, "data.token").String()) 247 if !base.IsBelow110 { 248 go signCallback(uin, gjson.GetBytes(response, "data.requestCallback").Array(), "sign") 249 } 250 return sign, extra, token, nil 251 } 252 253 var registerLock sync.Mutex 254 255 func signRegister(uin int64, androidID, guid []byte, qimei36, key string) { 256 if base.IsBelow110 { 257 log.Warn("签名服务器版本低于1.1.0, 跳过实例注册") 258 return 259 } 260 signServer, resp, err := requestSignServer( 261 http.MethodGet, 262 "register"+fmt.Sprintf("?uin=%v&android_id=%v&guid=%v&qimei36=%v&key=%s", 263 uin, utils.B2S(androidID), hex.EncodeToString(guid), qimei36, key), 264 nil, nil, 265 ) 266 if err != nil { 267 log.Warnf("注册QQ实例时出现错误: %v. server: %v", err, signServer) 268 return 269 } 270 msg := gjson.GetBytes(resp, "msg") 271 if gjson.GetBytes(resp, "code").Int() != 0 { 272 log.Warnf("注册QQ实例时出现错误: %v. server: %v", msg, signServer) 273 return 274 } 275 log.Infof("注册QQ实例 %v 成功: %v", uin, msg) 276 } 277 278 func signRefreshToken(uin string) error { 279 log.Info("正在刷新 token") 280 _, resp, err := requestSignServer( 281 http.MethodGet, 282 "request_token?uin="+uin, 283 nil, nil, 284 ) 285 if err != nil { 286 return err 287 } 288 msg := gjson.GetBytes(resp, "msg") 289 code := gjson.GetBytes(resp, "code") 290 if code.Int() != 0 { 291 return errors.New("code=" + code.String() + ", msg: " + msg.String()) 292 } 293 go signCallback(uin, gjson.GetBytes(resp, "data").Array(), "request token") 294 return nil 295 } 296 297 var missTokenCount = uint64(0) 298 var lastToken = "" 299 300 func sign(seq uint64, uin string, cmd string, qua string, buff []byte) (sign []byte, extra []byte, token []byte, err error) { 301 i := 0 302 for { 303 sign, extra, token, err = signRequset(seq, uin, cmd, qua, buff) 304 cs := ss.get() 305 if cs == nil { 306 // 最好在请求后判断,否则若被设置为nil后不会再请求签名, 307 // 导致在下一次有请求签名服务操作之前,ss无法更新 308 err = errors.New("nil signserver") 309 log.Warn("nil sign-server") // 返回的err并不会log出来,加条日志 310 return 311 } 312 if err != nil { 313 log.Warnf("获取sso sign时出现错误: %v. server: %v", err, cs.URL) 314 } 315 if i > 0 { 316 break 317 } 318 i++ 319 if (!base.IsBelow110) && base.Account.AutoRegister && err == nil && len(sign) == 0 { 320 if registerLock.TryLock() { // 避免并发时多处同时销毁并重新注册 321 log.Debugf("请求签名:cmd=%v, qua=%v, buff=%v", seq, cmd, hex.EncodeToString(buff)) 322 log.Debugf("返回结果:sign=%v, extra=%v, token=%v", 323 hex.EncodeToString(sign), hex.EncodeToString(extra), hex.EncodeToString(token)) 324 log.Warn("获取签名为空,实例可能丢失,正在尝试重新注册") 325 defer registerLock.Unlock() 326 err := signServerDestroy(uin) 327 if err != nil { 328 log.Warnln(err) // 实例真的丢失时则必出错,或许应该不 return , 以重新获取本次签名 329 // return nil, nil, nil, err 330 } 331 signRegister(base.Account.Uin, device.AndroidId, device.Guid, device.QImei36, cs.Key) 332 } 333 continue 334 } 335 if (!base.IsBelow110) && base.Account.AutoRefreshToken && len(token) == 0 { 336 log.Warnf("token 已过期, 总丢失 token 次数为 %v", atomic.AddUint64(&missTokenCount, 1)) 337 if registerLock.TryLock() { 338 defer registerLock.Unlock() 339 if err := signRefreshToken(uin); err != nil { 340 log.Warnf("刷新 token 出现错误: %v. server: %v", err, cs.URL) 341 } else { 342 log.Info("刷新 token 成功") 343 } 344 } 345 continue 346 } 347 break 348 } 349 if tokenString := hex.EncodeToString(token); lastToken != tokenString { 350 log.Infof("token 已更新:%v -> %v", lastToken, tokenString) 351 lastToken = tokenString 352 } 353 rule := base.Account.RuleChangeSignServer 354 if (len(sign) == 0 && rule >= 1) || (len(token) == 0 && rule >= 2) { 355 ss.set(nil) 356 } 357 return sign, extra, token, err 358 } 359 360 func signServerDestroy(uin string) error { 361 signServer, signVersion, err := signVersion() 362 if err != nil { 363 return errors.Wrapf(err, "获取签名服务版本出现错误, server: %v", signServer) 364 } 365 if global.VersionNameCompare("v"+signVersion, "v1.1.6") { 366 return errors.Errorf("当前签名服务器版本 %v 低于 1.1.6,无法使用 destroy 接口", signVersion) 367 } 368 cs := ss.get() 369 if cs == nil { 370 return errors.New("nil signserver") 371 } 372 signServer, resp, err := requestSignServer( 373 http.MethodGet, 374 "destroy"+fmt.Sprintf("?uin=%v&key=%v", uin, cs.Key), 375 nil, nil, 376 ) 377 if err != nil || gjson.GetBytes(resp, "code").Int() != 0 { 378 return errors.Wrapf(err, "destroy 实例出现错误, server: %v", signServer) 379 } 380 return nil 381 } 382 383 func signVersion() (signServer string, version string, err error) { 384 signServer, resp, err := requestSignServer(http.MethodGet, "", nil, nil) 385 if err != nil { 386 return signServer, "", err 387 } 388 if gjson.GetBytes(resp, "code").Int() == 0 { 389 return signServer, gjson.GetBytes(resp, "data.version").String(), nil 390 } 391 return signServer, "", errors.New("empty version") 392 } 393 394 // 定时刷新 token, interval 为间隔时间(分钟) 395 func signStartRefreshToken(interval int64) { 396 if interval <= 0 { 397 log.Warn("定时刷新 token 已关闭") 398 return 399 } 400 log.Infof("每 %v 分钟将刷新一次签名 token", interval) 401 if interval < 10 { 402 log.Warnf("间隔时间 %v 分钟较短,推荐 30~40 分钟", interval) 403 } 404 if interval > 60 { 405 log.Warn("间隔时间不能超过 60 分钟,已自动设置为 60 分钟") 406 interval = 60 407 } 408 t := time.NewTicker(time.Duration(interval) * time.Minute) 409 qqstr := strconv.FormatInt(base.Account.Uin, 10) 410 defer t.Stop() 411 for range t.C { 412 cs, master := ss.get(), &base.SignServers[0] 413 if (cs == nil || cs.URL != master.URL) && isServerAvaliable(master.URL) { 414 ss.set(master) 415 log.Infof("主签名服务器可用,已切换至主签名服务器 %v", master.URL) 416 } 417 cs = ss.get() 418 if cs == nil { 419 log.Warn("无法获得可用签名服务器,停止 token 定时刷新") 420 return 421 } 422 err := signRefreshToken(qqstr) 423 if err != nil { 424 log.Warnf("刷新 token 出现错误: %v. server: %v", err, cs.URL) 425 } 426 } 427 }