Files
huya-go/internal/huya/http_client.go
T

175 lines
4.9 KiB
Go

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
}