fix: channel not found

This commit is contained in:
ThetaDev 2022-10-11 12:59:43 +02:00
parent 012cde8b51
commit ef35c48890
6 changed files with 108 additions and 43 deletions

View file

@ -160,7 +160,7 @@ impl MapResponse<Channel<Paginator<ChannelVideo>>> for response::Channel {
lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Channel<Paginator<ChannelVideo>>>, ExtractionError> {
let content = map_channel_content(self.contents, id);
let content = map_channel_content(self.contents, id, self.alerts)?;
let mut warnings = content.warnings;
let grid = match content.c {
response::channel::ChannelContent::GridRenderer { items } => Some(items),
@ -191,7 +191,7 @@ impl MapResponse<Channel<Paginator<ChannelPlaylist>>> for response::Channel {
lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Channel<Paginator<ChannelPlaylist>>>, ExtractionError> {
let content = map_channel_content(self.contents, id);
let content = map_channel_content(self.contents, id, self.alerts)?;
let mut warnings = content.warnings;
let grid = match content.c {
response::channel::ChannelContent::GridRenderer { items } => Some(items),
@ -222,7 +222,7 @@ impl MapResponse<Channel<ChannelInfo>> for response::Channel {
lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Channel<ChannelInfo>>, ExtractionError> {
let content = map_channel_content(self.contents, id);
let content = map_channel_content(self.contents, id, self.alerts)?;
let mut warnings = content.warnings;
let meta = match content.c {
response::channel::ChannelContent::ChannelAboutFullMetadataRenderer(meta) => Some(meta),
@ -419,14 +419,18 @@ fn map_vanity_url(url: &str, id: &str) -> Option<String> {
}
fn map_channel<T>(
header: response::channel::Header,
metadata: response::channel::Metadata,
microformat: response::channel::Microformat,
header: Option<response::channel::Header>,
metadata: Option<response::channel::Metadata>,
microformat: Option<response::channel::Microformat>,
content: T,
id: &str,
lang: Language,
) -> Result<Channel<T>, ExtractionError> {
let metadata = metadata.channel_metadata_renderer;
let header = header.ok_or(ExtractionError::NoData)?;
let metadata = metadata
.ok_or(ExtractionError::NoData)?
.channel_metadata_renderer;
let microformat = microformat.ok_or(ExtractionError::NoData)?;
if metadata.external_id != id {
return Err(ExtractionError::WrongResult(format!(
@ -494,39 +498,54 @@ fn map_channel<T>(
}
fn map_channel_content(
contents: response::channel::Contents,
contents: Option<response::channel::Contents>,
id: &str,
) -> MapResult<response::channel::ChannelContent> {
let mut tabs = contents.two_column_browse_results_renderer.tabs;
let mut sectionlist = some_or_bail!(
tabs.try_swap_remove(0),
MapResult::error("no tab".to_owned())
)
.tab_renderer
.content
.section_list_renderer;
alerts: Option<Vec<response::Alert>>,
) -> Result<MapResult<response::channel::ChannelContent>, ExtractionError> {
match contents {
Some(contents) => {
let mut tabs = contents.two_column_browse_results_renderer.tabs;
let mut sectionlist = some_or_bail!(
tabs.try_swap_remove(0),
Ok(MapResult::error("no tab".to_owned()))
)
.tab_renderer
.content
.section_list_renderer;
if let Some(target_id) = sectionlist.target_id {
// YouTube falls back to the featured page if the channel does not have a "videos" tab.
// This is the case for YouTube Music channels.
if target_id.starts_with(&format!("browse-feed{}featured", id)) {
return MapResult::ok(response::channel::ChannelContent::None);
if let Some(target_id) = sectionlist.target_id {
// YouTube falls back to the featured page if the channel does not have a "videos" tab.
// This is the case for YouTube Music channels.
if target_id.starts_with(&format!("browse-feed{}featured", id)) {
return Ok(MapResult::ok(response::channel::ChannelContent::None));
}
}
let mut itemsection = some_or_bail!(
sectionlist.contents.try_swap_remove(0),
Ok(MapResult::error("no sectionlist".to_owned()))
)
.item_section_renderer
.contents;
let content = some_or_bail!(
itemsection.try_swap_remove(0),
Ok(MapResult::error("no channel content".to_owned()))
);
Ok(MapResult::ok(content))
}
None => match alerts {
Some(alerts) => Err(ExtractionError::ContentUnavailable(
alerts
.into_iter()
.map(|a| a.alert_renderer.text)
.collect::<Vec<_>>()
.join(" "),
)),
None => Err(ExtractionError::InvalidData("no contents".into())),
},
}
let mut itemsection = some_or_bail!(
sectionlist.contents.try_swap_remove(0),
MapResult::error("no sectionlist".to_owned())
)
.item_section_renderer
.contents;
let content = some_or_bail!(
itemsection.try_swap_remove(0),
MapResult::error("no channel content".to_owned())
);
MapResult::ok(content)
}
#[cfg(test)]

View file

@ -1009,7 +1009,13 @@ impl RustyPipeQuery {
Ok(mapres.c)
}
Err(e) => {
create_report(Level::ERR, Some(e.to_string()), Vec::new());
match e {
ExtractionError::VideoUnavailable(_, _)
| ExtractionError::VideoAgeRestricted
| ExtractionError::ContentUnavailable(_)
| ExtractionError::NoData => (),
_ => create_report(Level::ERR, Some(e.to_string()), Vec::new()),
}
Err(e.into())
}
},

View file

@ -1,9 +1,9 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::VecSkipError;
use serde_with::{DefaultOnError, VecSkipError};
use super::ChannelBadge;
use super::Thumbnails;
use super::{Alert, ChannelBadge};
use super::{ContentRenderer, ContentsRenderer, VideoListItem};
use crate::serializer::ignore_any;
use crate::serializer::{text::Text, MapResult, VecLogError};
@ -12,10 +12,13 @@ use crate::serializer::{text::Text, MapResult, VecLogError};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Channel {
pub header: Header,
pub contents: Contents,
pub metadata: Metadata,
pub microformat: Microformat,
#[serde_as(as = "DefaultOnError")]
pub header: Option<Header>,
pub contents: Option<Contents>,
pub metadata: Option<Metadata>,
pub microformat: Option<Microformat>,
#[serde_as(as = "Option<DefaultOnError>")]
pub alerts: Option<Vec<Alert>>,
}
#[serde_as]

View file

@ -313,6 +313,20 @@ pub enum VideoBadgeStyle {
BadgeStyleTypeLiveNow,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Alert {
pub alert_renderer: AlertRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AlertRenderer {
#[serde_as(as = "Text")]
pub text: String,
}
// YouTube Music
#[serde_as]

View file

@ -75,6 +75,10 @@ pub enum ExtractionError {
VideoUnavailable(&'static str, String),
#[error("Video is age restricted")]
VideoAgeRestricted,
#[error("Content is not available. Reason (from YT): {0}")]
ContentUnavailable(String),
#[error("Got no data from YouTube")]
NoData,
#[error("deserialization error: {0}")]
Deserialization(#[from] serde_json::Error),
#[error("got invalid data from YT: {0}")]

View file

@ -2,6 +2,7 @@ use chrono::{Datelike, Timelike};
use rstest::rstest;
use rustypipe::client::{ClientType, RustyPipe};
use rustypipe::error::{Error, ExtractionError};
use rustypipe::model::richtext::ToPlaintext;
use rustypipe::model::{
AudioCodec, AudioFormat, Channel, SearchItem, Verification, VideoCodec, VideoFormat,
@ -857,6 +858,24 @@ async fn channel_more(
assert_channel(&channel_info, id, name);
}
#[rstest]
#[case::gaming("UCOpNcN46UbXVtpKMrmU4Abg", false)]
#[case::not_found("UCOpNcN46UbXVtpKMrmU4Abx", true)]
#[tokio::test]
async fn channel_error(#[case] id: &str, #[case] not_found: bool) {
let rp = RustyPipe::builder().strict().build();
let err = rp.query().channel_videos(&id).await.unwrap_err();
if not_found {
assert!(matches!(
err,
Error::Extraction(ExtractionError::ContentUnavailable(_))
));
} else {
assert!(matches!(err, Error::Extraction(ExtractionError::NoData)));
}
}
//#CHANNEL_RSS
#[tokio::test]