mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 08:55:28 +08:00
179 lines
4.9 KiB
Go
179 lines
4.9 KiB
Go
package deepseek
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"ds2api/internal/auth"
|
|
"ds2api/internal/config"
|
|
)
|
|
|
|
func (c *Client) Login(ctx context.Context, acc config.Account) (string, error) {
|
|
payload := map[string]any{
|
|
"password": strings.TrimSpace(acc.Password),
|
|
"device_id": "deepseek_to_api",
|
|
"os": "android",
|
|
}
|
|
if email := strings.TrimSpace(acc.Email); email != "" {
|
|
payload["email"] = email
|
|
} else if mobile := strings.TrimSpace(acc.Mobile); mobile != "" {
|
|
loginMobile, areaCode := normalizeMobileForLogin(mobile)
|
|
payload["mobile"] = loginMobile
|
|
payload["area_code"] = areaCode
|
|
} else {
|
|
return "", errors.New("missing email/mobile")
|
|
}
|
|
resp, err := c.postJSON(ctx, c.regular, DeepSeekLoginURL, BaseHeaders, payload)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
code := intFrom(resp["code"])
|
|
if code != 0 {
|
|
return "", fmt.Errorf("login failed: %v", resp["msg"])
|
|
}
|
|
data, _ := resp["data"].(map[string]any)
|
|
if intFrom(data["biz_code"]) != 0 {
|
|
return "", fmt.Errorf("login failed: %v", data["biz_msg"])
|
|
}
|
|
bizData, _ := data["biz_data"].(map[string]any)
|
|
user, _ := bizData["user"].(map[string]any)
|
|
token, _ := user["token"].(string)
|
|
if strings.TrimSpace(token) == "" {
|
|
return "", errors.New("missing login token")
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
func (c *Client) CreateSession(ctx context.Context, a *auth.RequestAuth, maxAttempts int) (string, error) {
|
|
if maxAttempts <= 0 {
|
|
maxAttempts = c.maxRetries
|
|
}
|
|
attempts := 0
|
|
refreshed := false
|
|
for attempts < maxAttempts {
|
|
headers := c.authHeaders(a.DeepSeekToken)
|
|
resp, status, err := c.postJSONWithStatus(ctx, c.regular, DeepSeekCreateSessionURL, headers, map[string]any{"agent": "chat"})
|
|
if err != nil {
|
|
config.Logger.Warn("[create_session] request error", "error", err, "account", a.AccountID)
|
|
attempts++
|
|
continue
|
|
}
|
|
code := intFrom(resp["code"])
|
|
if status == http.StatusOK && code == 0 {
|
|
data, _ := resp["data"].(map[string]any)
|
|
bizData, _ := data["biz_data"].(map[string]any)
|
|
sessionID, _ := bizData["id"].(string)
|
|
if sessionID != "" {
|
|
return sessionID, nil
|
|
}
|
|
}
|
|
msg, _ := resp["msg"].(string)
|
|
config.Logger.Warn("[create_session] failed", "status", status, "code", code, "msg", msg, "use_config_token", a.UseConfigToken, "account", a.AccountID)
|
|
if a.UseConfigToken {
|
|
if isTokenInvalid(status, code, msg) && !refreshed {
|
|
if c.Auth.RefreshToken(ctx, a) {
|
|
refreshed = true
|
|
continue
|
|
}
|
|
}
|
|
if c.Auth.SwitchAccount(ctx, a) {
|
|
refreshed = false
|
|
attempts++
|
|
continue
|
|
}
|
|
}
|
|
attempts++
|
|
}
|
|
return "", errors.New("create session failed")
|
|
}
|
|
|
|
func (c *Client) GetPow(ctx context.Context, a *auth.RequestAuth, maxAttempts int) (string, error) {
|
|
if maxAttempts <= 0 {
|
|
maxAttempts = c.maxRetries
|
|
}
|
|
attempts := 0
|
|
for attempts < maxAttempts {
|
|
headers := c.authHeaders(a.DeepSeekToken)
|
|
resp, status, err := c.postJSONWithStatus(ctx, c.regular, DeepSeekCreatePowURL, headers, map[string]any{"target_path": "/api/v0/chat/completion"})
|
|
if err != nil {
|
|
config.Logger.Warn("[get_pow] request error", "error", err, "account", a.AccountID)
|
|
attempts++
|
|
continue
|
|
}
|
|
code := intFrom(resp["code"])
|
|
if status == http.StatusOK && code == 0 {
|
|
data, _ := resp["data"].(map[string]any)
|
|
bizData, _ := data["biz_data"].(map[string]any)
|
|
challenge, _ := bizData["challenge"].(map[string]any)
|
|
answer, err := c.powSolver.Compute(ctx, challenge)
|
|
if err != nil {
|
|
attempts++
|
|
continue
|
|
}
|
|
return BuildPowHeader(challenge, answer)
|
|
}
|
|
msg, _ := resp["msg"].(string)
|
|
config.Logger.Warn("[get_pow] failed", "status", status, "code", code, "msg", msg, "use_config_token", a.UseConfigToken, "account", a.AccountID)
|
|
if a.UseConfigToken {
|
|
if isTokenInvalid(status, code, msg) {
|
|
if c.Auth.RefreshToken(ctx, a) {
|
|
continue
|
|
}
|
|
}
|
|
if c.Auth.SwitchAccount(ctx, a) {
|
|
attempts++
|
|
continue
|
|
}
|
|
}
|
|
attempts++
|
|
}
|
|
return "", errors.New("get pow failed")
|
|
}
|
|
|
|
func (c *Client) authHeaders(token string) map[string]string {
|
|
headers := make(map[string]string, len(BaseHeaders)+1)
|
|
for k, v := range BaseHeaders {
|
|
headers[k] = v
|
|
}
|
|
headers["authorization"] = "Bearer " + token
|
|
return headers
|
|
}
|
|
|
|
func isTokenInvalid(status int, code int, msg string) bool {
|
|
msg = strings.ToLower(msg)
|
|
if status == http.StatusUnauthorized || status == http.StatusForbidden {
|
|
return true
|
|
}
|
|
if code == 40001 || code == 40002 || code == 40003 {
|
|
return true
|
|
}
|
|
return strings.Contains(msg, "token") || strings.Contains(msg, "unauthorized")
|
|
}
|
|
|
|
func normalizeMobileForLogin(raw string) (mobile string, areaCode any) {
|
|
s := strings.TrimSpace(raw)
|
|
if s == "" {
|
|
return "", nil
|
|
}
|
|
hasPlus := strings.HasPrefix(s, "+")
|
|
var b strings.Builder
|
|
b.Grow(len(s))
|
|
for _, r := range s {
|
|
if unicode.IsDigit(r) {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
digits := b.String()
|
|
if digits == "" {
|
|
return "", nil
|
|
}
|
|
if (hasPlus || strings.HasPrefix(digits, "86")) && strings.HasPrefix(digits, "86") && len(digits) == 13 {
|
|
return digits[2:], nil
|
|
}
|
|
return digits, nil
|
|
}
|