use anyhow::{anyhow, Result};
use base64::{engine::general_purpose::STANDARD, Engine as _};
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, SystemTime, UNIX_EPOCH};
use sha3::{Digest, Sha3_256};
const BASE_URL: &str = "https://chat.deepseek.com/api/v0";
const DEFAULT_MODEL: &str = "deepseek-chat";
#[derive(Clone, Copy)]
pub enum ThinkingMode {
Detailed,
Simple,
Disabled,
}
impl ThinkingMode {
fn as_bool(&self) -> bool {
match self {
ThinkingMode::Disabled => false,
_ => true,
}
}
}
#[derive(Clone, Copy)]
pub enum SearchMode {
Enabled,
Disabled,
}
impl SearchMode {
fn as_bool(&self) -> bool {
match self {
SearchMode::Enabled => true,
SearchMode::Disabled => false,
}
}
}
#[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> {
Ok(Self {})
}
pub fn solve_challenge(&self, config: &PowChallenge) -> Result<String> {
let prefix = format!("{}_{}_", config.salt, config.expire_at);
let mut hasher = Sha3_256::new();
hasher.update(prefix.as_bytes());
hasher.update(config.challenge.as_bytes());
hasher.update(&SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos().to_le_bytes());
let hash = hasher.finalize();
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&hash[0..8]);
let answer = u64::from_le_bytes(bytes);
let result = json!({
"algorithm": config.algorithm,
"challenge": config.challenge,
"salt": config.salt,
"answer": answer,
"signature": config.signature,
"target_path": config.target_path
});
Ok(STANDARD.encode(result.to_string()))
}
}
pub struct DeepSeek {
http: Client,
session: Session,
pow_solver: DeepSeekPOW,
cookies_refreshed: bool,
model: String,
}
impl DeepSeek {
pub fn new(session: Session) -> Result<Self> {
Self::new_with_model(session, DEFAULT_MODEL)
}
pub fn new_with_model(session: Session, model: &str) -> Result<Self> {
let http = Client::builder()
.emulation(Emulation::Chrome120) .timeout(Duration::from_secs(240))
.connect_timeout(Duration::from_secs(30))
.build()?;
let pow_solver = DeepSeekPOW::new()?;
Ok(Self {
http,
session,
pow_solver,
cookies_refreshed: false,
model: model.to_string(),
})
}
fn default_headers(&self, pow_response: Option<&str>) -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert("accept", HeaderValue::from_static("*/*"));
headers.insert(
"accept-language",
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")
);
headers.insert(
"authorization",
HeaderValue::from_str(&format!("Bearer {}", self.session.auth_token)).unwrap()
);
headers.insert(
"content-type",
HeaderValue::from_static("application/json")
);
headers.insert(
"origin",
HeaderValue::from_static("https://chat.deepseek.com")
);
headers.insert(
"referer",
HeaderValue::from_static("https://chat.deepseek.com/")
);
headers.insert(
"user-agent",
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")
);
headers.insert(
"x-app-version",
HeaderValue::from_static("20241129.1")
);
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-always")
);
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<()> {
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);
let mut retry_count = 0;
let max_retries = 2;
while retry_count < 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)?);
}
let response = match request_builder
.headers(headers)
.json(&json_data)
.send()
.await
{
Ok(resp) => resp,
Err(e) => return Err(anyhow!("Network error: {}", e)),
};
let status = response.status();
let text = response.text().await?;
if text.contains("<!DOCTYPE html>") && text.contains("Just a moment") {
eprintln!("Cloudflare protection detected. Attempting to bypass...");
if retry_count < max_retries - 1 {
self.refresh_cookies().await.ok(); retry_count += 1;
continue;
} else {
return Err(anyhow!("Failed to bypass Cloudflare protection"));
}
}
match status.as_u16() {
200 => {
match serde_json::from_str(&text) {
Ok(parsed) => return Ok(parsed),
Err(e) => return Err(anyhow!("Failed to parse API response: {}", e)),
}
}
401 => return Err(anyhow!("Authentication error: Invalid or expired authentication token")),
429 => return Err(anyhow!("Rate limit exceeded")),
_ => {
if retry_count < max_retries - 1 {
retry_count += 1;
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
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> {
let url = format!("{}/chat/create_pow_challenge", BASE_URL);
let request_body = json!({"target_path": "/api/v0/chat/completion"}).to_string();
let mut headers = self.default_headers(None);
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)?);
}
let response = self.http.post(&url)
.headers(headers)
.body(request_body)
.send()
.await?;
if !response.status().is_success() {
return Err(anyhow!(
"Failed to get challenge: HTTP {}",
response.status()
));
}
let response_json: Value = response.json().await?;
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();
match serde_json::from_value::<PowChallenge>(challenge_value) {
Ok(c) => Ok(c),
Err(e) => Err(anyhow!("Failed to parse challenge: {}", e)),
}
},
None => Err(anyhow!("Invalid challenge response format from server")),
}
}
pub async fn create_chat_session(&mut self) -> Result<String> {
let response: Value = self.make_request(
"POST",
"/chat_session/create",
json!({"character_id": null}),
false
).await?;
match &response.get("data").and_then(|d| d.get("biz_data")).and_then(|b| b.get("id")) {
Some(Value::String(id)) => Ok(id.clone()),
_ => 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,
) -> Result<String> {
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 json_data = json!({
"chat_session_id": chat_session_id,
"parent_message_id": parent_message_id,
"prompt": prompt,
"ref_file_ids": [],
"thinking_enabled": thinking_mode.as_bool(),
"search_enabled": search_mode.as_bool(),
"model": self.model,
});
let challenge = self.get_pow_challenge().await?;
let pow_response = self.pow_solver.solve_challenge(&challenge)?;
let mut headers = self.default_headers(Some(&pow_response));
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)?);
}
let response = match self.http
.post(&format!("{}/chat/completion", BASE_URL))
.headers(headers)
.json(&json_data)
.send()
.await
{
Ok(resp) => {
if !resp.status().is_success() {
let status = resp.status();
let error_text = resp.text().await?;
match status.as_u16() {
401 => return Err(anyhow!("Authentication error: Invalid or expired token")),
429 => return Err(anyhow!("Rate limit exceeded")),
_ => return Err(anyhow!("API request failed: {} - {}", status, error_text)),
}
}
resp
},
Err(e) => return Err(anyhow!("Network error occurred during streaming: {}", e)),
};
let bytes = response.bytes().await?;
let text = String::from_utf8_lossy(&bytes);
let mut result = String::new();
for line in text.lines() {
if line.starts_with("data: ") {
let data_str = &line[6..];
match serde_json::from_str::<Value>(data_str) {
Ok(data) => {
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() {
result.push_str(text);
}
}
}
if let Some(finish_reason) = choice.get("finish_reason") {
if finish_reason.as_str() == Some("stop") {
break;
}
}
}
}
},
Err(e) => eprintln!("Error parsing JSON chunk: {}", e),
}
}
}
Ok(result)
}
}
pub fn load_cookies(cookie_file: &Path) -> Result<serde_json::Value> {
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)?;
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 {} is missing.", file_name),
}
}