| package protocol |
|
|
| import ( |
| _ "embed" |
| "encoding/json" |
| "fmt" |
| "strings" |
| ) |
|
|
| const ( |
| DeepSeekHost = "chat.deepseek.com" |
| DeepSeekLoginURL = "https://chat.deepseek.com/api/v0/users/login" |
| DeepSeekCreateSessionURL = "https://chat.deepseek.com/api/v0/chat_session/create" |
| DeepSeekCreatePowURL = "https://chat.deepseek.com/api/v0/chat/create_pow_challenge" |
| DeepSeekCompletionURL = "https://chat.deepseek.com/api/v0/chat/completion" |
| DeepSeekContinueURL = "https://chat.deepseek.com/api/v0/chat/continue" |
| DeepSeekUploadFileURL = "https://chat.deepseek.com/api/v0/file/upload_file" |
| DeepSeekFetchFilesURL = "https://chat.deepseek.com/api/v0/file/fetch_files" |
| DeepSeekFetchSessionURL = "https://chat.deepseek.com/api/v0/chat_session/fetch_page" |
| DeepSeekDeleteSessionURL = "https://chat.deepseek.com/api/v0/chat_session/delete" |
| DeepSeekDeleteAllSessionsURL = "https://chat.deepseek.com/api/v0/chat_session/delete_all" |
| DeepSeekCompletionTargetPath = "/api/v0/chat/completion" |
| DeepSeekUploadTargetPath = "/api/v0/file/upload_file" |
| ) |
|
|
| var defaultStaticBaseHeaders = map[string]string{ |
| "Host": "chat.deepseek.com", |
| "Accept": "application/json", |
| "Content-Type": "application/json", |
| "accept-charset": "UTF-8", |
| } |
|
|
| var defaultSkipContainsPatterns = []string{ |
| "quasi_status", |
| "elapsed_secs", |
| "token_usage", |
| "pending_fragment", |
| "conversation_mode", |
| "fragments/-1/status", |
| "fragments/-2/status", |
| "fragments/-3/status", |
| } |
|
|
| var defaultSkipExactPaths = []string{ |
| "response/search_status", |
| } |
|
|
| var ClientVersion string |
| var currentPlatform = "android" |
| var currentAndroidAPILevel = "35" |
| var BaseHeaders = map[string]string{} |
| var SkipContainsPatterns = cloneStringSlice(defaultSkipContainsPatterns) |
| var SkipExactPathSet = toStringSet(defaultSkipExactPaths) |
|
|
| type clientConstants struct { |
| Name string `json:"name"` |
| Platform string `json:"platform"` |
| Version string `json:"version"` |
| AndroidAPILevel string `json:"android_api_level"` |
| Locale string `json:"locale"` |
| } |
|
|
| type sharedConstants struct { |
| Client clientConstants `json:"client"` |
| BaseHeaders map[string]string `json:"base_headers"` |
| SkipContainsPattern []string `json:"skip_contains_patterns"` |
| SkipExactPaths []string `json:"skip_exact_paths"` |
| } |
|
|
| |
| var sharedConstantsJSON []byte |
|
|
| |
| var webConstantsJSON []byte |
|
|
| func init() { |
| cfg := sharedConstants{} |
| if err := json.Unmarshal(sharedConstantsJSON, &cfg); err != nil { |
| panic(fmt.Errorf("load DeepSeek shared constants: %w", err)) |
| } |
| applySharedConstants(cfg) |
| } |
|
|
| func applySharedConstants(cfg sharedConstants) { |
| client := normalizeClientConstants(cfg.Client) |
| ClientVersion = client.Version |
| if client.Platform == "android" { |
| currentAndroidAPILevel = client.AndroidAPILevel |
| } |
| BaseHeaders = buildBaseHeaders(client, cfg.BaseHeaders) |
| SkipContainsPatterns = cloneStringSlice(defaultSkipContainsPatterns) |
| if len(cfg.SkipContainsPattern) > 0 { |
| SkipContainsPatterns = cloneStringSlice(cfg.SkipContainsPattern) |
| } |
| SkipExactPathSet = toStringSet(defaultSkipExactPaths) |
| if len(cfg.SkipExactPaths) > 0 { |
| SkipExactPathSet = toStringSet(cfg.SkipExactPaths) |
| } |
| } |
|
|
| func normalizeClientConstants(in clientConstants) clientConstants { |
| if in.Name == "" { |
| in.Name = "DeepSeek" |
| } |
| if in.Platform == "" { |
| in.Platform = "android" |
| } |
| if in.AndroidAPILevel == "" { |
| in.AndroidAPILevel = "35" |
| } |
| if in.Locale == "" { |
| in.Locale = "zh_CN" |
| } |
| return in |
| } |
|
|
| func buildBaseHeaders(client clientConstants, overrides map[string]string) map[string]string { |
| out := cloneStringMap(defaultStaticBaseHeaders) |
| for k, v := range overrides { |
| if k == "" || v == "" { |
| continue |
| } |
| out[k] = v |
| } |
| out["User-Agent"] = userAgentForPlatform(client) |
| if client.Platform != "" { |
| out["x-client-platform"] = client.Platform |
| } |
| if client.Version != "" { |
| out["x-client-version"] = client.Version |
| } |
| if client.Locale != "" { |
| out["x-client-locale"] = client.Locale |
| } |
| return out |
| } |
|
|
| const webUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" |
|
|
| func userAgentForPlatform(client clientConstants) string { |
| if client.Platform == "web" { |
| return webUserAgent |
| } |
| if client.Name != "" && client.Version != "" { |
| ua := client.Name + "/" + client.Version |
| if client.Platform == "android" && client.AndroidAPILevel != "" { |
| ua += " Android/" + client.AndroidAPILevel |
| } |
| return ua |
| } |
| return "" |
| } |
|
|
| |
| func ApplyPlatform(platform string) { |
| p := strings.ToLower(platform) |
| var data []byte |
| switch p { |
| case "web": |
| data = webConstantsJSON |
| default: |
| data = sharedConstantsJSON |
| p = "android" |
| } |
| cfg := sharedConstants{} |
| if err := json.Unmarshal(data, &cfg); err != nil { |
| panic(fmt.Errorf("load DeepSeek %s constants: %w", p, err)) |
| } |
| currentPlatform = p |
| applySharedConstants(cfg) |
| } |
|
|
| |
| |
| |
| func ApplyVersionOverrides(androidVersion, webVersion, androidAPILevel string) { |
| if currentPlatform == "android" { |
| ver := androidVersion |
| if ver == "" { |
| ver = ClientVersion |
| } |
| apiLevel := androidAPILevel |
| if apiLevel == "" { |
| apiLevel = currentAndroidAPILevel |
| } |
| ClientVersion = ver |
| BaseHeaders["x-client-version"] = ver |
| BaseHeaders["x-client-platform"] = "android" |
| BaseHeaders["User-Agent"] = "DeepSeek/" + ver + " Android/" + apiLevel |
| } else if currentPlatform == "web" { |
| ver := webVersion |
| if ver == "" { |
| ver = ClientVersion |
| } |
| ClientVersion = ver |
| BaseHeaders["x-client-version"] = ver |
| BaseHeaders["x-app-version"] = ver |
| } |
| } |
|
|
| |
| func IsWebPlatform() bool { |
| return currentPlatform == "web" |
| } |
|
|
| |
| func UserAgent() string { |
| if currentPlatform == "web" { |
| return webUserAgent |
| } |
| return "DeepSeek/" + ClientVersion |
| } |
|
|
| |
| |
| func WebExtraHeaders() map[string]string { |
| return map[string]string{ |
| "sec-ch-ua": `"Chromium";v="137", "Not/A)Brand";v="24"`, |
| "sec-ch-ua-mobile": "?0", |
| "sec-ch-ua-platform": `"Windows"`, |
| "origin": "https://chat.deepseek.com", |
| "referer": "https://chat.deepseek.com/", |
| "sec-fetch-dest": "empty", |
| "sec-fetch-mode": "cors", |
| "sec-fetch-site": "same-origin", |
| } |
| } |
|
|
| func cloneStringMap(in map[string]string) map[string]string { |
| out := make(map[string]string, len(in)) |
| for k, v := range in { |
| out[k] = v |
| } |
| return out |
| } |
|
|
| func cloneStringSlice(in []string) []string { |
| out := make([]string, len(in)) |
| copy(out, in) |
| return out |
| } |
|
|
| func toStringSet(in []string) map[string]struct{} { |
| out := make(map[string]struct{}, len(in)) |
| for _, v := range in { |
| if v == "" { |
| continue |
| } |
| out[v] = struct{}{} |
| } |
| return out |
| } |
|
|
| const ( |
| KeepAliveTimeout = 5 |
| StreamIdleTimeout = 300 |
| MaxKeepaliveCount = 40 |
| ) |
|
|