186 lines
5.5 KiB
Go
186 lines
5.5 KiB
Go
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
|
|
}
|