use crate::errors::{CouldNotRetrieveTranscript, CouldNotRetrieveTranscriptReason};
use reqwest::Client;
use serde_json::{json, Value};
pub struct InnerTubeClient {
client: Client,
}
impl InnerTubeClient {
pub fn new(client: Client) -> Self {
Self { client }
}
pub async fn get_transcript_data(
&self,
video_id: &str,
) -> Result<Value, CouldNotRetrieveTranscript> {
let url = "https://www.youtube.com/youtubei/v1/get_transcript";
let payload = json!({
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20231219.04.00",
"hl": "en",
"gl": "US"
}
},
"params": self.encode_transcript_params(video_id)
});
let response = self.client
.post(url)
.json(&payload)
.header("Content-Type", "application/json")
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
.send()
.await
.map_err(|e| CouldNotRetrieveTranscript {
video_id: video_id.to_string(),
reason: Some(CouldNotRetrieveTranscriptReason::YouTubeRequestFailed(
format!("InnerTube API request failed: {}", e)
)),
})?;
if !response.status().is_success() {
return Err(CouldNotRetrieveTranscript {
video_id: video_id.to_string(),
reason: Some(CouldNotRetrieveTranscriptReason::YouTubeRequestFailed(
format!("InnerTube API returned status: {}", response.status()),
)),
});
}
let data: Value = response
.json()
.await
.map_err(|e| CouldNotRetrieveTranscript {
video_id: video_id.to_string(),
reason: Some(CouldNotRetrieveTranscriptReason::YouTubeDataUnparsable(
format!("Failed to parse InnerTube response: {}", e),
)),
})?;
Ok(data)
}
fn encode_transcript_params(&self, video_id: &str) -> String {
use base64::{engine::general_purpose, Engine as _};
let params = json!({
"videoId": video_id
});
general_purpose::STANDARD.encode(params.to_string())
}
pub async fn get_transcript_list(
&self,
video_id: &str,
) -> Result<Value, CouldNotRetrieveTranscript> {
let url = "https://www.youtube.com/youtubei/v1/player";
let payload = json!({
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20231219.04.00",
"hl": "en",
"gl": "US"
}
},
"videoId": video_id
});
let response = self.client
.post(url)
.json(&payload)
.header("Content-Type", "application/json")
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
.send()
.await
.map_err(|e| CouldNotRetrieveTranscript {
video_id: video_id.to_string(),
reason: Some(CouldNotRetrieveTranscriptReason::YouTubeRequestFailed(
format!("InnerTube player API request failed: {}", e)
)),
})?;
if !response.status().is_success() {
return Err(CouldNotRetrieveTranscript {
video_id: video_id.to_string(),
reason: Some(CouldNotRetrieveTranscriptReason::YouTubeRequestFailed(
format!(
"InnerTube player API returned status: {}",
response.status()
),
)),
});
}
let data: Value = response
.json()
.await
.map_err(|e| CouldNotRetrieveTranscript {
video_id: video_id.to_string(),
reason: Some(CouldNotRetrieveTranscriptReason::YouTubeDataUnparsable(
format!("Failed to parse InnerTube player response: {}", e),
)),
})?;
Ok(data)
}
}