feat: add ChannelRss
- add documentation - small model refactor: rename player VideoPlayerDetails.thumbnails to thumbnail
This commit is contained in:
parent
6ac5bc3782
commit
305c3ee70e
29 changed files with 2222 additions and 118 deletions
|
|
@ -1,5 +1,4 @@
|
|||
use anyhow::{anyhow, bail, Result};
|
||||
use reqwest::Method;
|
||||
use serde::Serialize;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -68,7 +67,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"channel_videos",
|
||||
channel_id,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -89,7 +87,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"channel_videos_continuation",
|
||||
ctoken,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -111,7 +108,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"channel_playlists",
|
||||
channel_id,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -132,7 +128,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"channel_videos_continuation",
|
||||
ctoken,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -151,7 +146,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"channel_info",
|
||||
channel_id,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
|
|||
92
src/client/channel_rss.rs
Normal file
92
src/client/channel_rss.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{model::ChannelRss, report::Report};
|
||||
|
||||
use super::{response, RustyPipeQuery};
|
||||
|
||||
impl RustyPipeQuery {
|
||||
pub async fn channel_rss(&self, channel_id: &str) -> Result<ChannelRss> {
|
||||
let url = format!(
|
||||
"https://www.youtube.com/feeds/videos.xml?channel_id={}",
|
||||
channel_id
|
||||
);
|
||||
let xml = self
|
||||
.client
|
||||
.http_request_txt(self.client.inner.http.get(&url).build()?)
|
||||
.await?;
|
||||
|
||||
match quick_xml::de::from_str::<response::ChannelRss>(&xml) {
|
||||
Ok(feed) => Ok(feed.into()),
|
||||
Err(e) => {
|
||||
if let Some(reporter) = &self.client.inner.reporter {
|
||||
let report = Report {
|
||||
info: Default::default(),
|
||||
level: crate::report::Level::ERR,
|
||||
operation: "channel_rss".to_owned(),
|
||||
error: Some(e.to_string()),
|
||||
msgs: Vec::new(),
|
||||
deobf_data: None,
|
||||
http_request: crate::report::HTTPRequest {
|
||||
url: url,
|
||||
method: "GET".to_owned(),
|
||||
req_header: BTreeMap::new(),
|
||||
req_body: String::new(),
|
||||
status: 200,
|
||||
resp_body: xml,
|
||||
},
|
||||
};
|
||||
|
||||
reporter.report(&report);
|
||||
}
|
||||
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::File, io::BufReader, path::Path};
|
||||
|
||||
use chrono::{Datelike, Timelike};
|
||||
|
||||
use crate::{
|
||||
client::{response, RustyPipe},
|
||||
model::ChannelRss,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_channel_rss() {
|
||||
let rp = RustyPipe::builder().strict().build();
|
||||
let channel = rp
|
||||
.query()
|
||||
.channel_rss("UCHnyfMqiRRG1u-2MsSQLbXA")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(channel.id, "UCHnyfMqiRRG1u-2MsSQLbXA");
|
||||
assert_eq!(channel.name, "Veritasium");
|
||||
assert_eq!(channel.create_date.year(), 2010);
|
||||
assert_eq!(channel.create_date.month(), 7);
|
||||
assert_eq!(channel.create_date.day(), 21);
|
||||
assert_eq!(channel.create_date.hour(), 7);
|
||||
assert_eq!(channel.create_date.minute(), 18);
|
||||
|
||||
assert!(!channel.videos.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_channel_rss() {
|
||||
let xml_path = Path::new("testfiles/channel_rss/base.xml");
|
||||
let xml_file = File::open(xml_path).unwrap();
|
||||
|
||||
let feed: response::ChannelRss =
|
||||
quick_xml::de::from_reader(BufReader::new(xml_file)).unwrap();
|
||||
let map_res: ChannelRss = feed.into();
|
||||
|
||||
insta::assert_ron_snapshot!("map_channel_rss", map_res);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
//! YouTube API Client
|
||||
|
||||
mod channel;
|
||||
mod pagination;
|
||||
mod player;
|
||||
|
|
@ -5,6 +7,9 @@ mod playlist;
|
|||
mod response;
|
||||
mod video_details;
|
||||
|
||||
#[cfg(feature = "rss")]
|
||||
mod channel_rss;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -14,7 +19,7 @@ use fancy_regex::Regex;
|
|||
use log::{error, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::Rng;
|
||||
use reqwest::{header, Client, ClientBuilder, Method, Request, RequestBuilder, Response};
|
||||
use reqwest::{header, Client, ClientBuilder, Request, RequestBuilder, Response};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
|
|
@ -787,24 +792,16 @@ impl RustyPipeQuery {
|
|||
/// - `ctype`: Client type (`Desktop`, `DesktopMusic`, `Android`, ...)
|
||||
/// - `method`: HTTP method
|
||||
/// - `endpoint`: YouTube API endpoint (`https://www.youtube.com/youtubei/v1/<XYZ>?key=...`)
|
||||
async fn request_builder(
|
||||
&self,
|
||||
ctype: ClientType,
|
||||
method: Method,
|
||||
endpoint: &str,
|
||||
) -> RequestBuilder {
|
||||
async fn request_builder(&self, ctype: ClientType, endpoint: &str) -> RequestBuilder {
|
||||
match ctype {
|
||||
ClientType::Desktop => self
|
||||
.client
|
||||
.inner
|
||||
.http
|
||||
.request(
|
||||
method,
|
||||
format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_URL, endpoint, DESKTOP_API_KEY, DISABLE_PRETTY_PRINT_PARAMETER
|
||||
),
|
||||
)
|
||||
.post(format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_URL, endpoint, DESKTOP_API_KEY, DISABLE_PRETTY_PRINT_PARAMETER
|
||||
))
|
||||
.header(header::ORIGIN, "https://www.youtube.com")
|
||||
.header(header::REFERER, "https://www.youtube.com")
|
||||
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
||||
|
|
@ -817,16 +814,13 @@ impl RustyPipeQuery {
|
|||
.client
|
||||
.inner
|
||||
.http
|
||||
.request(
|
||||
method,
|
||||
format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBE_MUSIC_V1_URL,
|
||||
endpoint,
|
||||
DESKTOP_MUSIC_API_KEY,
|
||||
DISABLE_PRETTY_PRINT_PARAMETER
|
||||
),
|
||||
)
|
||||
.post(format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBE_MUSIC_V1_URL,
|
||||
endpoint,
|
||||
DESKTOP_MUSIC_API_KEY,
|
||||
DISABLE_PRETTY_PRINT_PARAMETER
|
||||
))
|
||||
.header(header::ORIGIN, "https://music.youtube.com")
|
||||
.header(header::REFERER, "https://music.youtube.com")
|
||||
.header(header::COOKIE, self.client.inner.consent_cookie.to_owned())
|
||||
|
|
@ -839,13 +833,10 @@ impl RustyPipeQuery {
|
|||
.client
|
||||
.inner
|
||||
.http
|
||||
.request(
|
||||
method,
|
||||
format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_URL, endpoint, DESKTOP_API_KEY, DISABLE_PRETTY_PRINT_PARAMETER
|
||||
),
|
||||
)
|
||||
.post(format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_URL, endpoint, DESKTOP_API_KEY, DISABLE_PRETTY_PRINT_PARAMETER
|
||||
))
|
||||
.header(header::ORIGIN, "https://www.youtube.com")
|
||||
.header(header::REFERER, "https://www.youtube.com")
|
||||
.header("X-YouTube-Client-Name", "1")
|
||||
|
|
@ -854,16 +845,13 @@ impl RustyPipeQuery {
|
|||
.client
|
||||
.inner
|
||||
.http
|
||||
.request(
|
||||
method,
|
||||
format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_GAPIS_URL,
|
||||
endpoint,
|
||||
ANDROID_API_KEY,
|
||||
DISABLE_PRETTY_PRINT_PARAMETER
|
||||
),
|
||||
)
|
||||
.post(format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_GAPIS_URL,
|
||||
endpoint,
|
||||
ANDROID_API_KEY,
|
||||
DISABLE_PRETTY_PRINT_PARAMETER
|
||||
))
|
||||
.header(
|
||||
header::USER_AGENT,
|
||||
format!(
|
||||
|
|
@ -876,16 +864,10 @@ impl RustyPipeQuery {
|
|||
.client
|
||||
.inner
|
||||
.http
|
||||
.request(
|
||||
method,
|
||||
format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_GAPIS_URL,
|
||||
endpoint,
|
||||
IOS_API_KEY,
|
||||
DISABLE_PRETTY_PRINT_PARAMETER
|
||||
),
|
||||
)
|
||||
.post(format!(
|
||||
"{}{}?key={}{}",
|
||||
YOUTUBEI_V1_GAPIS_URL, endpoint, IOS_API_KEY, DISABLE_PRETTY_PRINT_PARAMETER
|
||||
))
|
||||
.header(
|
||||
header::USER_AGENT,
|
||||
format!(
|
||||
|
|
@ -911,7 +893,6 @@ impl RustyPipeQuery {
|
|||
/// - `endpoint`: YouTube API endpoint (`https://www.youtube.com/youtubei/v1/<XYZ>?key=...`)
|
||||
/// - `body`: Serializable request body to be sent in json format
|
||||
/// - `deobf`: Deobfuscator (is passed to the mapper to deobfuscate stream URLs).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn execute_request_deobf<
|
||||
R: DeserializeOwned + MapResponse<M> + Debug,
|
||||
M,
|
||||
|
|
@ -921,13 +902,12 @@ impl RustyPipeQuery {
|
|||
ctype: ClientType,
|
||||
operation: &str,
|
||||
id: &str,
|
||||
method: Method,
|
||||
endpoint: &str,
|
||||
body: &B,
|
||||
deobf: Option<&Deobfuscator>,
|
||||
) -> Result<M> {
|
||||
let request = self
|
||||
.request_builder(ctype, method.clone(), endpoint)
|
||||
.request_builder(ctype, endpoint)
|
||||
.await
|
||||
.json(body)
|
||||
.build()?;
|
||||
|
|
@ -943,9 +923,7 @@ impl RustyPipeQuery {
|
|||
let create_report = |level: Level, error: Option<String>, msgs: Vec<String>| {
|
||||
if let Some(reporter) = &self.client.inner.reporter {
|
||||
let report = Report {
|
||||
package: "rustypipe".to_owned(),
|
||||
version: "0.1.0".to_owned(),
|
||||
date: chrono::Local::now(),
|
||||
info: Default::default(),
|
||||
level,
|
||||
operation: format!("{}({})", operation, id),
|
||||
error,
|
||||
|
|
@ -953,7 +931,7 @@ impl RustyPipeQuery {
|
|||
deobf_data: deobf.map(Deobfuscator::get_data),
|
||||
http_request: crate::report::HTTPRequest {
|
||||
url: request_url,
|
||||
method: method.to_string(),
|
||||
method: "POST".to_string(),
|
||||
req_header: request_headers
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
|
|
@ -1030,11 +1008,10 @@ impl RustyPipeQuery {
|
|||
ctype: ClientType,
|
||||
operation: &str,
|
||||
id: &str,
|
||||
method: Method,
|
||||
endpoint: &str,
|
||||
body: &B,
|
||||
) -> Result<M> {
|
||||
self.execute_request_deobf::<R, M, B>(ctype, operation, id, method, endpoint, body, None)
|
||||
self.execute_request_deobf::<R, M, B>(ctype, operation, id, endpoint, body, None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ use anyhow::{anyhow, bail, Result};
|
|||
use chrono::{Local, NaiveDateTime, NaiveTime, TimeZone};
|
||||
use fancy_regex::Regex;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Method;
|
||||
use serde::Serialize;
|
||||
use url::Url;
|
||||
|
||||
|
|
@ -98,7 +97,6 @@ impl RustyPipeQuery {
|
|||
client_type,
|
||||
"player",
|
||||
video_id,
|
||||
Method::POST,
|
||||
"player",
|
||||
&request_body,
|
||||
Some(&deobf),
|
||||
|
|
@ -175,7 +173,7 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
title: video_details.title,
|
||||
description: video_details.short_description,
|
||||
length: video_details.length_seconds,
|
||||
thumbnails: video_details.thumbnail.into(),
|
||||
thumbnail: video_details.thumbnail.into(),
|
||||
channel: ChannelId {
|
||||
id: video_details.channel_id,
|
||||
name: video_details.author,
|
||||
|
|
@ -439,7 +437,7 @@ fn map_audio_stream(
|
|||
deobf: &Deobfuscator,
|
||||
last_nsig: &mut [String; 2],
|
||||
) -> MapResult<Option<AudioStream>> {
|
||||
static LANG_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r#"^([a-z]{2})\."#).unwrap());
|
||||
static LANG_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r#"^([a-z]{2,3})\."#).unwrap());
|
||||
|
||||
let (mtype, codecs) = some_or_bail!(
|
||||
parse_mime(&f.mime_type),
|
||||
|
|
@ -631,7 +629,7 @@ mod tests {
|
|||
#[case::android(ClientType::Android)]
|
||||
#[case::ios(ClientType::Ios)]
|
||||
#[test_log::test(tokio::test)]
|
||||
async fn t_get_player(#[case] client_type: ClientType) {
|
||||
async fn get_player(#[case] client_type: ClientType) {
|
||||
let rp = RustyPipe::builder().strict().build();
|
||||
let player_data = rp.query().player("n4tK7LYFxI0", client_type).await.unwrap();
|
||||
|
||||
|
|
@ -647,7 +645,7 @@ mod tests {
|
|||
));
|
||||
}
|
||||
assert_eq!(player_data.details.length, 259);
|
||||
assert!(!player_data.details.thumbnails.is_empty());
|
||||
assert!(!player_data.details.thumbnail.is_empty());
|
||||
assert_eq!(player_data.details.channel.id, "UC_aEa8K-EOJ3D6gOs7HcyNg");
|
||||
assert_eq!(player_data.details.channel.name, "NoCopyrightSounds");
|
||||
assert!(player_data.details.view_count > 146818808);
|
||||
|
|
@ -733,6 +731,18 @@ mod tests {
|
|||
assert!(player_data.expires_in_seconds > 10000);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tmp() {
|
||||
let rp = RustyPipe::builder().strict().build();
|
||||
let player_data = rp
|
||||
.query()
|
||||
.player("tVWWp1PqDus", ClientType::Desktop)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
dbg!(&player_data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn t_cipher_to_url() {
|
||||
let signature_cipher = "s=w%3DAe%3DA6aDNQLkViKS7LOm9QtxZJHKwb53riq9qEFw-ecBWJCAiA%3DcEg0tn3dty9jEHszfzh4Ud__bg9CEHVx4ix-7dKsIPAhIQRw8JQ0qOA&sp=sig&url=https://rr5---sn-h0jelnez.googlevideo.com/videoplayback%3Fexpire%3D1659376413%26ei%3Dvb7nYvH5BMK8gAfBj7ToBQ%26ip%3D2003%253Ade%253Aaf06%253A6300%253Ac750%253A1b77%253Ac74a%253A80e3%26id%3Do-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2%26itag%3D251%26source%3Dyoutube%26requiressl%3Dyes%26mh%3DhH%26mm%3D31%252C26%26mn%3Dsn-h0jelnez%252Csn-4g5ednsl%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D5%26pl%3D37%26initcwndbps%3D1588750%26spc%3DlT-Khi831z8dTejFIRCvCEwx_6romtM%26vprv%3D1%26mime%3Daudio%252Fwebm%26ns%3Db_Mq_qlTFcSGlG9RpwpM9xQH%26gir%3Dyes%26clen%3D3781277%26dur%3D229.301%26lmt%3D1655510291473933%26mt%3D1659354538%26fvip%3D5%26keepalive%3Dyes%26fexp%3D24001373%252C24007246%26c%3DWEB%26rbqsm%3Dfr%26txp%3D4532434%26n%3Dd2g6G2hVqWIXxedQ%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cspc%252Cvprv%252Cmime%252Cns%252Cgir%252Cclen%252Cdur%252Clmt%26lsparams%3Dmh%252Cmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DAG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%253D%253D";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use reqwest::Method;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -34,7 +33,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"playlist",
|
||||
playlist_id,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -52,7 +50,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"get_playlist_continuation",
|
||||
ctoken,
|
||||
Method::POST,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
|
|
|
|||
81
src/client/response/channel_rss.rs
Normal file
81
src/client/response/channel_rss.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::Thumbnail;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ChannelRss {
|
||||
#[serde(rename = "$unflatten=yt:channelId")]
|
||||
pub channel_id: String,
|
||||
#[serde(rename = "$unflatten=title")]
|
||||
pub title: String,
|
||||
#[serde(rename = "$unflatten=published")]
|
||||
pub create_date: DateTime<Utc>,
|
||||
pub entry: Vec<Entry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Entry {
|
||||
#[serde(rename = "$unflatten=yt:videoId")]
|
||||
pub video_id: String,
|
||||
#[serde(rename = "$unflatten=title")]
|
||||
pub title: String,
|
||||
#[serde(rename = "$unflatten=published")]
|
||||
pub published: DateTime<Utc>,
|
||||
#[serde(rename = "$unflatten=updated")]
|
||||
pub updated: DateTime<Utc>,
|
||||
#[serde(rename = "$unflatten=media:group")]
|
||||
pub media_group: MediaGroup,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct MediaGroup {
|
||||
#[serde(rename = "$unflatten=media:thumbnail")]
|
||||
pub thumbnail: Thumbnail,
|
||||
#[serde(rename = "$unflatten=media:description")]
|
||||
pub description: String,
|
||||
#[serde(rename = "$unflatten=media:community")]
|
||||
pub community: Community,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Community {
|
||||
#[serde(rename = "$unflatten=media:starRating")]
|
||||
pub rating: Rating,
|
||||
#[serde(rename = "$unflatten=media:statistics")]
|
||||
pub statistics: Statistics,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Rating {
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Statistics {
|
||||
pub views: u64,
|
||||
}
|
||||
|
||||
impl From<ChannelRss> for crate::model::ChannelRss {
|
||||
fn from(feed: ChannelRss) -> Self {
|
||||
Self {
|
||||
id: feed.channel_id,
|
||||
name: feed.title,
|
||||
videos: feed
|
||||
.entry
|
||||
.into_iter()
|
||||
.map(|item| crate::model::ChannelRssVideo {
|
||||
id: item.video_id,
|
||||
title: item.title,
|
||||
description: item.media_group.description,
|
||||
thumbnail: item.media_group.thumbnail.into(),
|
||||
publish_date: item.published,
|
||||
update_date: item.updated,
|
||||
view_count: item.media_group.community.statistics.views,
|
||||
like_count: item.media_group.community.rating.count,
|
||||
})
|
||||
.collect(),
|
||||
create_date: feed.create_date,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,11 @@ pub use video_details::VideoComments;
|
|||
pub use video_details::VideoDetails;
|
||||
pub use video_details::VideoRecommendations;
|
||||
|
||||
#[cfg(feature = "rss")]
|
||||
pub mod channel_rss;
|
||||
#[cfg(feature = "rss")]
|
||||
pub use channel_rss::ChannelRss;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError};
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,7 @@ VideoPlayer(
|
|||
title: "Inspiring Cinematic Uplifting (Creative Commons)",
|
||||
description: Some("► Download Music: http://bit.ly/2QLufeh\nImportant to know! You can download this track for free through Patreon. You will pay only for new tracks! So join others and let\'s make next track together!\n\n► MORE MUSIC: Become my patron and get access to all our music from Patreon library. More Info here: http://bit.ly/2JJDFHb\n\n► Additional edit versions of this track you can download here: http://bit.ly/2WdRinT (5 versions)\n--------------------- \n\n►DESCRIPTION:\nInspiring Cinematic Uplifting Trailer Background - epic music for trailer video project with powerful drums, energetic orchestra and gentle piano melody. This motivational cinematic theme will work as perfect background for beautiful epic moments, landscapes, nature, drone video, motivational products and achievements.\n--------------------- \n\n► LICENSE:\n● If you need a license for your project, you can purchase it here: \nhttps://1.envato.market/ajicu (Audiojungle)\nhttps://bit.ly/3fWZZuI (Pond5)\n--------------------- \n\n► LISTEN ON:\n● Spotify - https://spoti.fi/2sHm3UH\n● Apple Music - https://apple.co/3qBjbUO\n--------------------- \n\n► SUBSCRIBE FOR MORE: \nPatreon: http://bit.ly/2JJDFHb\nYoutube: http://bit.ly/2AYBzfA\nFacebook: http://bit.ly/2T6dTx5\nInstagram: http://bit.ly/2BHJ8rB\nTwitter: http://bit.ly/2MwtOlT\nSoundCloud: http://bit.ly/2IwVVmt\nAudiojungle: https://1.envato.market/ajrsm\nPond5: https://bit.ly/2TLi1rW\n--------------------- \n►Photo by Vittorio Staffolani from Pexels\n--------------------- \n\nFAQ:\n\n► Can I use this music in my videos? \n● Sure! Just download this track and you are ready to use it! We only ask to credit us. \n-------------------- \n\n► What is \"Creative Commons\"? \nCreative Commons is a system that allows you to legally use “some rights reserved” music, movies, images, and other content — all for free. Licensees may copy, distribute, display and perform the work and make derivative works and remixes based on it only if they give the author or licensor the credits.\n-------------------- \n\n► Will I have any copyright issues with this track?\n● No, you should not have any copyright problems with this track!\n-------------------- \n\n► Is it necessary to become your patron?\n● No it\'s not necessary. But we recommend you to become our patron because you will get access to huge library of music. You will download only highest quality files. You will find additional edited versions of every track. You always be tuned with our news. You will find music not only from Roman Senyk but also from another talented authors.\n-------------------- \n\n► Why I received a copyright claim when I used this track?\n● Do not panic! This is very common situation. Content ID fingerprint system can mismatch our music. Just dispute the claim by showing our original track. Or send us the link to your video (romansenykmusic@gmail.com) and attach some screenshot with claim information. Claim will be released until 24 hours!\n\n► How to credit you in my video?\n● Just add to the description of your project information about Author, Name of Song and the link to our original track. Or copy and paste:\n\nMusic Info: Inspiring Cinematic Uplifting by RomanSenykMusic.\nMusic Link: https://youtu.be/pPvd8UxmSbQ\n--------------------- \n\n► If you have any questions, you can write in the comments for this video or by email: romansenykmusic@gmail.com\n--------------------- \n\nStay tuned! The best is yet to come! \nThanks For Listening!\nRoman Senyk"),
|
||||
length: 163,
|
||||
thumbnails: [
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi_webp/pPvd8UxmSbQ/default.webp",
|
||||
width: 120,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ VideoPlayer(
|
|||
title: "Inspiring Cinematic Uplifting (Creative Commons)",
|
||||
description: Some("► Download Music: http://bit.ly/2QLufeh\nImportant to know! You can download this track for free through Patreon. You will pay only for new tracks! So join others and let\'s make next track together!\n\n► MORE MUSIC: Become my patron and get access to all our music from Patreon library. More Info here: http://bit.ly/2JJDFHb\n\n► Additional edit versions of this track you can download here: http://bit.ly/2WdRinT (5 versions)\n--------------------- \n\n►DESCRIPTION:\nInspiring Cinematic Uplifting Trailer Background - epic music for trailer video project with powerful drums, energetic orchestra and gentle piano melody. This motivational cinematic theme will work as perfect background for beautiful epic moments, landscapes, nature, drone video, motivational products and achievements.\n--------------------- \n\n► LICENSE:\n● If you need a license for your project, you can purchase it here: \nhttps://1.envato.market/ajicu (Audiojungle)\nhttps://bit.ly/3fWZZuI (Pond5)\n--------------------- \n\n► LISTEN ON:\n● Spotify - https://spoti.fi/2sHm3UH\n● Apple Music - https://apple.co/3qBjbUO\n--------------------- \n\n► SUBSCRIBE FOR MORE: \nPatreon: http://bit.ly/2JJDFHb\nYoutube: http://bit.ly/2AYBzfA\nFacebook: http://bit.ly/2T6dTx5\nInstagram: http://bit.ly/2BHJ8rB\nTwitter: http://bit.ly/2MwtOlT\nSoundCloud: http://bit.ly/2IwVVmt\nAudiojungle: https://1.envato.market/ajrsm\nPond5: https://bit.ly/2TLi1rW\n--------------------- \n►Photo by Vittorio Staffolani from Pexels\n--------------------- \n\nFAQ:\n\n► Can I use this music in my videos? \n● Sure! Just download this track and you are ready to use it! We only ask to credit us. \n-------------------- \n\n► What is \"Creative Commons\"? \nCreative Commons is a system that allows you to legally use “some rights reserved” music, movies, images, and other content — all for free. Licensees may copy, distribute, display and perform the work and make derivative works and remixes based on it only if they give the author or licensor the credits.\n-------------------- \n\n► Will I have any copyright issues with this track?\n● No, you should not have any copyright problems with this track!\n-------------------- \n\n► Is it necessary to become your patron?\n● No it\'s not necessary. But we recommend you to become our patron because you will get access to huge library of music. You will download only highest quality files. You will find additional edited versions of every track. You always be tuned with our news. You will find music not only from Roman Senyk but also from another talented authors.\n-------------------- \n\n► Why I received a copyright claim when I used this track?\n● Do not panic! This is very common situation. Content ID fingerprint system can mismatch our music. Just dispute the claim by showing our original track. Or send us the link to your video (romansenykmusic@gmail.com) and attach some screenshot with claim information. Claim will be released until 24 hours!\n\n► How to credit you in my video?\n● Just add to the description of your project information about Author, Name of Song and the link to our original track. Or copy and paste:\n\nMusic Info: Inspiring Cinematic Uplifting by RomanSenykMusic.\nMusic Link: https://youtu.be/pPvd8UxmSbQ\n--------------------- \n\n► If you have any questions, you can write in the comments for this video or by email: romansenykmusic@gmail.com\n--------------------- \n\nStay tuned! The best is yet to come! \nThanks For Listening!\nRoman Senyk"),
|
||||
length: 163,
|
||||
thumbnails: [
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/pPvd8UxmSbQ/hqdefault.jpg?sqp=-oaymwEbCKgBEF5IVfKriqkDDggBFQAAiEIYAXABwAEG&rs=AOn4CLBSNHImLtGal2a95M5oyTT_uuTZlw",
|
||||
width: 168,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ VideoPlayer(
|
|||
title: "Inspiring Cinematic Uplifting",
|
||||
description: None,
|
||||
length: 163,
|
||||
thumbnails: [
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/pPvd8UxmSbQ/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AOn4CLC-0nIQMyPuy8CtzqTMl6z1rmG_XQ",
|
||||
width: 400,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ VideoPlayer(
|
|||
title: "Inspiring Cinematic Uplifting (Creative Commons)",
|
||||
description: Some("► Download Music: http://bit.ly/2QLufeh\nImportant to know! You can download this track for free through Patreon. You will pay only for new tracks! So join others and let\'s make next track together!\n\n► MORE MUSIC: Become my patron and get access to all our music from Patreon library. More Info here: http://bit.ly/2JJDFHb\n\n► Additional edit versions of this track you can download here: http://bit.ly/2WdRinT (5 versions)\n--------------------- \n\n►DESCRIPTION:\nInspiring Cinematic Uplifting Trailer Background - epic music for trailer video project with powerful drums, energetic orchestra and gentle piano melody. This motivational cinematic theme will work as perfect background for beautiful epic moments, landscapes, nature, drone video, motivational products and achievements.\n--------------------- \n\n► LICENSE:\n● If you need a license for your project, you can purchase it here: \nhttps://1.envato.market/ajicu (Audiojungle)\nhttps://bit.ly/3fWZZuI (Pond5)\n--------------------- \n\n► LISTEN ON:\n● Spotify - https://spoti.fi/2sHm3UH\n● Apple Music - https://apple.co/3qBjbUO\n--------------------- \n\n► SUBSCRIBE FOR MORE: \nPatreon: http://bit.ly/2JJDFHb\nYoutube: http://bit.ly/2AYBzfA\nFacebook: http://bit.ly/2T6dTx5\nInstagram: http://bit.ly/2BHJ8rB\nTwitter: http://bit.ly/2MwtOlT\nSoundCloud: http://bit.ly/2IwVVmt\nAudiojungle: https://1.envato.market/ajrsm\nPond5: https://bit.ly/2TLi1rW\n--------------------- \n►Photo by Vittorio Staffolani from Pexels\n--------------------- \n\nFAQ:\n\n► Can I use this music in my videos? \n● Sure! Just download this track and you are ready to use it! We only ask to credit us. \n-------------------- \n\n► What is \"Creative Commons\"? \nCreative Commons is a system that allows you to legally use “some rights reserved” music, movies, images, and other content — all for free. Licensees may copy, distribute, display and perform the work and make derivative works and remixes based on it only if they give the author or licensor the credits.\n-------------------- \n\n► Will I have any copyright issues with this track?\n● No, you should not have any copyright problems with this track!\n-------------------- \n\n► Is it necessary to become your patron?\n● No it\'s not necessary. But we recommend you to become our patron because you will get access to huge library of music. You will download only highest quality files. You will find additional edited versions of every track. You always be tuned with our news. You will find music not only from Roman Senyk but also from another talented authors.\n-------------------- \n\n► Why I received a copyright claim when I used this track?\n● Do not panic! This is very common situation. Content ID fingerprint system can mismatch our music. Just dispute the claim by showing our original track. Or send us the link to your video (romansenykmusic@gmail.com) and attach some screenshot with claim information. Claim will be released until 24 hours!\n\n► How to credit you in my video?\n● Just add to the description of your project information about Author, Name of Song and the link to our original track. Or copy and paste:\n\nMusic Info: Inspiring Cinematic Uplifting by RomanSenykMusic.\nMusic Link: https://youtu.be/pPvd8UxmSbQ\n--------------------- \n\n► If you have any questions, you can write in the comments for this video or by email: romansenykmusic@gmail.com\n--------------------- \n\nStay tuned! The best is yet to come! \nThanks For Listening!\nRoman Senyk"),
|
||||
length: 163,
|
||||
thumbnails: [
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/pPvd8UxmSbQ/mqdefault.jpg",
|
||||
width: 320,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ VideoPlayer(
|
|||
title: "Inspiring Cinematic Uplifting (Creative Commons)",
|
||||
description: Some("► Download Music: http://bit.ly/2QLufeh\nImportant to know! You can download this track for free through Patreon. You will pay only for new tracks! So join others and let\'s make next track together!\n\n► MORE MUSIC: Become my patron and get access to all our music from Patreon library. More Info here: http://bit.ly/2JJDFHb\n\n► Additional edit versions of this track you can download here: http://bit.ly/2WdRinT (5 versions)\n--------------------- \n\n►DESCRIPTION:\nInspiring Cinematic Uplifting Trailer Background - epic music for trailer video project with powerful drums, energetic orchestra and gentle piano melody. This motivational cinematic theme will work as perfect background for beautiful epic moments, landscapes, nature, drone video, motivational products and achievements.\n--------------------- \n\n► LICENSE:\n● If you need a license for your project, you can purchase it here: \nhttps://1.envato.market/ajicu (Audiojungle)\nhttps://bit.ly/3fWZZuI (Pond5)\n--------------------- \n\n► LISTEN ON:\n● Spotify - https://spoti.fi/2sHm3UH\n● Apple Music - https://apple.co/3qBjbUO\n--------------------- \n\n► SUBSCRIBE FOR MORE: \nPatreon: http://bit.ly/2JJDFHb\nYoutube: http://bit.ly/2AYBzfA\nFacebook: http://bit.ly/2T6dTx5\nInstagram: http://bit.ly/2BHJ8rB\nTwitter: http://bit.ly/2MwtOlT\nSoundCloud: http://bit.ly/2IwVVmt\nAudiojungle: https://1.envato.market/ajrsm\nPond5: https://bit.ly/2TLi1rW\n--------------------- \n►Photo by Vittorio Staffolani from Pexels\n--------------------- \n\nFAQ:\n\n► Can I use this music in my videos? \n● Sure! Just download this track and you are ready to use it! We only ask to credit us. \n-------------------- \n\n► What is \"Creative Commons\"? \nCreative Commons is a system that allows you to legally use “some rights reserved” music, movies, images, and other content — all for free. Licensees may copy, distribute, display and perform the work and make derivative works and remixes based on it only if they give the author or licensor the credits.\n-------------------- \n\n► Will I have any copyright issues with this track?\n● No, you should not have any copyright problems with this track!\n-------------------- \n\n► Is it necessary to become your patron?\n● No it\'s not necessary. But we recommend you to become our patron because you will get access to huge library of music. You will download only highest quality files. You will find additional edited versions of every track. You always be tuned with our news. You will find music not only from Roman Senyk but also from another talented authors.\n-------------------- \n\n► Why I received a copyright claim when I used this track?\n● Do not panic! This is very common situation. Content ID fingerprint system can mismatch our music. Just dispute the claim by showing our original track. Or send us the link to your video (romansenykmusic@gmail.com) and attach some screenshot with claim information. Claim will be released until 24 hours!\n\n► How to credit you in my video?\n● Just add to the description of your project information about Author, Name of Song and the link to our original track. Or copy and paste:\n\nMusic Info: Inspiring Cinematic Uplifting by RomanSenykMusic.\nMusic Link: https://youtu.be/pPvd8UxmSbQ\n--------------------- \n\n► If you have any questions, you can write in the comments for this video or by email: romansenykmusic@gmail.com\n--------------------- \n\nStay tuned! The best is yet to come! \nThanks For Listening!\nRoman Senyk"),
|
||||
length: 163,
|
||||
thumbnails: [
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/pPvd8UxmSbQ/default.jpg",
|
||||
width: 120,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use reqwest::Method;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -44,7 +43,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"video_details",
|
||||
video_id,
|
||||
Method::POST,
|
||||
"next",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -62,7 +60,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"video_recommendations",
|
||||
ctoken,
|
||||
Method::POST,
|
||||
"next",
|
||||
&request_body,
|
||||
)
|
||||
|
|
@ -80,7 +77,6 @@ impl RustyPipeQuery {
|
|||
ClientType::Desktop,
|
||||
"video_comments",
|
||||
ctoken,
|
||||
Method::POST,
|
||||
"next",
|
||||
&request_body,
|
||||
)
|
||||
|
|
|
|||
Reference in a new issue