#[cfg(feature = "impersonate")]
pub(super) mod auth;
pub mod helpers;
pub(super) mod oembed;
pub(super) mod types;
pub mod url;
pub use helpers::{extract_csrf_token, extract_username_from_url};
#[cfg(feature = "impersonate")]
pub use helpers::{parse_voyager_activity, parse_voyager_profile};
#[cfg(feature = "impersonate")]
pub use types::VoyagerProfileResponse;
#[cfg(feature = "impersonate")]
pub use types::{
VoyagerActivityResponse, VoyagerCommentary, VoyagerFeedElement, VoyagerText, VoyagerUpdateValue,
};
pub use url::{LinkedInUrlKind, classify_linkedin_url};
use anyhow::{Result, bail};
use async_trait::async_trait;
use super::{SiteContent, SiteProvider};
use crate::http_client::AcceleratedClient;
pub struct LinkedInProvider;
#[async_trait]
impl SiteProvider for LinkedInProvider {
fn name(&self) -> &'static str {
"linkedin"
}
fn matches(&self, url: &str) -> bool {
classify_linkedin_url(url).is_some()
}
async fn extract(
&self,
url: &str,
client: &AcceleratedClient,
cookies: Option<&str>,
_prefetched_html: Option<&[u8]>,
) -> Result<SiteContent> {
#[cfg(not(feature = "impersonate"))]
let _ = cookies;
let kind = classify_linkedin_url(url)
.ok_or_else(|| anyhow::anyhow!("URL does not match any LinkedIn pattern"))?;
#[cfg(feature = "impersonate")]
{
if let Some(cookie_header) = cookies
&& !cookie_header.is_empty()
{
match auth::fetch_authenticated(url, cookie_header, kind).await {
Ok(content) => return Ok(content),
Err(e) => {
tracing::warn!("LinkedIn authenticated fetch failed for {}: {}", url, e);
if !kind.has_oembed_fallback() {
return Err(e);
}
tracing::debug!("Falling back to oEmbed for {}", url);
}
}
}
if kind.requires_auth() && cookies.is_none_or(str::is_empty) {
bail!(
"LinkedIn {} pages require authentication.\n\
Use: nab fetch {} --cookies brave",
match kind {
LinkedInUrlKind::Profile => "profile",
LinkedInUrlKind::Company => "company",
LinkedInUrlKind::Activity => "activity",
_ => "content",
},
url
);
}
}
#[cfg(not(feature = "impersonate"))]
if kind.requires_auth() {
bail!(
"LinkedIn {} pages require the `impersonate` feature.\n\
Build with: cargo build --features impersonate\n\
Then: nab fetch {} --cookies brave",
match kind {
LinkedInUrlKind::Profile => "profile",
LinkedInUrlKind::Company => "company",
LinkedInUrlKind::Activity => "activity",
_ => "content",
},
url
);
}
oembed::fetch_oembed(url, client).await
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;