feat: add session headers when using cookie auth

This commit is contained in:
ThetaDev 2025-01-04 20:08:51 +01:00
parent 63f86b6e18
commit 3c95b52cea
No known key found for this signature in database
GPG key ID: E319D3C5148D65B6

View file

@ -277,6 +277,15 @@ struct OauthToken {
expires_at: OffsetDateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AuthCookie {
cookie: String,
#[serde(skip_serializing_if = "Option::is_none")]
account_syncid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
session_index: Option<String>,
}
impl OauthToken {
fn from_response(
value: OauthTokenResponseInner,
@ -293,6 +302,16 @@ impl OauthToken {
}
}
impl AuthCookie {
fn new(cookie: String) -> Self {
Self {
cookie,
account_syncid: None,
session_index: None,
}
}
}
const DEFAULT_UA: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0";
const MOBILE_UA: &str = "Mozilla/5.0 (Android 14; Mobile; rv:129.0) Gecko/129.0 Firefox/129.0";
const TV_UA: &str = "Mozilla/5.0 (SMART-TV; Linux; Tizen 5.0) AppleWebKit/538.1 (KHTML, like Gecko) Version/5.0 NativeTVAds Safari/538.1";
@ -489,7 +508,7 @@ struct CacheHolder {
clients: HashMap<ClientType, AsyncRwLock<CacheEntry<ClientData>>>,
deobf: AsyncRwLock<CacheEntry<DeobfData>>,
oauth_token: RwLock<Option<OauthToken>>,
auth_cookie: RwLock<Option<String>>,
auth_cookie: RwLock<Option<AuthCookie>>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
@ -501,7 +520,7 @@ struct CacheData {
#[serde(skip_serializing_if = "Option::is_none")]
oauth_token: Option<OauthToken>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_cookie: Option<String>,
auth_cookie: Option<AuthCookie>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
@ -1455,9 +1474,61 @@ impl RustyPipe {
///
/// I recommend to log in using Incognito mode, get the cookies from the devtools
/// and then close the page.
pub async fn set_auth_cookie<S: Into<String>>(&self, cookie: S) {
pub async fn set_auth_cookie<S: Into<String>>(&self, cookie: S) -> Result<(), Error> {
let mut auth_cookie = AuthCookie::new(cookie.into());
self.extract_session_headers(&mut auth_cookie).await?;
let mut c = self.inner.cache.auth_cookie.write().unwrap();
*c = Some(cookie.into());
*c = Some(auth_cookie);
Ok(())
}
/// Since YouTube allows multiple channels/profiles per account, cookie-authenticated requests must include
/// the X-Goog-AuthUser and X-Goog-PageId headers to specify which account should be used.
///
/// The header values are included in the ytcfg object which is embedded in the html code.
async fn extract_session_headers(&self, auth_cookie: &mut AuthCookie) -> Result<(), Error> {
let re_session_id = Regex::new(r#"""USER_SESSION_ID"":"[\d]+?""#).unwrap();
let re_sync_id = Regex::new(r#""datasyncId":"([\w|]+?)""#).unwrap();
let re_session_index = Regex::new(r#""SESSION_INDEX":"([\d]+?)""#).unwrap();
let req = self
.inner
.http
.get("https://www.youtube.com/results?search_query=")
.header(header::COOKIE, &auth_cookie.cookie)
.build()?;
let html = self.http_request_txt(&req).await?;
if !re_session_id.is_match(&html) {
return Err(Error::Auth(AuthError::NoLogin));
}
let datasync_id =
util::get_cg_from_regex(&re_sync_id, &html, 1).ok_or(Error::Extraction(
ExtractionError::InvalidData("could not find datasyncId on html page".into()),
))?;
// datasyncid is of the form "channel_syncid||user_syncid" for secondary channel
// and just "user_syncid||" for primary channel. We only want the channel_syncid
let (channel_syncid, user_syncid) =
datasync_id
.split_once("||")
.ok_or(Error::Extraction(ExtractionError::InvalidData(
"datasyncId does not contain || seperator".into(),
)))?;
auth_cookie.account_syncid = if user_syncid.is_empty() {
None
} else {
Some(channel_syncid.to_owned())
};
auth_cookie.session_index = Some(
util::get_cg_from_regex(&re_session_index, &html, 1).ok_or(Error::Extraction(
ExtractionError::InvalidData("could not find SESSION_INDEX on html page".into()),
))?,
);
Ok(())
}
}
@ -1842,10 +1913,17 @@ impl RustyPipeQuery {
.unwrap()
.clone()
.ok_or(Error::Auth(AuthError::NoLogin))?;
if let Some(auth_header) = Self::sapisidhash_header(&auth_cookie, ctype) {
if let Some(auth_header) = Self::sapisidhash_header(&auth_cookie.cookie, ctype) {
r = r.header(header::AUTHORIZATION, auth_header);
}
cookie = Some(auth_cookie);
if let Some(session_index) = auth_cookie.session_index {
r = r.header("X-Goog-AuthUser", session_index);
}
if let Some(account_syncid) = auth_cookie.account_syncid {
r = r.header("X-Goog-PageId", account_syncid);
}
cookie = Some(auth_cookie.cookie);
} else if ctype == ClientType::Tv {
let access_token = self.client.user_auth_access_token().await?;
r = r.header(header::AUTHORIZATION, format!("Bearer {}", access_token));