1use crate::enums::{gemini_headers, rotate_cookies_headers, Endpoint, Model};
4use crate::error::{Error, Result};
5use crate::utils::upload_file;
6
7use rand::Rng;
8use regex::Regex;
9use reqwest::cookie::Jar;
10use reqwest::{Client, Url};
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13
14use std::path::Path;
15use std::sync::Arc;
16use std::time::Duration;
17
18const SNLM0E_PATTERN: &str = r#"["']SNlM0e["']\s*:\s*["']([^"']+)["']"#;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ChatResponse {
23 pub content: String,
25 pub conversation_id: String,
27 pub response_id: String,
29 pub factuality_queries: Option<Value>,
31 pub text_query: String,
33 pub choices: Vec<Choice>,
35 pub error: bool,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Choice {
44 pub id: String,
46 pub content: String,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct SavedConversation {
53 pub conversation_name: String,
55 #[serde(rename = "_reqid")]
57 pub reqid: u32,
58 pub conversation_id: String,
60 pub response_id: String,
62 pub choice_id: String,
64 #[serde(rename = "SNlM0e")]
66 pub snlm0e: String,
67 pub model_name: String,
69 pub timestamp: String,
71}
72
73pub struct AsyncChatbot {
103 client: Client,
104 snlm0e: String,
105 conversation_id: String,
106 response_id: String,
107 choice_id: String,
108 reqid: u32,
109 secure_1psidts: String,
110 model: Model,
111 proxy: Option<String>,
112}
113
114impl AsyncChatbot {
115 pub async fn new(
135 secure_1psid: &str,
136 secure_1psidts: &str,
137 model: Model,
138 proxy: Option<&str>,
139 timeout: u64,
140 ) -> Result<Self> {
141 if secure_1psid.is_empty() {
142 return Err(Error::Authentication(
143 "__Secure-1PSID cookie is required".to_string(),
144 ));
145 }
146
147 let jar = Jar::default();
149 let url: Url = "https://gemini.google.com".parse().unwrap();
150 jar.add_cookie_str(
152 &format!(
153 "__Secure-1PSID={}; Domain=.google.com; Path=/; Secure; SameSite=None",
154 secure_1psid
155 ),
156 &url,
157 );
158 jar.add_cookie_str(
159 &format!(
160 "__Secure-1PSIDTS={}; Domain=.google.com; Path=/; Secure; SameSite=None",
161 secure_1psidts
162 ),
163 &url,
164 );
165
166 let mut headers = gemini_headers();
168 if let Some(model_headers) = model.headers() {
169 headers.extend(model_headers);
170 }
171
172 let mut builder = Client::builder()
174 .cookie_provider(Arc::new(jar))
175 .default_headers(headers)
176 .timeout(Duration::from_secs(timeout));
177
178 if let Some(proxy_url) = proxy {
179 builder = builder.proxy(reqwest::Proxy::all(proxy_url)?);
180 }
181
182 let client = builder.build()?;
183
184 let mut chatbot = Self {
185 client,
186 snlm0e: String::new(),
187 conversation_id: String::new(),
188 response_id: String::new(),
189 choice_id: String::new(),
190 reqid: rand::thread_rng().gen_range(1000000..9999999),
191 secure_1psidts: secure_1psidts.to_string(),
192 model,
193 proxy: proxy.map(|s| s.to_string()),
194 };
195
196 chatbot.snlm0e = chatbot.get_snlm0e().await?;
198
199 Ok(chatbot)
200 }
201
202 async fn get_snlm0e(&mut self) -> Result<String> {
204 if self.secure_1psidts.is_empty() {
206 let _ = self.rotate_cookies().await;
207 }
208
209 let response = self.client.get(Endpoint::Init.url()).send().await?;
210
211 let status = response.status();
212 let text = response.text().await?;
213
214 if !status.is_success() {
215 if status.as_u16() == 401 || status.as_u16() == 403 {
216 return Err(Error::Authentication(format!(
217 "Authentication failed (status {}). Check cookies.",
218 status
219 )));
220 }
221 return Err(Error::Parse(format!("HTTP error: {}", status)));
222 }
223
224 if text.contains("\"identifier-shown\"")
227 || text.contains("SignIn?continue")
228 || text.contains("Sign in - Google Accounts")
229 {
230 return Err(Error::Authentication(
231 "Authentication failed. Cookies might be invalid or expired.".to_string(),
232 ));
233 }
234
235 let re = Regex::new(SNLM0E_PATTERN).unwrap();
237 match re.captures(&text) {
238 Some(caps) => Ok(caps.get(1).unwrap().as_str().to_string()),
239 None => {
240 if text.contains("429") {
241 Err(Error::Parse(
242 "SNlM0e not found. Rate limit likely exceeded.".to_string(),
243 ))
244 } else {
245 Err(Error::Parse(
246 "SNlM0e value not found in response. Check cookie validity.".to_string(),
247 ))
248 }
249 }
250 }
251 }
252
253 async fn rotate_cookies(&mut self) -> Result<Option<String>> {
255 let response = self
256 .client
257 .post(Endpoint::RotateCookies.url())
258 .headers(rotate_cookies_headers())
259 .body(r#"[000,"-0000000000000000000"]"#)
260 .send()
261 .await?;
262
263 if !response.status().is_success() {
264 return Ok(None);
265 }
266
267 for cookie in response.cookies() {
271 if cookie.name() == "__Secure-1PSIDTS" {
272 let new_value = cookie.value().to_string();
273 self.secure_1psidts = new_value.clone();
274 return Ok(Some(new_value));
275 }
276 }
277
278 Ok(None)
279 }
280
281 pub async fn ask(&mut self, message: &str, image: Option<&[u8]>) -> Result<ChatResponse> {
300 if self.snlm0e.is_empty() {
301 return Err(Error::NotInitialized(
302 "AsyncChatbot not properly initialized. SNlM0e is missing.".to_string(),
303 ));
304 }
305
306 let image_upload_id = if let Some(img_data) = image {
308 Some(upload_file(img_data, self.proxy.as_deref()).await?)
309 } else {
310 None
311 };
312
313 let message_struct: Value = if let Some(ref upload_id) = image_upload_id {
315 serde_json::json!([
316 [message],
317 [[[upload_id, 1]]],
318 [&self.conversation_id, &self.response_id, &self.choice_id]
319 ])
320 } else {
321 serde_json::json!([
322 [message],
323 null,
324 [&self.conversation_id, &self.response_id, &self.choice_id]
325 ])
326 };
327
328 let freq_value = serde_json::json!([null, serde_json::to_string(&message_struct)?]);
330 let params = [
331 ("bl", "boq_assistant-bard-web-server_20240625.13_p0"),
332 ("_reqid", &self.reqid.to_string()),
333 ("rt", "c"),
334 ];
335
336 let form_data = [
337 ("f.req", serde_json::to_string(&freq_value)?),
338 ("at", self.snlm0e.clone()),
339 ];
340
341 let response = self
342 .client
343 .post(Endpoint::Generate.url())
344 .query(¶ms)
345 .form(&form_data)
346 .send()
347 .await?;
348
349 if !response.status().is_success() {
350 return Err(Error::Network(response.error_for_status().unwrap_err()));
351 }
352
353 let text = response.text().await?;
354 self.parse_response(&text)
355 }
356
357 fn parse_response(&mut self, text: &str) -> Result<ChatResponse> {
359 let lines: Vec<&str> = text.lines().collect();
360 if lines.len() < 3 {
361 return Err(Error::Parse(format!(
362 "Unexpected response format. Content: {}...",
363 &text[..text.len().min(200)]
364 )));
365 }
366
367 let mut body: Option<Value> = None;
369
370 for line in &lines {
371 if line.is_empty() || *line == ")]}" {
373 continue;
374 }
375
376 let mut clean_line = *line;
377 if clean_line.starts_with(")]}") {
378 clean_line = clean_line.get(4..).unwrap_or("").trim();
379 }
380
381 if !clean_line.starts_with('[') {
382 continue;
383 }
384
385 if let Ok(response_json) = serde_json::from_str::<Value>(clean_line) {
386 if let Some(arr) = response_json.as_array() {
387 for part in arr {
388 if let Some(part_arr) = part.as_array() {
389 if part_arr.len() > 2
390 && part_arr.first().and_then(|v| v.as_str()) == Some("wrb.fr")
391 {
392 if let Some(inner_str) = part_arr.get(2).and_then(|v| v.as_str()) {
393 if let Ok(main_part) = serde_json::from_str::<Value>(inner_str)
394 {
395 if main_part
396 .as_array()
397 .map(|a| a.len() > 4 && !a[4].is_null())
398 .unwrap_or(false)
399 {
400 body = Some(main_part);
401 break;
402 }
403 }
404 }
405 }
406 }
407 }
408 }
409
410 if body.is_some() {
411 break;
412 }
413 }
414 }
415
416 let body = body.ok_or_else(|| {
417 Error::Parse("Failed to parse response body. No valid data found.".to_string())
418 })?;
419
420 let body_arr = body.as_array().unwrap();
422
423 let content = body_arr
426 .get(4)
427 .and_then(|v| v.as_array())
428 .and_then(|a| a.first())
429 .and_then(|v| v.as_array())
430 .and_then(|a| a.get(1))
431 .and_then(|v| v.as_array())
432 .and_then(|a| a.first())
433 .and_then(|v| v.as_str())
434 .unwrap_or("")
435 .to_string();
436
437 let conversation_id = body_arr
439 .get(1)
440 .and_then(|v| v.as_array())
441 .and_then(|a| a.first())
442 .and_then(|v| v.as_str())
443 .unwrap_or(&self.conversation_id)
444 .to_string();
445
446 let response_id = body_arr
447 .get(1)
448 .and_then(|v| v.as_array())
449 .and_then(|a| a.get(1))
450 .and_then(|v| v.as_str())
451 .unwrap_or(&self.response_id)
452 .to_string();
453
454 let factuality_queries = body_arr.get(3).cloned();
456 let text_query = body_arr
457 .get(2)
458 .and_then(|v| v.as_array())
459 .and_then(|a| a.first())
460 .and_then(|v| v.as_str())
461 .unwrap_or("")
462 .to_string();
463
464 let mut choices = Vec::new();
466 if let Some(candidates) = body_arr.get(4).and_then(|v| v.as_array()) {
467 for candidate in candidates {
468 if let Some(cand_arr) = candidate.as_array() {
469 if cand_arr.len() > 1 {
470 let id = cand_arr
471 .first()
472 .and_then(|v| v.as_str())
473 .unwrap_or("")
474 .to_string();
475 let choice_content = cand_arr
476 .get(1)
477 .and_then(|v| v.as_array())
478 .and_then(|a| a.first())
479 .and_then(|v| v.as_str())
480 .unwrap_or("")
481 .to_string();
482 choices.push(Choice {
483 id,
484 content: choice_content,
485 });
486 }
487 }
488 }
489 }
490
491 let choice_id = choices
492 .first()
493 .map(|c| c.id.clone())
494 .unwrap_or_else(|| self.choice_id.clone());
495
496 self.conversation_id = conversation_id.clone();
498 self.response_id = response_id.clone();
499 self.choice_id = choice_id;
500 self.reqid += rand::thread_rng().gen_range(1000..9000);
501
502 Ok(ChatResponse {
503 content,
504 conversation_id,
505 response_id,
506 factuality_queries,
507 text_query,
508 choices,
509 error: false,
510 })
511 }
512
513 pub async fn save_conversation(&self, file_path: &str, conversation_name: &str) -> Result<()> {
518 let mut conversations = self.load_conversations(file_path).await?;
519
520 let conversation_data = SavedConversation {
521 conversation_name: conversation_name.to_string(),
522 reqid: self.reqid,
523 conversation_id: self.conversation_id.clone(),
524 response_id: self.response_id.clone(),
525 choice_id: self.choice_id.clone(),
526 snlm0e: self.snlm0e.clone(),
527 model_name: self.model.name().to_string(),
528 timestamp: chrono_now(),
529 };
530
531 let mut found = false;
533 for conv in &mut conversations {
534 if conv.conversation_name == conversation_name {
535 *conv = conversation_data.clone();
536 found = true;
537 break;
538 }
539 }
540 if !found {
541 conversations.push(conversation_data);
542 }
543
544 if let Some(parent) = Path::new(file_path).parent() {
546 std::fs::create_dir_all(parent)?;
547 }
548
549 let json = serde_json::to_string_pretty(&conversations)?;
550 std::fs::write(file_path, json)?;
551
552 Ok(())
553 }
554
555 pub async fn load_conversations(&self, file_path: &str) -> Result<Vec<SavedConversation>> {
559 if !Path::new(file_path).exists() {
560 return Ok(Vec::new());
561 }
562
563 let content = std::fs::read_to_string(file_path)?;
564 let conversations: Vec<SavedConversation> = serde_json::from_str(&content)?;
565 Ok(conversations)
566 }
567
568 pub async fn load_conversation(
575 &mut self,
576 file_path: &str,
577 conversation_name: &str,
578 ) -> Result<bool> {
579 let conversations = self.load_conversations(file_path).await?;
580
581 for conv in conversations {
582 if conv.conversation_name == conversation_name {
583 self.reqid = conv.reqid;
584 self.conversation_id = conv.conversation_id;
585 self.response_id = conv.response_id;
586 self.choice_id = conv.choice_id;
587 self.snlm0e = conv.snlm0e;
588
589 if let Some(model) = Model::from_name(&conv.model_name) {
590 self.model = model;
591 }
592
593 return Ok(true);
594 }
595 }
596
597 Ok(false)
598 }
599
600 pub fn conversation_id(&self) -> &str {
602 &self.conversation_id
603 }
604
605 pub fn model(&self) -> &Model {
607 &self.model
608 }
609
610 pub fn reset(&mut self) {
614 self.conversation_id.clear();
615 self.response_id.clear();
616 self.choice_id.clear();
617 self.reqid = rand::thread_rng().gen_range(1000000..9999999);
618 }
619}
620
621fn chrono_now() -> String {
623 use std::time::{SystemTime, UNIX_EPOCH};
624 let duration = SystemTime::now()
625 .duration_since(UNIX_EPOCH)
626 .unwrap_or_default();
627 format!("{}", duration.as_secs())
628}