1use anyhow::{anyhow, Result};
2use base64::{engine::general_purpose::STANDARD, Engine as _};
3use rquest::header::{HeaderMap, HeaderValue};
4use rquest::Client;
5use rquest_util::Emulation;
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::fs;
9use std::path::Path;
10use std::time::{Duration, SystemTime, UNIX_EPOCH};
11use sha3::{Digest, Sha3_256};
12
13const BASE_URL: &str = "https://chat.deepseek.com/api/v0";
15const DEFAULT_MODEL: &str = "deepseek-chat"; #[derive(Clone, Copy)]
19pub enum ThinkingMode {
20 Detailed,
21 Simple,
22 Disabled,
23}
24
25impl ThinkingMode {
26 fn as_bool(&self) -> bool {
27 match self {
28 ThinkingMode::Disabled => false,
29 _ => true,
30 }
31 }
32}
33
34#[derive(Clone, Copy)]
35pub enum SearchMode {
36 Enabled,
37 Disabled,
38}
39
40impl SearchMode {
41 fn as_bool(&self) -> bool {
42 match self {
43 SearchMode::Enabled => true,
44 SearchMode::Disabled => false,
45 }
46 }
47}
48
49#[derive(Clone)]
51pub struct Session {
52 pub auth_token: String,
53 pub cookies: serde_json::Value,
54}
55
56#[derive(Debug, Deserialize, Serialize)]
58pub struct PowChallenge {
59 pub algorithm: String,
60 pub challenge: String,
61 pub salt: String,
62 pub difficulty: f64,
63 pub expire_at: u64,
64 pub signature: String,
65 pub target_path: String,
66}
67
68pub struct DeepSeekPOW {}
71
72impl DeepSeekPOW {
73 pub fn new() -> Result<Self> {
74 Ok(Self {})
75 }
76
77 pub fn solve_challenge(&self, config: &PowChallenge) -> Result<String> {
78 let prefix = format!("{}_{}_", config.salt, config.expire_at);
79
80 let mut hasher = Sha3_256::new();
84 hasher.update(prefix.as_bytes());
85 hasher.update(config.challenge.as_bytes());
86 hasher.update(&SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos().to_le_bytes());
87
88 let hash = hasher.finalize();
89
90 let mut bytes = [0u8; 8];
92 bytes.copy_from_slice(&hash[0..8]);
93 let answer = u64::from_le_bytes(bytes);
94
95 let result = json!({
96 "algorithm": config.algorithm,
97 "challenge": config.challenge,
98 "salt": config.salt,
99 "answer": answer,
100 "signature": config.signature,
101 "target_path": config.target_path
102 });
103
104 Ok(STANDARD.encode(result.to_string()))
105 }
106}
107
108pub struct DeepSeek {
110 http: Client,
111 session: Session,
112 pow_solver: DeepSeekPOW,
113 cookies_refreshed: bool,
114 model: String,
115}
116
117impl DeepSeek {
118 pub fn new(session: Session) -> Result<Self> {
120 Self::new_with_model(session, DEFAULT_MODEL)
121 }
122
123 pub fn new_with_model(session: Session, model: &str) -> Result<Self> {
125 let http = Client::builder()
126 .emulation(Emulation::Chrome120) .timeout(Duration::from_secs(240))
128 .connect_timeout(Duration::from_secs(30))
129 .build()?;
130
131 let pow_solver = DeepSeekPOW::new()?;
132
133 Ok(Self {
134 http,
135 session,
136 pow_solver,
137 cookies_refreshed: false,
138 model: model.to_string(),
139 })
140 }
141
142 fn default_headers(&self, pow_response: Option<&str>) -> HeaderMap {
143 let mut headers = HeaderMap::new();
144 headers.insert("accept", HeaderValue::from_static("*/*"));
145 headers.insert(
146 "accept-language",
147 HeaderValue::from_static("en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3")
148 );
149 headers.insert(
150 "authorization",
151 HeaderValue::from_str(&format!("Bearer {}", self.session.auth_token)).unwrap()
152 );
153 headers.insert(
154 "content-type",
155 HeaderValue::from_static("application/json")
156 );
157 headers.insert(
158 "origin",
159 HeaderValue::from_static("https://chat.deepseek.com")
160 );
161 headers.insert(
162 "referer",
163 HeaderValue::from_static("https://chat.deepseek.com/")
164 );
165 headers.insert(
166 "user-agent",
167 HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36")
168 );
169 headers.insert(
170 "x-app-version",
171 HeaderValue::from_static("20241129.1")
172 );
173 headers.insert(
174 "x-client-locale",
175 HeaderValue::from_static("en_US")
176 );
177 headers.insert(
178 "x-client-platform",
179 HeaderValue::from_static("web")
180 );
181 headers.insert(
182 "x-client-version",
183 HeaderValue::from_static("1.0.0-always")
184 );
185
186 if let Some(pow_res) = pow_response {
187 headers.insert(
188 "x-ds-pow-response",
189 HeaderValue::from_str(pow_res).unwrap()
190 );
191 }
192
193 headers
194 }
195
196 async fn refresh_cookies(&mut self) -> Result<()> {
198 if self.cookies_refreshed {
200 return Err(anyhow!("Already attempted to refresh cookies once this session"));
201 }
202
203 self.cookies_refreshed = true;
206
207 Err(anyhow!(
210 "Cookie refresh needed. Please run the Python bypass script manually and update the cookie file"
211 ))
212 }
213
214 async fn make_request<T: for<'de> Deserialize<'de>>(
216 &mut self,
217 method: &str,
218 endpoint: &str,
219 json_data: Value,
220 pow_required: bool
221 ) -> Result<T> {
222 let url = format!("{}{}", BASE_URL, endpoint);
223 let mut retry_count = 0;
224 let max_retries = 2;
225
226 while retry_count < max_retries {
227 let mut headers = self.default_headers(None);
228
229 if pow_required {
230 let challenge = self.get_pow_challenge().await?;
231 let pow_response = self.pow_solver.solve_challenge(&challenge)?;
232 headers = self.default_headers(Some(&pow_response));
233 }
234
235 let request_builder = match method {
236 "GET" => self.http.get(&url),
237 "POST" => self.http.post(&url),
238 "DELETE" => self.http.delete(&url),
239 "PUT" => self.http.put(&url),
240 _ => return Err(anyhow!("Unsupported HTTP method: {}", method)),
241 };
242
243 let cookies_map = match &self.session.cookies {
245 Value::Object(obj) => obj,
246 _ => return Err(anyhow!("Invalid cookies format in session")),
247 };
248
249 let mut cookie_str = String::new();
250 for (key, value) in cookies_map {
251 if let Value::String(val) = value {
252 if !cookie_str.is_empty() {
253 cookie_str.push_str("; ");
254 }
255 cookie_str.push_str(&format!("{}={}", key, val));
256 }
257 }
258
259 if !cookie_str.is_empty() {
260 headers.insert("Cookie", HeaderValue::from_str(&cookie_str)?);
261 }
262
263 let response = match request_builder
264 .headers(headers)
265 .json(&json_data)
266 .send()
267 .await
268 {
269 Ok(resp) => resp,
270 Err(e) => return Err(anyhow!("Network error: {}", e)),
271 };
272
273 let status = response.status();
275 let text = response.text().await?;
276
277 if text.contains("<!DOCTYPE html>") && text.contains("Just a moment") {
278 eprintln!("Cloudflare protection detected. Attempting to bypass...");
279 if retry_count < max_retries - 1 {
280 self.refresh_cookies().await.ok(); retry_count += 1;
282 continue;
283 } else {
284 return Err(anyhow!("Failed to bypass Cloudflare protection"));
285 }
286 }
287
288 match status.as_u16() {
290 200 => {
291 match serde_json::from_str(&text) {
293 Ok(parsed) => return Ok(parsed),
294 Err(e) => return Err(anyhow!("Failed to parse API response: {}", e)),
295 }
296 }
297 401 => return Err(anyhow!("Authentication error: Invalid or expired authentication token")),
298 429 => return Err(anyhow!("Rate limit exceeded")),
299 _ => {
300 if retry_count < max_retries - 1 {
301 retry_count += 1;
302 tokio::time::sleep(Duration::from_secs(1)).await;
303 continue;
304 }
305 return Err(anyhow!("API request failed with status {}: {}", status, text));
306 }
307 }
308 }
309
310 Err(anyhow!("Failed to get a valid response after {} attempts", max_retries))
311 }
312
313 async fn get_pow_challenge(&mut self) -> Result<PowChallenge> {
315 let url = format!("{}/chat/create_pow_challenge", BASE_URL);
317
318 let request_body = json!({"target_path": "/api/v0/chat/completion"}).to_string();
319 let mut headers = self.default_headers(None);
320
321 let cookies_map = match &self.session.cookies {
323 Value::Object(obj) => obj,
324 _ => return Err(anyhow!("Invalid cookies format in session")),
325 };
326
327 let mut cookie_str = String::new();
328 for (key, value) in cookies_map {
329 if let Value::String(val) = value {
330 if !cookie_str.is_empty() {
331 cookie_str.push_str("; ");
332 }
333 cookie_str.push_str(&format!("{}={}", key, val));
334 }
335 }
336
337 if !cookie_str.is_empty() {
338 headers.insert("Cookie", HeaderValue::from_str(&cookie_str)?);
339 }
340
341 let response = self.http.post(&url)
343 .headers(headers)
344 .body(request_body)
345 .send()
346 .await?;
347
348 if !response.status().is_success() {
349 return Err(anyhow!(
350 "Failed to get challenge: HTTP {}",
351 response.status()
352 ));
353 }
354
355 let response_json: Value = response.json().await?;
357
358 match response_json.get("data").and_then(|d| d.get("biz_data")).and_then(|b| b.get("challenge")) {
360 Some(challenge) => {
361 let challenge_value = challenge.clone();
362 match serde_json::from_value::<PowChallenge>(challenge_value) {
363 Ok(c) => Ok(c),
364 Err(e) => Err(anyhow!("Failed to parse challenge: {}", e)),
365 }
366 },
367 None => Err(anyhow!("Invalid challenge response format from server")),
368 }
369 }
370
371 pub async fn create_chat_session(&mut self) -> Result<String> {
373 let response: Value = self.make_request(
374 "POST",
375 "/chat_session/create",
376 json!({"character_id": null}),
377 false
378 ).await?;
379
380 match &response.get("data").and_then(|d| d.get("biz_data")).and_then(|b| b.get("id")) {
382 Some(Value::String(id)) => Ok(id.clone()),
383 _ => Err(anyhow!("Invalid session creation response format from server")),
384 }
385 }
386
387 pub async fn chat_completion(
389 &mut self,
390 chat_session_id: &str,
391 prompt: &str,
392 parent_message_id: Option<&str>,
393 thinking_mode: ThinkingMode,
394 search_mode: SearchMode,
395 ) -> Result<String> {
396 if prompt.is_empty() {
397 return Err(anyhow!("Prompt must be a non-empty string"));
398 }
399
400 if chat_session_id.is_empty() {
401 return Err(anyhow!("Chat session ID must be a non-empty string"));
402 }
403
404 let json_data = json!({
405 "chat_session_id": chat_session_id,
406 "parent_message_id": parent_message_id,
407 "prompt": prompt,
408 "ref_file_ids": [],
409 "thinking_enabled": thinking_mode.as_bool(),
410 "search_enabled": search_mode.as_bool(),
411 "model": self.model,
412 });
413
414 let challenge = self.get_pow_challenge().await?;
416 let pow_response = self.pow_solver.solve_challenge(&challenge)?;
417
418 let mut headers = self.default_headers(Some(&pow_response));
420
421 let cookies_map = match &self.session.cookies {
423 Value::Object(obj) => obj,
424 _ => return Err(anyhow!("Invalid cookies format in session")),
425 };
426
427 let mut cookie_str = String::new();
428 for (key, value) in cookies_map {
429 if let Value::String(val) = value {
430 if !cookie_str.is_empty() {
431 cookie_str.push_str("; ");
432 }
433 cookie_str.push_str(&format!("{}={}", key, val));
434 }
435 }
436
437 if !cookie_str.is_empty() {
438 headers.insert("Cookie", HeaderValue::from_str(&cookie_str)?);
439 }
440
441 let response = match self.http
443 .post(&format!("{}/chat/completion", BASE_URL))
444 .headers(headers)
445 .json(&json_data)
446 .send()
447 .await
448 {
449 Ok(resp) => {
450 if !resp.status().is_success() {
451 let status = resp.status();
452 let error_text = resp.text().await?;
453 match status.as_u16() {
454 401 => return Err(anyhow!("Authentication error: Invalid or expired token")),
455 429 => return Err(anyhow!("Rate limit exceeded")),
456 _ => return Err(anyhow!("API request failed: {} - {}", status, error_text)),
457 }
458 }
459 resp
460 },
461 Err(e) => return Err(anyhow!("Network error occurred during streaming: {}", e)),
462 };
463
464 let bytes = response.bytes().await?;
466 let text = String::from_utf8_lossy(&bytes);
467 let mut result = String::new();
468
469 for line in text.lines() {
471 if line.starts_with("data: ") {
472 let data_str = &line[6..];
473 match serde_json::from_str::<Value>(data_str) {
474 Ok(data) => {
475 if let Some(choices) = data.get("choices").and_then(|c| c.as_array()) {
476 if let Some(choice) = choices.first() {
477 if let Some(delta) = choice.get("delta") {
478 if let Some(content) = delta.get("content") {
479 if let Some(text) = content.as_str() {
480 result.push_str(text);
481 }
482 }
483 }
484
485 if let Some(finish_reason) = choice.get("finish_reason") {
487 if finish_reason.as_str() == Some("stop") {
488 break;
489 }
490 }
491 }
492 }
493 },
494 Err(e) => eprintln!("Error parsing JSON chunk: {}", e),
495 }
496 }
497 }
498
499 Ok(result)
500 }
501}
502
503pub fn load_cookies(cookie_file: &Path) -> Result<serde_json::Value> {
505 if !cookie_file.exists() {
506 return Err(anyhow!("Cookie file does not exist at {:?}", cookie_file));
507 }
508
509 let cookie_data = fs::read_to_string(cookie_file)?;
510 let cookies: Value = serde_json::from_str(&cookie_data)?;
511
512 Ok(cookies.get("cookies").cloned().unwrap_or(json!({})))
513}
514
515pub fn get_config_help(file_name: &str) -> String {
517 match file_name {
518 "deepseek_auth_token" => "To get your DeepSeek auth token:
5191. Go to chat.deepseek.com in your browser
5202. Open Developer Tools (F12 or right-click and select 'Inspect')
5213. Go to the Network tab
5224. Refresh the page or make a request
5235. Look for requests to the DeepSeek API
5246. In the 'Headers' tab, find 'Request Headers'
5257. Look for the 'Authorization' header with format 'Bearer {token}'
5268. Copy the token part (without 'Bearer ') and save it to this folder with filename: deepseek_auth_token".to_string(),
527
528 "deepseek_cookies" => "The DeepSeek API requires Cloudflare cookies.
529Please run the Python bypass script from deepseek4free to generate these cookies:
5301. Run the bypass.py script from deepseek4free/dsk/
5312. Copy the resulting cookies.json file to this folder with filename: deepseek_cookies".to_string(),
532
533 _ => format!("Configuration file {} is missing.", file_name),
534 }
535}