use log::{debug, warn, error, info, trace};
use anyhow::{anyhow, Result};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use futures::StreamExt;
use rquest::header::{HeaderMap, HeaderValue};
use rquest::Client;
use rquest_util::Emulation;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::fs;
use std::path::Path;
use std::time::{Duration, Instant};
use rand::Rng;
use crate::sha3;
const BASE_URL: &str = "https://chat.deepseek.com/api/v0";
#[derive(Clone, Copy)]
pub enum ThinkingMode {
Detailed,
Simple,
Disabled,
}
impl ThinkingMode {
fn as_bool(&self) -> bool {
!matches!(self, ThinkingMode::Disabled)
}
}
#[derive(Clone, Copy)]
pub enum SearchMode {
Enabled,
Disabled,
}
impl SearchMode {
fn as_bool(&self) -> bool {
matches!(self, SearchMode::Enabled)
}
}
#[derive(Clone)]
pub struct Session {
pub auth_token: String,
pub cookies: serde_json::Value,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct PowChallenge {
pub algorithm: String,
pub challenge: String,
pub salt: String,
pub difficulty: f64,
pub expire_at: u64,
pub signature: String,
pub target_path: String,
}
pub struct DeepSeekPOW {}
impl DeepSeekPOW {
pub fn new() -> Result<Self> {
debug!("Initializing native SHA3 POW solver");
Ok(Self {})
}
fn solve_with_wasm(&self, config: &PowChallenge) -> Result<i64> {
debug!("Attempting POW solve with WASM implementation");
use crate::sha3::WasmInstance;
match WasmInstance::new() {
Ok(mut instance) => {
let prefix = format!("{}_{}_", config.salt, config.expire_at);
match instance.calculate_hash(&config.challenge, &prefix, config.difficulty) {
Ok(Some(nonce)) => {
Ok(nonce as i64)
}
Ok(None) => {
Err(anyhow!("WASM did not find solution"))
}
Err(e) => {
Err(anyhow!("WASM error: {}", e))
}
}
}
Err(e) => {
Err(anyhow!("Failed to create WASM instance: {}", e))
}
}
}
fn solve_with_native(&self, config: &PowChallenge) -> Result<i64> {
debug!("Solving POW with native SHA3 implementation");
let challenge_str = &config.challenge;
let prefix = format!("{}_{}_", config.salt, config.expire_at);
let prefix_bytes = prefix.as_bytes();
let challenge_bytes = challenge_str.as_bytes();
debug!("Solving with prefix: '{}', challenge: '{}', difficulty: {}",
prefix, challenge_str, config.difficulty);
let (success, nonce) = sha3::solve(challenge_bytes, prefix_bytes, config.difficulty);
if success {
info!("Native POW solver found solution: nonce = {nonce}");
Ok(nonce as i64)
} else {
Err(anyhow!("Failed to find POW solution within attempt limit"))
}
}
pub fn solve_challenge(&self, config: &PowChallenge) -> Result<String> {
debug!("Processing verification: algorithm={}, difficulty={}", config.algorithm, config.difficulty);
let _solve_start = std::time::Instant::now();
let answer = match self.solve_with_wasm(config) {
Ok(nonce) => {
nonce
}
Err(_wasm_err) => {
self.solve_with_native(config)?
}
};
let result = json!({
"algorithm": config.algorithm,
"challenge": config.challenge,
"salt": config.salt,
"answer": answer,
"signature": config.signature,
"target_path": config.target_path
});
let encoded = STANDARD.encode(result.to_string());
debug!("POW response encoded: {encoded}");
Ok(encoded)
}
}
pub struct DeepSeek {
http: Client,
session: Session,
pow_solver: DeepSeekPOW,
cookies_refreshed: bool,
last_request_time: Instant,
message_counter: i32,
message_history: Vec<ChatMessage>,
}
#[derive(Clone, Debug, Serialize)]
struct ChatMessage {
role: String,
content: String,
}
impl DeepSeek {
pub fn new(session: Session) -> Result<Self> {
info!("Creating DeepSeek client");
let http = Client::builder()
.emulation(Emulation::Chrome131)
.timeout(Duration::from_secs(600))
.connect_timeout(Duration::from_secs(15))
.gzip(true)
.brotli(true)
.deflate(true)
.build()?;
let pow_solver = DeepSeekPOW::new()?;
debug!("DeepSeek client initialized successfully");
Ok(Self {
http,
session,
pow_solver,
cookies_refreshed: false,
last_request_time: Instant::now(),
message_counter: 0,
message_history: Vec::new(),
})
}
fn default_headers(&self, pow_response: Option<&str>) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert("accept", HeaderValue::from_static("*/*"));
headers.insert(
"accept-encoding",
HeaderValue::from_static("gzip, deflate, br, zstd")
);
headers.insert(
"accept-language",
HeaderValue::from_static("en-US,en;q=0.9")
);
headers.insert(
"authorization",
HeaderValue::from_str(&format!("Bearer {}", self.session.auth_token)).unwrap()
);
headers.insert(
"cache-control",
HeaderValue::from_static("no-cache")
);
headers.insert(
"content-type",
HeaderValue::from_static("application/json")
);
headers.insert(
"dnt",
HeaderValue::from_static("1")
);
headers.insert(
"origin",
HeaderValue::from_static("https://chat.deepseek.com")
);
headers.insert(
"pragma",
HeaderValue::from_static("no-cache")
);
headers.insert(
"priority",
HeaderValue::from_static("u=1, i")
);
headers.insert(
"referer",
HeaderValue::from_static("https://chat.deepseek.com/")
);
headers.insert(
"sec-ch-ua",
HeaderValue::from_static("\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"")
);
headers.insert(
"sec-ch-ua-mobile",
HeaderValue::from_static("?0")
);
headers.insert(
"sec-ch-ua-platform",
HeaderValue::from_static("\"Windows\"")
);
headers.insert(
"sec-fetch-dest",
HeaderValue::from_static("empty")
);
headers.insert(
"sec-fetch-mode",
HeaderValue::from_static("cors")
);
headers.insert(
"sec-fetch-site",
HeaderValue::from_static("same-origin")
);
headers.insert(
"user-agent",
HeaderValue::from_static("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
);
headers.insert(
"x-app-version",
HeaderValue::from_static("20241201.0")
);
headers.insert(
"x-client-locale",
HeaderValue::from_static("en_US")
);
headers.insert(
"x-client-platform",
HeaderValue::from_static("web")
);
headers.insert(
"x-client-version",
HeaderValue::from_static("1.0.0")
);
if let Some(pow_res) = pow_response {
headers.insert(
"x-ds-pow-response",
HeaderValue::from_str(pow_res).unwrap()
);
}
headers
}
async fn refresh_cookies(&mut self) -> Result<()> {
warn!("Attempting to refresh cookies");
if self.cookies_refreshed {
return Err(anyhow!("Already attempted to refresh cookies once this session"));
}
self.cookies_refreshed = true;
Err(anyhow!(
"Cookie refresh needed. Please run the Python bypass script manually and update the cookie file"
))
}
async fn make_request<T: for<'de> Deserialize<'de>>(
&mut self,
method: &str,
endpoint: &str,
json_data: Value,
pow_required: bool
) -> Result<T> {
let url = format!("{BASE_URL}{endpoint}");
debug!("Making {method} request to: {url}");
let mut retry_count = 0;
trace!("Request JSON data: {json_data}");
let max_retries = 2;
while retry_count < max_retries {
debug!("Request attempt {} of {}", retry_count + 1, max_retries);
let mut headers = self.default_headers(None);
if pow_required {
let challenge = self.get_pow_challenge().await?;
let pow_response = self.pow_solver.solve_challenge(&challenge)?;
headers = self.default_headers(Some(&pow_response));
}
let request_builder = match method {
"GET" => self.http.get(&url),
"POST" => self.http.post(&url),
"DELETE" => self.http.delete(&url),
"PUT" => self.http.put(&url),
_ => return Err(anyhow!("Unsupported HTTP method: {}", method)),
};
let cookies_map = match &self.session.cookies {
Value::Object(obj) => obj,
_ => return Err(anyhow!("Invalid cookies format in session")),
};
let mut cookie_str = String::new();
for (key, value) in cookies_map {
if let Value::String(val) = value {
if !cookie_str.is_empty() {
cookie_str.push_str("; ");
}
cookie_str.push_str(&format!("{key}={val}"));
}
}
if !cookie_str.is_empty() {
headers.insert("Cookie", HeaderValue::from_str(&cookie_str)?);
debug!("Added cookies: {} characters", cookie_str.len());
} else {
warn!("No cookies available for request");
}
let elapsed = self.last_request_time.elapsed();
if elapsed < Duration::from_millis(200) {
let mut rng = rand::thread_rng();
let delay_ms = rng.gen_range(200..800);
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
}
self.last_request_time = Instant::now();
let response = match request_builder
.headers(headers)
.json(&json_data)
.send()
.await
{
Ok(resp) => resp,
Err(e) => {
error!("Network error on attempt {}: {}", retry_count + 1, e);
return Err(anyhow!("Network error: {}", e));
}
};
let status = response.status();
let text = response.text().await?;
debug!("Response received - Status: {}, Content length: {}", status, text.len());
trace!("Response body preview: {}", &text[..std::cmp::min(200, text.len())]);
if text.contains("<!DOCTYPE html>") && text.contains("Just a moment") {
debug!("Page verification required on attempt {}", retry_count + 1);
debug!("Response text snippet: {}", &text[..std::cmp::min(200, text.len())]);
if retry_count < max_retries - 1 {
self.refresh_cookies().await.ok(); retry_count += 1;
continue;
} else {
return Err(anyhow!("Request verification failed"));
}
}
match status.as_u16() {
200 => {
debug!("Successful response received, parsing JSON");
match serde_json::from_str(&text) {
Ok(parsed) => return Ok(parsed),
Err(e) => {
error!("Failed to parse JSON response: {e}");
debug!("Response text: {text}");
return Err(anyhow!("Failed to parse API response: {}", e));
}
}
}
401 => {
error!("Authentication error (401): {text}");
return Err(anyhow!("Authentication error: Invalid or expired authentication token"));
}
429 => {
warn!("Rate limit exceeded (429): {text}");
return Err(anyhow!("Rate limit exceeded"));
}
_ => {
warn!("HTTP error {} on attempt {}: {}", status, retry_count + 1, text);
if retry_count < max_retries - 1 {
retry_count += 1;
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
error!("API request failed after {max_retries} attempts with status {status}: {text}");
return Err(anyhow!("API request failed with status {}: {}", status, text));
}
}
}
Err(anyhow!("Failed to get a valid response after {} attempts", max_retries))
}
async fn get_pow_challenge(&mut self) -> Result<PowChallenge> {
debug!("Requesting verification token");
let url = format!("{BASE_URL}/chat/create_pow_challenge");
debug!("POW challenge URL: {url}");
let request_body = json!({"target_path": "/api/v0/chat/completion"}).to_string();
debug!("POW request body: {request_body}");
let mut headers = self.default_headers(None);
debug!("Prepared {} headers for POW request", headers.len());
let cookies_map = match &self.session.cookies {
Value::Object(obj) => {
debug!("Found {} cookie entries for POW request", obj.len());
obj
},
_ => {
error!("Invalid cookies format in session for POW request");
return Err(anyhow!("Invalid cookies format in session"));
}
};
let mut cookie_str = String::new();
let mut cookie_count = 0;
for (key, value) in cookies_map {
if let Value::String(val) = value {
if !cookie_str.is_empty() {
cookie_str.push_str("; ");
}
cookie_str.push_str(&format!("{key}={val}"));
cookie_count += 1;
}
}
if !cookie_str.is_empty() {
headers.insert("Cookie", HeaderValue::from_str(&cookie_str)?);
debug!("Added {} cookies to POW request ({} chars)", cookie_count, cookie_str.len());
} else {
warn!("No cookies available for POW challenge request");
}
debug!("Sending POW challenge HTTP request...");
let response = match self.http.post(&url)
.headers(headers)
.body(request_body)
.send()
.await {
Ok(r) => {
debug!("POW challenge request completed, status: {}", r.status());
r
},
Err(e) => {
error!("POW challenge HTTP request failed: {e}");
return Err(anyhow!("POW challenge request failed: {}", e));
}
};
let status = response.status();
debug!("POW challenge response status: {status}");
if !status.is_success() {
let error_text = response.text().await?;
error!("POW challenge request failed: HTTP {status} - {error_text}");
return Err(anyhow!(
"Failed to get challenge: HTTP {} - {}",
status,
error_text
));
}
debug!("POW challenge request successful, parsing response...");
let response_json: Value = match response.json().await {
Ok(json) => {
debug!("Successfully parsed POW challenge response JSON");
trace!("POW challenge response: {json:?}");
json
},
Err(e) => {
error!("Failed to parse POW challenge response as JSON: {e}");
return Err(anyhow!("Failed to parse POW response: {}", e));
}
};
match response_json.get("data").and_then(|d| d.get("biz_data")).and_then(|b| b.get("challenge")) {
Some(challenge) => {
let challenge_value = challenge.clone();
debug!("Received POW challenge: {challenge_value:?}");
match serde_json::from_value::<PowChallenge>(challenge_value) {
Ok(c) => Ok(c),
Err(e) => {
error!("Failed to parse POW challenge: {e}");
Err(anyhow!("Failed to parse challenge: {}", e))
}
}
},
None => {
error!("Invalid challenge response format. Full response: {response_json:?}");
Err(anyhow!("Invalid challenge response format from server"))
}
}
}
async fn get_fresh_pow_response(&mut self) -> Result<String> {
debug!("Requesting new POW challenge");
let challenge = self.get_pow_challenge().await?;
debug!("Solving POW challenge with difficulty: {}", challenge.difficulty);
let pow_response = self.pow_solver.solve_challenge(&challenge)?;
Ok(pow_response)
}
pub async fn create_chat_session(&mut self) -> Result<String> {
debug!("Creating new chat session");
let response: Value = self.make_request(
"POST",
"/chat_session/create",
json!({"character_id": null}),
false
).await?;
debug!("Chat session creation response: {response:?}");
match &response.get("data").and_then(|d| d.get("biz_data")).and_then(|b| b.get("id")) {
Some(Value::String(id)) => {
info!("Created chat session with ID: {id}");
Ok(id.clone())
},
_ => {
error!("Invalid session creation response format. Response: {response:?}");
Err(anyhow!("Invalid session creation response format from server"))
}
}
}
pub async fn chat_completion(
&mut self,
chat_session_id: &str,
prompt: &str,
parent_message_id: Option<&str>,
thinking_mode: ThinkingMode,
search_mode: SearchMode,
system_prompt: Option<&str>,
) -> Result<String> {
info!("Starting chat completion for session: {chat_session_id}");
debug!("Prompt length: {} chars", prompt.len());
trace!("Parent message ID: {parent_message_id:?}");
trace!("Thinking mode enabled: {}, Search mode enabled: {}", thinking_mode.as_bool(), search_mode.as_bool());
if prompt.is_empty() {
return Err(anyhow!("Prompt must be a non-empty string"));
}
if chat_session_id.is_empty() {
return Err(anyhow!("Chat session ID must be a non-empty string"));
}
let mut full_prompt = String::new();
if self.message_counter == 0 {
if let Some(sys_prompt) = system_prompt {
full_prompt.push_str("System: ");
full_prompt.push_str(sys_prompt);
full_prompt.push_str("\n\n");
}
}
full_prompt.push_str("User: ");
full_prompt.push_str(prompt);
self.message_history.push(ChatMessage {
role: "user".to_string(),
content: prompt.to_string(),
});
let parent_id = if self.message_counter == 0 { None } else { Some(format!("{}", self.message_counter * 2)) };
self.message_counter += 1;
let json_data = json!({
"chat_session_id": chat_session_id,
"parent_message_id": parent_id,
"prompt": full_prompt,
"ref_file_ids": [],
"thinking_enabled": thinking_mode.as_bool(),
"search_enabled": search_mode.as_bool(),
});
debug!("Chat completion request data prepared");
trace!("Request JSON: {json_data}");
let pow_response = self.get_fresh_pow_response().await?;
debug!("Preparing request headers with verification...");
let mut headers = self.default_headers(Some(&pow_response));
debug!("Processing session cookies...");
let cookies_map = match &self.session.cookies {
Value::Object(obj) => {
debug!("Found {} cookie entries in session", obj.len());
obj
},
_ => {
error!("Invalid cookies format in session - not an object");
return Err(anyhow!("Invalid cookies format in session"));
}
};
let mut cookie_str = String::new();
let mut cookie_count = 0;
for (key, value) in cookies_map {
if let Value::String(val) = value {
if !cookie_str.is_empty() {
cookie_str.push_str("; ");
}
cookie_str.push_str(&format!("{key}={val}"));
cookie_count += 1;
}
}
if !cookie_str.is_empty() {
headers.insert("Cookie", HeaderValue::from_str(&cookie_str)?);
debug!("Added {} cookies to request headers ({} chars total)", cookie_count, cookie_str.len());
} else {
warn!("No valid cookies found for chat completion request");
}
debug!("About to send HTTP POST request to chat completion endpoint...");
let url = format!("{BASE_URL}/chat/completion");
debug!("Target URL: {url}");
debug!("Request headers count: {}", headers.len());
let response = match self.http
.post(&url)
.headers(headers)
.json(&json_data)
.send()
.await
{
Ok(resp) => {
debug!("HTTP request completed successfully");
debug!("Response status: {}", resp.status());
debug!("Response headers count: {}", resp.headers().len());
for (name, value) in resp.headers() {
debug!("Response header: {name}: {value:?}");
}
if !resp.status().is_success() {
let status = resp.status();
warn!("HTTP request failed with status: {status}");
let error_text = match resp.text().await {
Ok(text) => {
debug!("Error response body length: {}", text.len());
trace!("Error response body: {text}");
text
},
Err(e) => {
error!("Failed to read error response body: {e}");
format!("Failed to read response: {e}")
}
};
error!("Chat completion failed: {status} - {error_text}");
match status.as_u16() {
401 => return Err(anyhow!("Authentication error: Invalid or expired token. Please check your auth_token file.")),
403 => return Err(anyhow!("Forbidden: Invalid cookies or IP blocked. Please refresh your cookies.")),
429 => return Err(anyhow!("Rate limit exceeded. Please wait and try again.")),
_ => return Err(anyhow!("API request failed: HTTP {} - {}", status, error_text)),
}
}
debug!("HTTP response is successful, proceeding to process stream...");
resp
},
Err(e) => {
error!("Network error during HTTP request: {e}");
error!("Error type: {e:?}");
return Err(anyhow!("Network error occurred during streaming: {}", e));
}
};
debug!("Processing SSE stream...");
let mut result = String::new();
let mut stream = response.bytes_stream();
let mut buffer = String::new();
let mut finished = false;
let mut chunk_count = 0;
while let Some(chunk_result) = stream.next().await {
let chunk_bytes = match chunk_result {
Ok(c) => c,
Err(e) => {
error!("Error reading stream chunk: {e}");
break;
}
};
chunk_count += 1;
let chunk_str = String::from_utf8_lossy(&chunk_bytes);
buffer.push_str(&chunk_str);
trace!("Received chunk #{}, buffer length: {}", chunk_count, buffer.len());
while let Some(newline_pos) = buffer.find('\n') {
let line = buffer[..newline_pos].to_string();
buffer = buffer[newline_pos + 1..].to_string();
let line = line.trim();
if line.is_empty() {
continue;
}
trace!("SSE line: {line}");
if let Some(event_str) = line.strip_prefix("event: ") {
match event_str {
"finish" => {
debug!("Received finish event, stream completed");
finished = true;
break;
}
"close" => {
debug!("Received close event");
finished = true;
break;
}
_ => {
trace!("SSE event: {event_str}");
}
}
continue;
}
if let Some(data_str) = line.strip_prefix("data: ") {
if data_str == "[DONE]" {
debug!("Received stream completion marker [DONE]");
finished = true;
break;
}
match serde_json::from_str::<Value>(data_str) {
Ok(data) => {
debug!("Parsed SSE data: {data:?}");
if let Some(value) = data.get("v") {
let mut content_added = false;
if let Some(path) = data.get("p").and_then(|p| p.as_str()) {
if let Some(operation) = data.get("o").and_then(|o| o.as_str()) {
if path == "response/content" && operation == "APPEND" {
if let Some(text) = value.as_str() {
result.push_str(text);
trace!("Appended response content: {text}");
content_added = true;
}
}
}
}
if !content_added {
if data.get("p").is_none() && data.get("o").is_none() {
if let Some(text) = value.as_str() {
result.push_str(text);
trace!("Appended thinking content: {text}");
content_added = true;
}
}
}
if !content_added {
if let Some(content) = extract_content_from_response(&data) {
result.push_str(&content);
trace!("Added fallback content: {content}");
}
}
}
if let Some(status) = data.get("status") {
if status.as_str() == Some("completed") || status.as_str() == Some("FINISHED") {
debug!("Stream finished with completed status");
finished = true;
break;
}
}
},
Err(e) => {
warn!("Error parsing SSE JSON: {e} - Data: {data_str}");
}
}
}
}
if finished {
break;
}
}
info!("Chat completion finished, processed {} chunks, result length: {}", chunk_count, result.len());
if result.is_empty() {
warn!("Empty result from SSE stream, buffer content: {:?}", buffer);
}
if !result.is_empty() {
self.message_history.push(ChatMessage {
role: "assistant".to_string(),
content: result.clone(),
});
debug!("Added assistant response to message history");
}
Ok(result)
}
pub async fn get_chat_history(&mut self, chat_session_id: &str) -> Result<Value> {
debug!("Getting chat history for session: {chat_session_id}");
let url = format!("{BASE_URL}/chat/history_messages?chat_session_id={chat_session_id}&cache_version=8");
debug!("History URL: {url}");
let response: Value = self.make_request(
"GET",
&format!("/chat/history_messages?chat_session_id={chat_session_id}&cache_version=8"),
json!(null),
false
).await?;
debug!("Chat history response received");
Ok(response)
}
}
fn extract_content_from_response(data: &Value) -> Option<String> {
if let Some(content) = data.get("content") {
if let Some(text) = content.as_str() {
return Some(text.to_string());
}
}
if let Some(response) = data.get("v").and_then(|v| v.get("response")) {
if let Some(content) = response.get("content") {
if let Some(text) = content.as_str() {
return Some(text.to_string());
}
}
}
if let Some(choices) = data.get("choices").and_then(|c| c.as_array()) {
if let Some(choice) = choices.first() {
if let Some(delta) = choice.get("delta") {
if let Some(content) = delta.get("content") {
if let Some(text) = content.as_str() {
return Some(text.to_string());
}
}
}
}
}
None
}
pub fn load_cookies(cookie_file: &Path) -> Result<serde_json::Value> {
debug!("Loading cookies from file: {cookie_file:?}");
if !cookie_file.exists() {
return Err(anyhow!("Cookie file does not exist at {:?}", cookie_file));
}
let cookie_data = fs::read_to_string(cookie_file)?;
let cookies: Value = serde_json::from_str(&cookie_data)?;
debug!("Successfully loaded cookies file, {} bytes", cookie_data.len());
Ok(cookies.get("cookies").cloned().unwrap_or(json!({})))
}
pub fn get_config_help(file_name: &str) -> String {
match file_name {
"deepseek_auth_token" => "To get your DeepSeek auth token:
1. Go to chat.deepseek.com in your browser
2. Open Developer Tools (F12 or right-click and select 'Inspect')
3. Go to the Network tab
4. Refresh the page or make a request
5. Look for requests to the DeepSeek API
6. In the 'Headers' tab, find 'Request Headers'
7. Look for the 'Authorization' header with format 'Bearer {token}'
8. Copy the token part (without 'Bearer ') and save it to this folder with filename: deepseek_auth_token".to_string(),
"deepseek_cookies" => "The DeepSeek API requires Cloudflare cookies.
Please run the Python bypass script from deepseek4free to generate these cookies:
1. Run the bypass.py script from deepseek4free/dsk/
2. Copy the resulting cookies.json file to this folder with filename: deepseek_cookies".to_string(),
_ => format!("Configuration file {file_name} is missing."),
}
}