package gui import ( "context" "errors" "fmt" "strconv" "strings" "time" "huya-go/internal/huya" "huya-go/internal/logger" "huya-go/internal/sniper" ) const ( CookieStatusValid = "valid" CookieStatusExpired = "expired" CookieStatusUnknown = "unknown" CookieStatusInvalid = "invalid" // cookieCheckAttempts 对「失效」结果连续确认:线上偶发「账号未绑定!」后下一轮 // 就恢复,直接判死会把好 CK 清掉;与抢购引擎的 sessionLostLimit 保持一致。 cookieCheckAttempts = 3 cookieCheckRetryDelay = 250 * time.Millisecond ) // CookieCheckResult 描述一条 CK 的服务端可用性检查结果。 // Status 取值:valid=可用、expired=失效/不可用、unknown=请求异常待确认、invalid=格式无法解析。 type CookieCheckResult struct { Line int `json:"line"` UID int64 `json:"uid"` Status string `json:"status"` Message string `json:"message,omitempty"` StatusCode int32 `json:"statusCode,omitempty"` CheckedAt int64 `json:"checkedAt"` } // CheckCookies 用 getUserScore 对每条 CK 做真实登录态校验。 // // 不依赖「能解析出 yyuid」这种本地格式判断:CK 里的 uid 还在、但登录态已经过期 // 是最常见的失败原因。getUserScore 必须携带有效身份,因此适合作为可用性探针。 func (a *App) CheckCookies(cfg Config) ([]CookieCheckResult, error) { cfg = normalizeConfig(cfg) lines := splitCookieLines(cfg.CookieText) if len(lines) == 0 { return nil, errors.New("Cookie 不能为空") } if cfg.SID <= 0 { return nil, errors.New("SID 不能为空") } client, closeClient, err := a.newClient(cfg) if err != nil { return nil, err } defer closeClient() accounts := make([]sniper.Account, len(lines)) for i, line := range lines { accounts[i] = sniper.Account{UID: huya.ResolveUID(line.text), Cookie: line.text} } results := make([]CookieCheckResult, len(lines)) a.runConcurrent(accounts, cookieCheckConcurrency(cfg.Concurrency), func(i int, account sniper.Account) { results[i] = checkOneCookie(client, lines[i], account, cfg.SID, cfg.TimeoutSec) }) valid, expired, unknown, invalid := 0, 0, 0, 0 for _, item := range results { switch item.Status { case CookieStatusValid: valid++ case CookieStatusExpired: expired++ case CookieStatusInvalid: invalid++ default: unknown++ } if item.Status == CookieStatusExpired { a.logAccount(categoryApp, logger.LevelWarn, strconv.FormatInt(item.UID, 10), "CK 已失效: %s (status=%d)", item.Message, item.StatusCode) } } a.addLog("info", "CK 检查完成: 总数=%d 可用=%d 失效=%d 检查失败=%d 格式错误=%d", len(results), valid, expired, unknown, invalid) return results, nil } func checkOneCookie(client huya.ActivityClient, line cookieLine, account sniper.Account, sid int32, timeoutSec int) CookieCheckResult { result := CookieCheckResult{ Line: line.lineNo, UID: account.UID, CheckedAt: time.Now().UnixMilli(), } if account.UID <= 0 { result.Status = CookieStatusInvalid result.Message = "无法解析 yyuid/udb_uid" return result } for attempt := 0; attempt < cookieCheckAttempts; attempt++ { ctx, cancel := requestContext(timeoutSec) resp, err := client.QueryUserScore(ctx, account.UID, account.Cookie, sid) cancel() status, message, statusCode := cookieCheckVerdict(resp, err) result.Status = status result.Message = message result.StatusCode = statusCode result.CheckedAt = time.Now().UnixMilli() if status != CookieStatusExpired || attempt == cookieCheckAttempts-1 { return result } time.Sleep(cookieCheckRetryDelay) } return result } // cookieCheckVerdict 只把明确的登录态文案判为失效;限流、网络错误等归为 unknown, // 避免在服务端繁忙时把一批正常 CK 误标成失效。 func cookieCheckVerdict(resp *huya.GetUserScoreResp, err error) (status, message string, statusCode int32) { if err != nil { message = strings.TrimSpace(err.Error()) if errors.Is(err, context.DeadlineExceeded) { message = "检查超时" } if message == "" { message = "请求失败" } return CookieStatusUnknown, message, 0 } if resp == nil { return CookieStatusUnknown, "服务端未返回数据", 0 } msg := strings.TrimSpace(resp.Msg) if isCookieExpiredMessage(msg) { if msg == "" { msg = "登录态已失效" } return CookieStatusExpired, msg, resp.Status } if resp.Status == 200 { return CookieStatusValid, "CK 可用", resp.Status } if msg == "" { msg = fmt.Sprintf("服务端返回 status=%d", resp.Status) } return CookieStatusUnknown, msg, resp.Status } func isCookieExpiredMessage(text string) bool { text = strings.ToLower(strings.TrimSpace(text)) if text == "" { return false } for _, needle := range []string{ "账号未绑定", "未绑定账号", "未绑定手机", "登录态", "登录失效", "登录过期", "请先登录", "请登录", "未登录", "重新登录", "会话失效", "会话过期", "凭证失效", "授权失效", "身份失效", "鉴权失败", "认证失败", "token 失效", "token失效", "cookie 失效", "cookie失效", "invalid token", "token expired", "unauthorized", "not logged in", "login expired", } { if strings.Contains(text, needle) { return true } } return false } // cookieCheckConcurrency 给检查单独限流:检查不是抢购,没必要跟着 100 线程配置齐射。 func cookieCheckConcurrency(configured int) int { if configured <= 0 { configured = 5 } if configured < 3 { configured = 3 } if configured > 10 { configured = 10 } return configured }