119 lines
4.9 KiB
Go
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
|
|
}
|