1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
use regex::Regex; use reqwest::header::{HeaderMap, HeaderValue}; use serde::export::Formatter; use std::error::Error; use std::fmt::Display; use crate::api::ApiResponse; #[derive(Debug)] struct ClientBuildError; impl Display for ClientBuildError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Failed to build a client.") } } impl Error for ClientBuildError {} pub(crate) struct Client { client: reqwest::Client, } impl Client { pub fn new(client: reqwest::Client) -> Self { Self { client } } pub fn create(name: &str, version: &str) -> Result<Self, Box<dyn Error>> { let mut builder = reqwest::Client::builder(); let mut headers = HeaderMap::new(); headers.insert("X-YouTube-Client-Name", HeaderValue::from_str(name)?); headers.insert("X-YouTube-Client-Version", HeaderValue::from_str(version)?); builder = builder.default_headers(headers); Ok(Self::new(builder.build()?)) } #[allow(clippy::clone_double_ref)] pub async fn build() -> Result<Self, Box<dyn Error>> { let html = reqwest::get("https://www.youtube.com/") .await? .text() .await?; let pattern = Regex::new(r#""clientVersion":"([\d.]+)""#)?; let captures = pattern.captures(&html).unwrap(); let version = captures.get(1).ok_or(ClientBuildError {})?.as_str(); Self::create("1", version.clone()) } pub async fn fetch_upcoming_live_streams( &self, channel_id: &str, ) -> Result<ApiResponse, Box<dyn Error>> { let url = format!( "https://www.youtube.com/channel/{}/videos?view=2&live_view=502&pbj=1", channel_id ); Ok(self.client.get(&url).send().await?.json().await?) } }