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  }