package huya import ( "bytes" "compress/gzip" "context" "fmt" "io" "net/http" "time" "huya-go/internal/taf" "huya-go/internal/wup" ) const DefaultCDNWSEndpoint = "https://cdnws.api.huya.com/" type HTTPClient struct { Endpoint string Client *http.Client Logger func(string) } type CallOptions struct { UID int64 GUID string Cookie string RequestID int32 Timeout time.Duration } func NewHTTPClient(logger func(string)) *HTTPClient { return &HTTPClient{ Endpoint: DefaultCDNWSEndpoint, Client: &http.Client{ Timeout: 15 * time.Second, }, Logger: logger, } } func (c *HTTPClient) logf(format string, args ...any) { if c.Logger != nil { c.Logger(fmt.Sprintf(format, args...)) } } func (c *HTTPClient) PrewarmActivity(ctx context.Context, uid int64, cookie string, sid int32) error { return nil } func (c *HTTPClient) CallRPC(ctx context.Context, service, method string, reqStruct taf.Marshaler, respStruct taf.Unmarshaler, opt CallOptions) (*wup.Response, error) { if opt.RequestID == 0 { opt.RequestID = 1 } reqWup := wup.NewRequest(service, method, opt.RequestID) if err := reqWup.WriteStruct("tReq", reqStruct); err != nil { return nil, err } body, err := reqWup.Encode() if err != nil { return nil, err } baseinfo, err := GenerateHTTPBaseinfo(opt.UID, opt.GUID, opt.Cookie) if err != nil { return nil, err } endpoint := c.Endpoint if endpoint == "" { endpoint = DefaultCDNWSEndpoint } url := endpoint + "?baseinfo=" + baseinfo httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return nil, err } httpReq.Header.Set("User-Agent", PCUserAgent) httpReq.Header.Set("Origin", "https://zt.huya.com") httpReq.Header.Set("Referer", "https://zt.huya.com/") httpReq.Header.Set("Content-Type", "application/octet-stream") httpReq.Header.Set("Accept-Language", "zh-CN,zh;q=0.9") client := c.Client if client == nil { client = http.DefaultClient } if opt.Timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, opt.Timeout) defer cancel() httpReq = httpReq.WithContext(ctx) } c.logf("[HTTP] POST %s.%s body=%dB", service, method, len(body)) httpResp, err := client.Do(httpReq) if err != nil { return nil, err } defer httpResp.Body.Close() respBody, err := io.ReadAll(httpResp.Body) if err != nil { return nil, err } if len(respBody) >= 2 && respBody[0] == 0x1f && respBody[1] == 0x8b { gz, err := gzip.NewReader(bytes.NewReader(respBody)) if err != nil { return nil, err } respBody, err = io.ReadAll(gz) _ = gz.Close() if err != nil { return nil, err } } if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 { return nil, fmt.Errorf("http status %d: %s", httpResp.StatusCode, string(respBody[:min(len(respBody), 200)])) } var resp wup.Response if err := resp.Decode(respBody); err != nil { return nil, err } if respStruct != nil { ok, err := resp.ReadStruct("tRsp", respStruct) if err != nil { return nil, err } if !ok { return nil, fmt.Errorf("%s.%s 响应缺少 tRsp/tResp", service, method) } } return &resp, nil } func (c *HTTPClient) buildUser(uid int64, cookie, ua string) ActivityUserID { return NewActivityUserID(uid, cookie, ua) } func (c *HTTPClient) QueryUserScore(ctx context.Context, uid int64, cookie string, sid int32) (*GetUserScoreResp, error) { req := &GetUserScoreReq{User: c.buildUser(uid, cookie, ActivityWebUA), SID: sid} var resp GetUserScoreResp _, err := c.CallRPC(ctx, ServantWebActUI, FuncGetUserScore, req, &resp, CallOptions{UID: uid, Cookie: cookie, RequestID: 2}) return &resp, err } func (c *HTTPClient) GetActPrizeList(ctx context.Context, uid int64, cookie string, sid int32) (*GetActPrizeListResp, error) { req := &GetActPrizeListReq{User: c.buildUser(uid, cookie, ActivityWebUA), SID: sid} var resp GetActPrizeListResp _, err := c.CallRPC(ctx, ServantWebActUI, FuncPrizeList, req, &resp, CallOptions{UID: uid, Cookie: cookie, RequestID: 1}) return &resp, err } func (c *HTTPClient) GetUserPrizeRecords(ctx context.Context, uid int64, cookie string, sid int32) (*GetUserPrizeRecordsResp, error) { user := c.buildUser(uid, cookie, ActivityRecordUA) user.GUID = CookieValue(cookie, "guid") req := &GetUserPrizeRecordsReq{User: user, SID: sid} var resp GetUserPrizeRecordsResp _, err := c.CallRPC(ctx, ServantWebActUI, FuncPrizeRecords, req, &resp, CallOptions{UID: uid, Cookie: cookie, RequestID: 6}) return &resp, err } func (c *HTTPClient) ScoreExchangePrize(ctx context.Context, uid int64, cookie string, sid, pid int32) (*ScoreExchangePrizeResp, error) { req := &ScoreExchangePrizeReq{ User: c.buildUser(uid, cookie, ActivityWebUA), SID: sid, PID: pid, ClientEnv: map[string]string{}, IsRole: 1, } var resp ScoreExchangePrizeResp _, err := c.CallRPC(ctx, ServantWebActUI, FuncExchangePrize, req, &resp, CallOptions{UID: uid, Cookie: cookie, RequestID: 1}) return &resp, err }