modified: Cargo.lock
modified: Cargo.toml modified: src/main.rs new file: src/objects.rs
This commit is contained in:
parent
312dd9bc90
commit
d0422a7ad9
1571
Cargo.lock
generated
1571
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
73
src/main.rs
73
src/main.rs
@ -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
157
src/objects.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user