use std::path::PathBuf;
use tail_fin_common::TailFinError;
pub fn no_mode_error(service: &str, cmd: &str) -> TailFinError {
TailFinError::Api(format!(
"No connection mode specified for {service}.\n\
\x20 Use --connect to use browser mode:\n\
\x20 tail-fin --connect 127.0.0.1:9222 {service} {cmd}\n\
\x20 Or --cookies to use saved cookies:\n\
\x20 tail-fin --cookies {service} {cmd}\n\
\x20 Some adapters (e.g. spotify) auto-launch a stealth browser when no mode is given."
))
}
pub struct Ctx {
pub connect: Option<String>,
pub cookies: Option<String>,
pub headed: bool,
}
pub fn default_cookies_path(site: &str) -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
PathBuf::from(home)
.join(".tail-fin")
.join(format!("{}-cookies.txt", site))
}
pub fn resolve_cookies_path(cookies_flag: &str, site: &str) -> PathBuf {
if cookies_flag == "auto" {
default_cookies_path(site)
} else {
PathBuf::from(cookies_flag)
}
}
pub async fn browser_session(
host: &str,
headed: bool,
) -> Result<night_fury_core::BrowserSession, TailFinError> {
Ok(night_fury_core::BrowserSession::builder()
.connect_to(format!("ws://{}", host))
.headed(headed)
.build()
.await?)
}
pub async fn launch_browser(headed: bool) -> Result<night_fury_core::BrowserSession, TailFinError> {
Ok(night_fury_core::BrowserSession::builder()
.headed(headed)
.build()
.await?)
}
pub async fn auto_launch_stealth(
url: &str,
headed: bool,
) -> Result<night_fury_core::BrowserSession, TailFinError> {
eprintln!("No connection mode specified. Launching stealth browser...");
Ok(night_fury_core::BrowserSession::builder()
.headed(headed)
.cloudflare_timeout(std::time::Duration::from_secs(30))
.launch_stealth(url)
.await?)
}
pub async fn launch_stealth_session(
url: &str,
headed: bool,
) -> Result<night_fury_core::BrowserSession, TailFinError> {
Ok(night_fury_core::BrowserSession::builder()
.headed(headed)
.launch_stealth(url)
.await?)
}
pub fn require_browser(
connect: &Option<String>,
service: &str,
action_name: &str,
) -> Result<String, TailFinError> {
match connect {
Some(host) => Ok(host.clone()),
None => Err(TailFinError::Api(format!(
"`{service} {action_name}` requires browser mode (--connect).\n\
\x20 Use: tail-fin --connect 127.0.0.1:9222 {service} {action_name} ..."
))),
}
}
pub async fn require_browser_session(
ctx: &Ctx,
service: &str,
) -> Result<night_fury_core::BrowserSession, TailFinError> {
if ctx.cookies.is_some() {
return Err(TailFinError::Api(format!(
"{service} cookie mode is not supported.\n\
\x20 Use --connect for browser mode."
)));
}
let host = match ctx.connect.as_deref() {
Some(h) => h,
None => {
return Err(TailFinError::Api(format!(
"{service} requires --connect.\n\
\x20 Example: tail-fin --connect 127.0.0.1:9222 {service} ..."
)));
}
};
browser_session(host, ctx.headed).await
}
pub fn print_json(value: &(impl serde::Serialize + ?Sized)) -> Result<(), TailFinError> {
println!("{}", serde_json::to_string_pretty(value)?);
Ok(())
}
pub fn print_list(
key: &str,
items: &impl serde::Serialize,
count: usize,
) -> Result<(), TailFinError> {
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
key: items,
"count": count,
}))?
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_cookies_path_auto_ends_with_site_cookies_txt() {
let p = resolve_cookies_path("auto", "twitter");
assert_eq!(
p.file_name().and_then(|n| n.to_str()),
Some("twitter-cookies.txt"),
"unexpected filename in: {}",
p.display()
);
assert_eq!(
p.parent()
.and_then(|pp| pp.file_name())
.and_then(|n| n.to_str()),
Some(".tail-fin"),
"unexpected parent directory in: {}",
p.display()
);
}
#[test]
fn resolve_cookies_path_explicit_is_verbatim() {
let p = resolve_cookies_path("/explicit/cookies.txt", "twitter");
assert_eq!(p.to_string_lossy(), "/explicit/cookies.txt");
}
#[test]
fn require_browser_errors_when_connect_missing() {
let err = require_browser(&None, "twitter", "timeline").unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("--connect"),
"error should mention --connect; got: {msg}"
);
assert!(
msg.contains("twitter timeline"),
"error should mention the service/action; got: {msg}"
);
}
#[test]
fn require_browser_returns_host_when_present() {
let host = require_browser(&Some("127.0.0.1:9222".to_string()), "twitter", "timeline")
.expect("should succeed when --connect is provided");
assert_eq!(host, "127.0.0.1:9222");
}
}