tydle 0.1.15

YouTube video extractor written in Rust that can be used anywhere in web or native environments, based on an extremely small subset of yt-dlp.
Documentation
use std::collections::HashMap;

use anyhow::Result;
use serde_json::{Map, Value};

use crate::{
    cache::CacheAccess,
    extractor::{
        auth::ExtractorAuthHandle,
        client::{INNERTUBE_CLIENTS, InnerTubeClient},
        extract::YtExtractor,
    },
    yt_interface::{PREFERRED_LOCALE, YtClient},
};

pub trait ExtractorYtCfgHandle {
    fn select_api_hostname(&self, default_client: Option<&YtClient>) -> &str;
    fn select_client_version(&self, default_client: Option<&YtClient>) -> &str;
    fn select_context(
        &self,
        ytcfg: Option<&HashMap<String, Value>>,
        default_client: Option<&YtClient>,
    ) -> Result<HashMap<String, Value>>;
    fn select_visitor_data(&self, ytcfgs: &[&HashMap<String, Value>]) -> Option<String>;
    fn select_default_ytcfg(&self, default_client: Option<&YtClient>) -> Result<InnerTubeClient>;
}

impl<P, C> ExtractorYtCfgHandle for YtExtractor<P, C>
where
    P: CacheAccess<(String, String)> + Send + Sync,
    C: CacheAccess,
{
    fn select_api_hostname(&self, default_client: Option<&YtClient>) -> &str {
        let client = default_client.unwrap_or(&self.tydle_options.default_client);
        let innertube_client = INNERTUBE_CLIENTS.get(client).unwrap();
        return innertube_client.innertube_host;
    }

    fn select_client_version(&self, default_client: Option<&YtClient>) -> &str {
        let client = default_client.unwrap_or(&self.tydle_options.default_client);
        let innertube_client = INNERTUBE_CLIENTS.get(client).unwrap();

        let innertube_client_context = innertube_client.innertube_context.get("client").unwrap();
        innertube_client_context
            .get("clientVersion")
            .unwrap()
            .as_str()
            .unwrap()
    }

    fn select_context(
        &self,
        ytcfg: Option<&HashMap<String, Value>>,
        default_client: Option<&YtClient>,
    ) -> Result<HashMap<String, Value>> {
        let client = default_client.unwrap_or(&self.tydle_options.default_client);

        let innertube_client = match ytcfg {
            Some(cfg) => {
                if !cfg.is_empty() {
                    cfg
                } else {
                    &INNERTUBE_CLIENTS
                        .get(client)
                        .unwrap()
                        .to_json_val_hashmap()?
                }
            }
            None => &INNERTUBE_CLIENTS
                .get(client)
                .unwrap()
                .to_json_val_hashmap()?,
        };

        let mut client_context = innertube_client
            .get("INNERTUBE_CONTEXT")
            .and_then(|v| v.get("client"))
            .cloned()
            .unwrap_or(Value::Object(Map::new()));

        if let Some(map) = client_context.as_object_mut() {
            map.insert(
                "hl".to_string(),
                Value::String(PREFERRED_LOCALE.to_string()),
            );
            map.insert("timeZone".to_string(), Value::String("UTC".to_string()));
            map.insert("utcOffsetMinutes".to_string(), Value::Number(0.into()));
        }

        if let Value::Object(map) = client_context {
            Ok(map.into_iter().collect())
        } else {
            Ok(HashMap::new())
        }
    }

    fn select_visitor_data(&self, ytcfgs: &[&HashMap<String, Value>]) -> Option<String> {
        for ytcfg in ytcfgs {
            if let Some(v) = ytcfg.get("VISITOR_DATA").and_then(|v| v.as_str()) {
                return Some(v.to_string());
            }

            if let Some(v) = ytcfg
                .get("INNERTUBE_CONTEXT")
                .and_then(|v| v.get("client"))
                .and_then(|v| v.get("visitorData"))
                .and_then(|v| v.as_str())
            {
                return Some(v.to_string());
            }

            if let Some(v) = ytcfg
                .get("responseContext")
                .and_then(|v| v.get("visitorData"))
                .and_then(|v| v.as_str())
            {
                return Some(v.to_string());
            }
        }

        None
    }

    fn select_default_ytcfg(&self, default_client: Option<&YtClient>) -> Result<InnerTubeClient> {
        let client = default_client.unwrap_or(&self.tydle_options.default_client);
        let mut ytcfg = INNERTUBE_CLIENTS.get(client).cloned().unwrap();

        if let (Some(auth_ua), true) = (&ytcfg.authenticated_user_agent, self.is_authenticated()?) {
            let innertube_client_context = ytcfg
                .innertube_context
                .entry("client")
                .or_insert_with(HashMap::new);

            innertube_client_context.insert("userAgent", (*auth_ua).into());
        }

        Ok(ytcfg)
    }
}