use std::time::Duration;
use async_trait::async_trait;
use tail_fin_core::{
AuthFailureKind, BrowserSession, FailureIndicators, SessionStatus, Site, SiteError,
};
pub struct YoutubeSite;
#[async_trait]
impl Site for YoutubeSite {
fn id(&self) -> &'static str {
"youtube"
}
fn display_name(&self) -> &'static str {
"YouTube"
}
fn cookie_domain_patterns(&self) -> &'static [&'static str] {
&["*.youtube.com", "*.google.com"]
}
fn refresh_url(&self) -> &'static str {
"https://www.youtube.com/"
}
fn refresh_interval_min(&self) -> Duration {
Duration::from_secs(180)
}
async fn validate(&self, session: &BrowserSession) -> Result<SessionStatus, SiteError> {
let status = session
.http_ping("https://www.youtube.com/")
.await
.map_err(|e| SiteError::ValidationFailed {
site: self.id(),
reason: format!("homepage ping: {e}"),
})?;
Ok(match status {
200 => SessionStatus::Valid,
401 | 403 => SessionStatus::Expired,
429 => SessionStatus::Blocked {
reason: "YouTube rate limit".into(),
retry_after: Some(Duration::from_secs(300)),
},
0 => SessionStatus::Unknown,
other => SessionStatus::Degrading {
estimated_expiry: None,
hint: format!("unexpected HTTP {other}"),
},
})
}
fn detect_auth_failure(&self, indicators: &FailureIndicators) -> Option<AuthFailureKind> {
match indicators.status {
Some(401) | Some(403) => Some(AuthFailureKind::CookieExpired),
Some(429) => Some(AuthFailureKind::RateLimited {
retry_after: Duration::from_secs(300),
}),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn indicators(status: u16) -> FailureIndicators {
FailureIndicators {
status: Some(status),
..Default::default()
}
}
#[test]
fn identity_fields() {
let s = YoutubeSite;
assert_eq!(s.id(), "youtube");
assert_eq!(s.display_name(), "YouTube");
assert_eq!(
s.cookie_domain_patterns(),
&["*.youtube.com", "*.google.com"]
);
assert_eq!(s.refresh_url(), "https://www.youtube.com/");
}
#[test]
fn detect_cookie_expired_on_401_and_403() {
for status in [401, 403] {
assert!(matches!(
YoutubeSite.detect_auth_failure(&indicators(status)),
Some(AuthFailureKind::CookieExpired)
));
}
}
#[test]
fn detect_rate_limited_on_429() {
match YoutubeSite.detect_auth_failure(&indicators(429)) {
Some(AuthFailureKind::RateLimited { retry_after }) => {
assert_eq!(retry_after, Duration::from_secs(300));
}
other => panic!("expected RateLimited, got {other:?}"),
}
}
#[test]
fn detect_returns_none_for_ok_and_unknown() {
assert!(YoutubeSite.detect_auth_failure(&indicators(200)).is_none());
assert!(YoutubeSite.detect_auth_failure(&indicators(500)).is_none());
}
}