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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use anyhow::Result;
use lazy_regex::{lazy_regex, Lazy, Regex};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

mod utils;
pub mod yt_initial_data;
pub mod yt_initial_player_response;
pub mod ytcfg;

use utils::match_regex_and_parse;

pub use yt_initial_data::YtInitialData;
pub use yt_initial_player_response::YtInitialPlayerResponse;
pub use ytcfg::Ytcfg;

static YTCFG_REGEX: Lazy<Regex> = lazy_regex!(r"ytInitialData\s*=\s*(\{.+?\});");
static YT_INITIAL_DATA_REGEX: Lazy<Regex> = lazy_regex!(r"ytInitialData\s*=\s*(\{.+?\});");
static YT_INITIAL_PLAYER_RESPONSE_REGEX: Lazy<Regex> =
    lazy_regex!(r"ytInitialPlayerResponse\s*=\s*(\{.+?\});");

#[derive(Debug, Deserialize, Serialize)]
pub struct YoutubeInfo {
    pub yt_initial_data: Option<YtInitialData>,
    pub yt_initial_player_response: Option<YtInitialPlayerResponse>,
    pub ytcfg: Option<Ytcfg>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct YoutubeInfoRaw {
    pub yt_initial_data: Option<serde_json::Value>,
    pub yt_initial_player_response: Option<serde_json::Value>,
    pub ytcfg: Option<serde_json::Value>,
}

impl TryFrom<YoutubeInfoRaw> for YoutubeInfo {
    type Error = anyhow::Error;

    fn try_from(value: YoutubeInfoRaw) -> Result<Self, Self::Error> {
        let yt_initial_data: Option<YtInitialData> = value
            .yt_initial_data
            .map(|s| serde_json::from_value(s))
            .transpose()?;

        let yt_initial_player_response: Option<YtInitialPlayerResponse> = value
            .yt_initial_player_response
            .map(|s| serde_json::from_value(s))
            .transpose()?;

        let ytcfg: Option<Ytcfg> = value.ytcfg.map(|s| serde_json::from_value(s)).transpose()?;

        Ok(YoutubeInfo {
            yt_initial_data,
            yt_initial_player_response,
            ytcfg,
        })
    }
}

pub fn parse_info_raw(page: &str) -> Result<YoutubeInfoRaw> {
    Ok(YoutubeInfoRaw {
        yt_initial_data: match_regex_and_parse(&page, &YT_INITIAL_DATA_REGEX, 1)?,
        yt_initial_player_response: match_regex_and_parse(
            &page,
            &YT_INITIAL_PLAYER_RESPONSE_REGEX,
            1,
        )?,
        ytcfg: match_regex_and_parse(&page, &YTCFG_REGEX, 1)?,
    })
}

pub fn parse_info(page: &str) -> Result<YoutubeInfo> {
    Ok(YoutubeInfo {
        yt_initial_data: match_regex_and_parse(&page, &YT_INITIAL_DATA_REGEX, 1)?,
        yt_initial_player_response: match_regex_and_parse(
            &page,
            &YT_INITIAL_PLAYER_RESPONSE_REGEX,
            1,
        )?,
        ytcfg: match_regex_and_parse(&page, &YTCFG_REGEX, 1)?,
    })
}