use std::path::{Path, PathBuf};
use serde_json::{json, Value};
use tail_fin_common::BrowserSession;
use tail_fin_common::cookies::{load_netscape_file, write_netscape_file};
use tail_fin_common::{tail_fin_dir, TailFinError};
pub const BEARER_TOKEN: &str =
"AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA";
pub fn default_cookies_path() -> PathBuf {
tail_fin_dir().join("twitter-cookies.txt")
}
pub async fn extract_ct0(session: &BrowserSession) -> Result<String, TailFinError> {
let cookies = session.get_cookies().await?;
for cookie in &cookies {
let name = cookie.get("name").and_then(|v| v.as_str()).unwrap_or("");
if name == "ct0" {
if let Some(value) = cookie.get("value").and_then(|v| v.as_str()) {
if !value.is_empty() {
return Ok(value.to_string());
}
}
}
}
Err(TailFinError::AuthRequired)
}
pub fn build_headers(ct0: &str) -> Value {
json!({
"Authorization": format!("Bearer {}", BEARER_TOKEN),
"X-Csrf-Token": ct0,
"X-Twitter-Auth-Type": "OAuth2Session",
"X-Twitter-Active-User": "yes",
"Content-Type": "application/json"
})
}
#[cfg(feature = "http")]
pub async fn export_cookies(chrome_host: &str, path: &Path) -> Result<usize, TailFinError> {
use tail_fin_common::cdp::find_tab_ws_url;
let ws_url = find_tab_ws_url(chrome_host, Some("x.com")).await?;
let mut tab = tail_fin_common::cdp::CdpTab::connect(&ws_url).await?;
let all_cookies = tab.get_all_cookies().await?;
let twitter_cookies: Vec<&Value> = all_cookies
.iter()
.filter(|c| {
let domain = c.get("domain").and_then(|v| v.as_str()).unwrap_or("");
domain.contains("twitter.com") || domain.contains("x.com")
})
.collect();
if twitter_cookies.is_empty() {
return Err(TailFinError::Api(
"No Twitter/X cookies found. Please log in to x.com in Chrome first.".into(),
));
}
let owned: Vec<Value> = twitter_cookies.into_iter().cloned().collect();
write_netscape_file(path, &owned)?;
Ok(owned.len())
}
pub fn load_cookies_from_file(path: &Path) -> Result<Vec<(String, String)>, TailFinError> {
load_netscape_file(path)
}
pub fn extract_auth_from_cookies(
cookies: &[(String, String)],
) -> Result<(String, String), TailFinError> {
let mut ct0 = None;
let mut auth_token = None;
for (name, value) in cookies {
match name.as_str() {
"ct0" => ct0 = Some(value.clone()),
"auth_token" => auth_token = Some(value.clone()),
_ => {}
}
}
match (ct0, auth_token) {
(Some(c), Some(a)) => Ok((c, a)),
(None, _) => Err(TailFinError::Api(
"ct0 cookie not found. Run `tail-fin --connect twitter export-cookies` to refresh."
.into(),
)),
(_, None) => Err(TailFinError::Api(
"auth_token not found. Run `tail-fin --connect twitter export-cookies` to refresh."
.into(),
)),
}
}