modified: Cargo.lock

modified:   Cargo.toml
	modified:   src/main.rs
	new file:   src/objects.rs
This commit is contained in:
Michael Wain 2025-02-25 15:26:22 +03:00
parent 312dd9bc90
commit d0422a7ad9
4 changed files with 1804 additions and 1 deletions

1571
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.12.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.138"

View File

@ -1,3 +1,74 @@
fn main() {
use std::error::Error;
use objects::YoutubeChannel;
mod objects;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hello, world!!");
let rid = get_channel("UCpDEe6JKiI5o8luXpk6UuiA".to_string()).await.unwrap();
get_playlists("UCpDEe6JKiI5o8luXpk6UuiA".to_string(), rid).await;
Ok(())
}
pub async fn get_playlists(channel_id: String, request_id: String) -> Result<(), Box<dyn Error>> {
let client = reqwest::Client::new();
let d= serde_json::json!({
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20201210.01.00",
"originalUrl": "https://www.youtube.com/",
"platform": "DESKTOP",
"clientFormFactor": "UNKNOWN_FORM_FACTOR",
"newVisitorCookie": true
}
},
"browseId": channel_id,
"params": request_id
});
let resp = client.post("https://www.youtube.com/youtubei/v1/browse?prettyPrint=false")
.body(d.to_string())
.send()
.await?
.text()
.await?;
println!("{}", resp);
let resp: YoutubeChannel = serde_json::from_str(&resp).unwrap();
println!("{:?}", resp.get_playlists());
Ok(())
}
pub async fn get_channel(channel_id: String) -> Result<String, Box<dyn Error>>{
let client = reqwest::Client::new();
let resp = client.post("https://www.youtube.com/youtubei/v1/browse?prettyPrint=false")
.body(r#"{
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20201210.01.00",
"originalUrl": "https://www.youtube.com/",
"platform": "DESKTOP",
"clientFormFactor": "UNKNOWN_FORM_FACTOR",
"newVisitorCookie": true
}
},
"browseId": "UCpDEe6JKiI5o8luXpk6UuiA",
"params": "EgZ2aWRlb3M%3D"
}"#)
.send()
.await?
.text()
.await?;
let resp: YoutubeChannel = serde_json::from_str(&resp).unwrap();
Ok(resp.get_playlists_request_str().unwrap())
}

157
src/objects.rs Normal file
View File

@ -0,0 +1,157 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct YoutubeChannel {
pub contents: ChannelContents,
}
#[derive(Deserialize)]
pub struct ChannelContents {
pub twoColumnBrowseResultsRenderer: TwoColumns,
}
#[derive(Deserialize)]
pub struct TwoColumns {
pub tabs: Vec<ChannelTab>
}
#[derive(Deserialize)]
pub struct ChannelTab {
pub tabRenderer: Option<TabRenderer>
}
#[derive(Deserialize)]
pub struct TabRenderer {
pub title: String,
pub endpoint: TabEndpoint,
pub content: Option<TabContent>
}
#[derive(Deserialize)]
pub struct SectionListRenderer{
pub contents: Option<Vec<Content>>
}
#[derive(Deserialize)]
pub struct Content {
pub itemSectionRenderer: Option<ItemSectionRenderer>
}
#[derive(Deserialize)]
pub struct ItemSectionRenderer {
pub contents: Vec<ItemSectionContent>
}
#[derive(Deserialize)]
pub struct ItemSectionContent {
pub gridRenderer: GridRenderer
}
#[derive(Deserialize)]
pub struct GridRenderer {
pub items: Vec<GridItem>
}
#[derive(Deserialize)]
pub struct GridItem {
pub lockupViewModel: LookUpViewModel
}
#[derive(Deserialize)]
pub struct LookUpViewModel {
pub metadata: ModelMetadata
}
#[derive(Deserialize)]
pub struct ModelMetadata {
pub lockupMetadataViewModel: LockupMetadataViewModel
}
#[derive(Deserialize)]
pub struct LockupMetadataViewModel {
pub title: PlaylistTitle,
pub metadata: PlaylistMetadata,
}
#[derive(Deserialize)]
pub struct PlaylistMetadata {
pub contentMetadataViewModel: ContentMetadataViewModel
}
#[derive(Deserialize)]
pub struct ContentMetadataViewModel {
pub metadataRows: Vec<MetadataRow>
}
#[derive(Deserialize)]
pub struct MetadataRow {
pub metadataParts: Vec<PlaylistTitle>
}
#[derive(Deserialize, Debug)]
pub struct PlaylistTitle {
pub content: Option<String>,
pub commandRuns: Option<Vec<CommandRun>>,
}
#[derive(Deserialize, Debug)]
pub struct CommandRun {
onTap: OnTap
}
#[derive(Deserialize, Debug)]
pub struct OnTap {
innertubeCommand: InnertubeCommand
}
#[derive(Deserialize, Debug)]
pub struct InnertubeCommand {
pub browseEndpoint: BrowseEndpoint,
}
#[derive(Deserialize)]
pub struct TabContent {
pub sectionListRenderer: SectionListRenderer
}
#[derive(Deserialize)]
pub struct TabEndpoint {
pub browseEndpoint: BrowseEndpoint,
}
#[derive(Deserialize, Debug)]
pub struct BrowseEndpoint {
pub browseId: String,
pub params: Option<String>,
}
impl YoutubeChannel {
pub fn get_playlists_request_str(&self) -> Option<String> {
for tab in &self.contents.twoColumnBrowseResultsRenderer.tabs {
if let Some(t) = &tab.tabRenderer {
if t.title == String::from("Playlists") {
return Some(t.endpoint.browseEndpoint.params.as_ref().unwrap().clone());
}
}
}
None
}
pub fn get_playlists(&self) -> Vec<(String, String)> {
let mut arr = Vec::new();
for tab in &self.contents.twoColumnBrowseResultsRenderer.tabs {
if let Some(t) = &tab.tabRenderer {
if t.title == String::from("Playlists") {
for item in &t.content.as_ref().unwrap().sectionListRenderer.contents.as_ref().unwrap()[0].itemSectionRenderer.as_ref().unwrap().contents[0].gridRenderer.items {
let title = item.lockupViewModel.metadata.lockupMetadataViewModel.title.content.clone().unwrap_or("!".to_string());
println!("{:#?}", item.lockupViewModel.metadata.lockupMetadataViewModel.metadata.contentMetadataViewModel.metadataRows[1].metadataParts[0]);
let url = item.lockupViewModel.metadata.lockupMetadataViewModel.metadata.contentMetadataViewModel.metadataRows[1].metadataParts[0].commandRuns.as_ref().unwrap()[0].onTap.innertubeCommand.browseEndpoint.browseId.clone();
arr.push((title, url));
}
}
}
}
arr
}
}