package api import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "net/url" "time" hub "git.ipao.vip/rogee/creator-hub/internal/environment" douyin "git.ipao.vip/rogee/creator-hub/internal/platform/douyin" ) type douyinGatewayBrowser struct { gateway hub.Gateway environment hub.EnvironmentContext } type douyinGatewayRequest struct { BindingVersion int64 `json:"binding_version"` RuntimeID string `json:"runtime_id"` NetworkID string `json:"network_id"` NetworkExitID string `json:"network_exit_id"` URL string `json:"url,omitempty"` } type douyinLoginQRResponse struct { ContentType string `json:"content_type"` BodyBase64 string `json:"body_base64"` QRDetected bool `json:"qr_detected"` } const maxCreatorLoginQRBytes = 8 << 20 func (browser douyinGatewayBrowser) LoginQR(ctx context.Context) (douyinLoginQRResponse, error) { payload := gatewayGenerationPayload(browser.environment) status, body, err := gatewayCallWithLimit(ctx, browser.gateway, http.MethodPost, "/v1/browsers/"+url.PathEscape(browser.environment.Alias)+"/douyin/login-qr", payload, 30*time.Second, largeGatewayResponseLimit) if err != nil { return douyinLoginQRResponse{}, err } if status != http.StatusOK { return douyinLoginQRResponse{}, fmt.Errorf("douyin login screen request rejected with HTTP %d: %s", status, string(body)) } var response douyinLoginQRResponse if err := json.Unmarshal(body, &response); err != nil { return douyinLoginQRResponse{}, fmt.Errorf("decode douyin login screen response: %w", err) } if response.ContentType != "image/png" || response.BodyBase64 == "" { return douyinLoginQRResponse{}, errors.New("douyin login screen response is invalid") } data, err := base64.StdEncoding.DecodeString(response.BodyBase64) if err != nil || len(data) == 0 || len(data) > maxCreatorLoginQRBytes { return douyinLoginQRResponse{}, errors.New("douyin login screen response is invalid") } return response, nil } func (browser douyinGatewayBrowser) Get(ctx context.Context, target string) (douyin.Response, error) { request, err := browser.request() if err != nil { return douyin.Response{}, err } request.URL = target status, body, err := gatewayCall(ctx, browser.gateway, http.MethodPost, "/v1/browsers/"+url.PathEscape(browser.environment.Alias)+"/douyin/get", request, 30*time.Second) if err != nil || status != http.StatusOK { return douyin.Response{}, errors.New("restricted browser operation failed") } var response struct { Status int `json:"status"` Body string `json:"body"` Challenge douyin.Challenge `json:"challenge"` } if json.Unmarshal(body, &response) != nil || response.Status < 100 || response.Status > 599 || (response.Challenge != douyin.ChallengeNone && response.Challenge != douyin.ChallengeCaptcha && response.Challenge != douyin.ChallengeDevice) { return douyin.Response{}, errors.New("restricted browser returned an invalid response") } return douyin.Response{Status: response.Status, Body: []byte(response.Body), Challenge: response.Challenge}, nil } type douyinHistoryMessage struct { ServerID string `json:"server_id"` SenderUID string `json:"sender_uid"` MessageType string `json:"message_type"` Content json.RawMessage `json:"content"` CreatedAt string `json:"created_at"` ServerState *int `json:"server_status"` } type douyinMessageHistory struct { Status string `json:"status"` HistorySource string `json:"history_source"` HistoryCursor string `json:"history_cursor"` HistoryHasMore bool `json:"history_has_more"` AccountUID string `json:"account_uid"` Conversation map[string]any `json:"conversation"` Messages []douyinHistoryMessage `json:"messages"` } func (browser douyinGatewayBrowser) request() (douyinGatewayRequest, error) { environment := browser.environment if browser.gateway.Endpoint == "" || browser.gateway.Token == "" || !accountRunnable(environment) || !gatewayGenerationIDPattern.MatchString(environment.AccountID) || !gatewayGenerationIDPattern.MatchString(environment.Alias) || !gatewayGenerationIDPattern.MatchString(environment.BindingID) || !gatewayGenerationIDPattern.MatchString(environment.Exit.ID) || environment.Exit.HealthStatus != "healthy" || environment.RuntimeCleanupPending || !gatewayGenerationIDPattern.MatchString(environment.RuntimeInstanceID) || !gatewayGenerationIDPattern.MatchString(environment.RuntimeID) || !gatewayGenerationIDPattern.MatchString(environment.RuntimeNetworkID) || environment.BindingVersion < 1 { return douyinGatewayRequest{}, errors.New("restricted browser is not ready") } return douyinGatewayRequest{BindingVersion: environment.BindingVersion, RuntimeID: environment.RuntimeID, NetworkID: environment.RuntimeNetworkID, NetworkExitID: environment.Exit.ID}, nil }