Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import (
"github.com/432539/gpt2api/internal/config"
"github.com/432539/gpt2api/internal/db"
"github.com/432539/gpt2api/internal/gateway"
"github.com/432539/gpt2api/internal/image"
" "github.com/432539/gpt2api/internal/image"
"github.com/432539/gpt2api/internal/upstream/chatgpt""
modelpkg "github.com/432539/gpt2api/internal/model"
"github.com/432539/gpt2api/internal/proxy"
gwratelimit "github.com/432539/gpt2api/internal/ratelimit"
Expand Down Expand Up @@ -142,7 +143,13 @@ func main() {
}

imageDAO := image.NewDAO(sqldb)
imageRunner := image.NewRunner(sched, imageDAO)
// Turnstile solver (optional): set upstream.turnstile_solver_url in config.yaml
var turnstileSolver chatgpt.TurnstileSolver
if cfg.Upstream.TurnstileSolverURL != "" {
turnstileSolver = chatgpt.NewHTTPTurnstileSolver(cfg.Upstream.TurnstileSolverURL)
log.Info("turnstile solver enabled", zap.String("url", cfg.Upstream.TurnstileSolverURL))
}
imageRunner := image.NewRunner(sched, imageDAO, turnstileSolver)
imagesH := &gateway.ImagesHandler{
Handler: gwH,
Runner: imageRunner,
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type UpstreamConfig struct {
BaseURL string `mapstructure:"base_url"`
RequestTimeoutSec int `mapstructure:"request_timeout_sec"`
SSEReadTimeoutSec int `mapstructure:"sse_read_timeout_sec"`
TurnstileSolverURL string `mapstructure:"turnstile_solver_url"`
}

// BackupConfig 数据库备份配置。
Expand Down
24 changes: 13 additions & 11 deletions internal/image/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ import (
// IMG2 已正式上线,不再做"灰度命中判定 / preview_only 换账号重试"这些节流操作,
// 拿到任意 file-service / sediment 引用即算成功,以速度和效率优先。
type Runner struct {
sched *scheduler.Scheduler
dao *DAO
sched *scheduler.Scheduler
dao *DAO
solver chatgpt.TurnstileSolver
}

// NewRunner 构造 Runner。
func NewRunner(sched *scheduler.Scheduler, dao *DAO) *Runner {
return &Runner{sched: sched, dao: dao}
func NewRunner(sched *scheduler.Scheduler, dao *DAO, solver chatgpt.TurnstileSolver) *Runner {
return &Runner{sched: sched, dao: dao, solver: solver}
}

// ReferenceImage 是图生图/编辑的一张参考图输入。
Expand Down Expand Up @@ -77,10 +78,10 @@ func (r *Runner) Run(ctx context.Context, opt RunOptions) *RunResult {
opt.MaxAttempts = 1
}
if opt.PerAttemptTimeout <= 0 {
opt.PerAttemptTimeout = 6 * time.Minute
opt.PerAttemptTimeout = 10 * time.Minute
}
if opt.PollMaxWait <= 0 {
opt.PollMaxWait = 300 * time.Second
opt.PollMaxWait = 480 * time.Second
}
if opt.UpstreamModel == "" {
// 对齐浏览器抓包 + 参考实现:图像走 f/conversation 时 model 字段和
Expand Down Expand Up @@ -173,11 +174,12 @@ func (r *Runner) runOnce(ctx context.Context, opt RunOptions, result *RunResult)

// 2) 构造上游 client
cli, err := chatgpt.New(chatgpt.Options{
AuthToken: lease.AuthToken,
DeviceID: lease.DeviceID,
SessionID: lease.SessionID,
ProxyURL: lease.ProxyURL,
Cookies: "", // 目前不从 oai_account_cookies 加载,后续 M3+ 再做
AuthToken: lease.AuthToken,
DeviceID: lease.DeviceID,
SessionID: lease.SessionID,
ProxyURL: lease.ProxyURL,
Cookies: "", // 目前不从 oai_account_cookies 加载,后续 M3+ 再做
TurnstileSolver: r.solver,
})
if err != nil {
return false, ErrUnknown, fmt.Errorf("chatgpt client: %w", err)
Expand Down
4 changes: 3 additions & 1 deletion internal/upstream/chatgpt/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ func truncatePrefix(s string, n int) string {
type ChatRequirementsPrepareResp struct {
Persona string `json:"persona"`
PrepareToken string `json:"prepare_token"`
PToken string `json:"-"` // local: the p_value / requirements_token used in prepare request
Turnstile struct {
Required bool `json:"required"`
DX string `json:"dx"`
Expand Down Expand Up @@ -384,6 +385,7 @@ func (c *Client) ChatRequirementsPrepare(ctx context.Context) (*ChatRequirements
if err := json.Unmarshal(buf, &out); err != nil {
return nil, fmt.Errorf("decode chat-requirements/prepare: %w", err)
}
out.PToken = reqToken
return &out, nil
}

Expand Down Expand Up @@ -476,7 +478,7 @@ func (c *Client) ChatRequirementsV2(ctx context.Context) (*ChatRequirementsResp,
if c.opts.TurnstileSolver != nil {
sCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
out, solveErr := c.opts.TurnstileSolver.Solve(sCtx, prep.Turnstile.DX)
out, solveErr := c.opts.TurnstileSolver.Solve(sCtx, prep.Turnstile.DX, prep.PToken)
if solveErr != nil || out == "" {
if logger := loggerL(); logger != nil {
logger.Warn("turnstile solver failed, fallback to single-step chat-requirements",
Expand Down
50 changes: 50 additions & 0 deletions internal/upstream/chatgpt/http_turnstile_solver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package chatgpt

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)

// HTTPTurnstileSolver calls an external HTTP service to solve Turnstile challenges.
// The service should accept POST /solve with {"dx": "...", "p": "..."} and return
// {"token": "..."}.
type HTTPTurnstileSolver struct {
url string
client *http.Client
}

// NewHTTPTurnstileSolver creates a solver that delegates to the given URL.
func NewHTTPTurnstileSolver(url string) *HTTPTurnstileSolver {
return &HTTPTurnstileSolver{url: url, client: &http.Client{}}
}

func (s *HTTPTurnstileSolver) Solve(ctx context.Context, dx string, token string) (string, error) {
body, _ := json.Marshal(map[string]string{"dx": dx, "p": token})
req, err := http.NewRequestWithContext(ctx, "POST", s.url, bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")

resp, err := s.client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
buf, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return "", fmt.Errorf("turnstile solver HTTP %d: %s", resp.StatusCode, string(buf))
}

var result struct {
Token string `json:"token"`
}
if err := json.Unmarshal(buf, &result); err != nil {
return "", fmt.Errorf("decode solver response: %w", err)
}
return result.Token, nil
}
11 changes: 8 additions & 3 deletions internal/upstream/chatgpt/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,12 @@ func ExtractImageToolMsgs(mapping map[string]interface{}) []ImageToolMsg {
if s, _ := author["role"].(string); s != "tool" {
continue
}
if s, _ := meta["async_task_type"].(string); s != "image_gen" {
// Match: old format (async_task_type=image_gen), new final (image_gen_title),
// or new preview (async_source without image_gen_title).
asyncType, _ := meta["async_task_type"].(string)
imgTitle, _ := meta["image_gen_title"].(string)
asyncSrc, _ := meta["async_source"].(string)
if asyncType != "image_gen" && imgTitle == "" && asyncSrc == "" {
continue
}
if s, _ := content["content_type"].(string); s != "multimodal_text" {
Expand Down Expand Up @@ -525,7 +530,7 @@ func (c *Client) PollConversationForImages(ctx context.Context, convID string, o
opt.MaxWait = 300 * time.Second
}
if opt.Interval <= 0 {
opt.Interval = 3 * time.Second
opt.Interval = 15 * time.Second
}
baseline := opt.BaselineToolIDs

Expand All @@ -551,7 +556,7 @@ func (c *Client) PollConversationForImages(ctx context.Context, convID string, o
if err != nil {
if ue, ok := err.(*UpstreamError); ok && ue.Status == 429 {
consecutive429++
if consecutive429 >= 3 {
if consecutive429 >= 10 {
return PollStatusError, nil, nil
}
sleep(ctx, 10*time.Second)
Expand Down
2 changes: 1 addition & 1 deletion internal/upstream/chatgpt/pow.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
// 没有 solver 时,Client.ChatRequirementsV2 会自动回退到老的单步
// chat-requirements 流程(Turnstile=true 直接忽略)。
type TurnstileSolver interface {
Solve(ctx context.Context, dx string) (string, error)
Solve(ctx context.Context, dx string, token string) (string, error)
}

const (
Expand Down