Files

119 lines
4.9 KiB
Go

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
}