refactor: use unified models for video/playlist/channel

This commit is contained in:
ThetaDev 2022-10-17 00:55:49 +02:00
parent b22f6995cc
commit dbcb7fe0df
41 changed files with 2156 additions and 1228 deletions

View file

@ -3,17 +3,14 @@ use url::Url;
use crate::{
error::{Error, ExtractionError},
model::{Channel, ChannelInfo, ChannelPlaylist, ChannelVideo, Paginator},
model::{Channel, ChannelInfo, Paginator, PlaylistItem, VideoItem},
param::{ChannelOrder, Language},
serializer::MapResult,
timeago,
util::{self, TryRemove},
};
use super::{
response::{self, FromWLang},
ClientType, MapResponse, QContinuation, RustyPipeQuery, YTContext,
};
use super::{response, ClientType, MapResponse, RustyPipeQuery, YTContext};
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
@ -41,7 +38,7 @@ impl RustyPipeQuery {
pub async fn channel_videos(
self,
channel_id: &str,
) -> Result<Channel<Paginator<ChannelVideo>>, Error> {
) -> Result<Channel<Paginator<VideoItem>>, Error> {
self.channel_videos_ordered(channel_id, ChannelOrder::default())
.await
}
@ -50,7 +47,7 @@ impl RustyPipeQuery {
self,
channel_id: &str,
order: ChannelOrder,
) -> Result<Channel<Paginator<ChannelVideo>>, Error> {
) -> Result<Channel<Paginator<VideoItem>>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QChannel {
context,
@ -72,30 +69,10 @@ impl RustyPipeQuery {
.await
}
pub async fn channel_videos_continuation(
self,
ctoken: &str,
) -> Result<Paginator<ChannelVideo>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QContinuation {
context,
continuation: ctoken,
};
self.execute_request::<response::ChannelCont, _, _>(
ClientType::Desktop,
"channel_videos_continuation",
ctoken,
"browse",
&request_body,
)
.await
}
pub async fn channel_playlists(
self,
channel_id: &str,
) -> Result<Channel<Paginator<ChannelPlaylist>>, Error> {
) -> Result<Channel<Paginator<PlaylistItem>>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QChannel {
context,
@ -113,26 +90,6 @@ impl RustyPipeQuery {
.await
}
pub async fn channel_playlists_continuation(
self,
ctoken: &str,
) -> Result<Paginator<ChannelPlaylist>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QContinuation {
context,
continuation: ctoken,
};
self.execute_request::<response::ChannelCont, _, _>(
ClientType::Desktop,
"channel_playlists_continuation",
ctoken,
"browse",
&request_body,
)
.await
}
pub async fn channel_info(&self, channel_id: &str) -> Result<Channel<ChannelInfo>, Error> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QChannel {
@ -152,13 +109,13 @@ impl RustyPipeQuery {
}
}
impl MapResponse<Channel<Paginator<ChannelVideo>>> for response::Channel {
impl MapResponse<Channel<Paginator<VideoItem>>> for response::Channel {
fn map_response(
self,
id: &str,
lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Channel<Paginator<ChannelVideo>>>, ExtractionError> {
) -> Result<MapResult<Channel<Paginator<VideoItem>>>, ExtractionError> {
let content = map_channel_content(self.contents, id, self.alerts)?;
let grid = match content {
response::channel::ChannelContent::GridRenderer { items } => Some(items),
@ -181,20 +138,22 @@ impl MapResponse<Channel<Paginator<ChannelVideo>>> for response::Channel {
}
}
impl MapResponse<Channel<Paginator<ChannelPlaylist>>> for response::Channel {
impl MapResponse<Channel<Paginator<PlaylistItem>>> for response::Channel {
fn map_response(
self,
id: &str,
lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Channel<Paginator<ChannelPlaylist>>>, ExtractionError> {
) -> Result<MapResult<Channel<Paginator<PlaylistItem>>>, ExtractionError> {
let content = map_channel_content(self.contents, id, self.alerts)?;
let grid = match content {
response::channel::ChannelContent::GridRenderer { items } => Some(items),
_ => None,
};
let p_res = grid.map(map_playlists).unwrap_or_default();
let p_res = grid
.map(|item| map_playlists(item, lang))
.unwrap_or_default();
Ok(MapResult {
c: map_channel(
@ -267,98 +226,29 @@ impl MapResponse<Channel<ChannelInfo>> for response::Channel {
}
}
impl MapResponse<Paginator<ChannelVideo>> for response::ChannelCont {
fn map_response(
self,
_id: &str,
lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Paginator<ChannelVideo>>, ExtractionError> {
let mut actions = self.on_response_received_actions;
let res = actions
.try_swap_remove(0)
.ok_or(ExtractionError::Retry)?
.append_continuation_items_action
.continuation_items;
Ok(map_videos(res, lang))
}
}
impl MapResponse<Paginator<ChannelPlaylist>> for response::ChannelCont {
fn map_response(
self,
_id: &str,
_lang: Language,
_deobf: Option<&crate::deobfuscate::Deobfuscator>,
) -> Result<MapResult<Paginator<ChannelPlaylist>>, ExtractionError> {
let mut actions = self.on_response_received_actions;
let res = actions
.try_swap_remove(0)
.ok_or(ExtractionError::Retry)?
.append_continuation_items_action
.continuation_items;
Ok(map_playlists(res))
}
}
fn map_videos(
res: MapResult<Vec<response::VideoListItem>>,
res: MapResult<Vec<response::YouTubeListItem>>,
lang: Language,
) -> MapResult<Paginator<ChannelVideo>> {
let mut ctoken = None;
let videos = res
.c
.into_iter()
.filter_map(|item| match item {
response::VideoListItem::GridVideoRenderer(video) => {
Some(ChannelVideo::from_w_lang(video, lang))
}
response::VideoListItem::RichItemRenderer {
content: response::RichItem::VideoRenderer(video),
} => Some(ChannelVideo::from_w_lang(video, lang)),
response::VideoListItem::ContinuationItemRenderer {
continuation_endpoint,
} => {
ctoken = Some(continuation_endpoint.continuation_command.token);
None
}
_ => None,
})
.collect();
) -> MapResult<Paginator<VideoItem>> {
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(lang);
mapper.map_response(res);
MapResult {
c: Paginator::new(None, videos, ctoken),
warnings: res.warnings,
c: Paginator::new(None, mapper.items, mapper.ctoken),
warnings: mapper.warnings,
}
}
fn map_playlists(
res: MapResult<Vec<response::VideoListItem>>,
) -> MapResult<Paginator<ChannelPlaylist>> {
let mut ctoken = None;
let playlists = res
.c
.into_iter()
.filter_map(|item| match item {
response::VideoListItem::GridPlaylistRenderer(playlist) => Some(playlist.into()),
response::VideoListItem::RichItemRenderer {
content: response::RichItem::PlaylistRenderer(playlist),
} => Some(playlist.into()),
response::VideoListItem::ContinuationItemRenderer {
continuation_endpoint,
} => {
ctoken = Some(continuation_endpoint.continuation_command.token);
None
}
_ => None,
})
.collect();
res: MapResult<Vec<response::YouTubeListItem>>,
lang: Language,
) -> MapResult<Paginator<PlaylistItem>> {
let mut mapper = response::YouTubeListMapper::<PlaylistItem>::new(lang);
mapper.map_response(res);
MapResult {
c: Paginator::new(None, playlists, ctoken),
warnings: res.warnings,
c: Paginator::new(None, mapper.items, mapper.ctoken),
warnings: mapper.warnings,
}
}
@ -516,7 +406,7 @@ mod tests {
use crate::{
client::{response, MapResponse},
model::{Channel, ChannelInfo, ChannelPlaylist, ChannelVideo, Paginator},
model::{Channel, ChannelInfo, Paginator, PlaylistItem, VideoItem},
param::Language,
serializer::MapResult,
};
@ -537,7 +427,7 @@ mod tests {
let channel: response::Channel =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Channel<Paginator<ChannelVideo>>> =
let map_res: MapResult<Channel<Paginator<VideoItem>>> =
channel.map_response(id, Language::En, None).unwrap();
assert!(
@ -557,27 +447,6 @@ mod tests {
}
}
#[test]
fn map_channel_videos_cont() {
let json_path = Path::new("testfiles/channel/channel_videos_cont.json");
let json_file = File::open(json_path).unwrap();
let channel: response::ChannelCont =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Paginator<ChannelVideo>> = channel
.map_response("UC2DjFE7Xf11URZqWBigcVOQ", Language::En, None)
.unwrap();
assert!(
map_res.warnings.is_empty(),
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!("map_channel_videos_cont", map_res.c, {
".items[].publish_date" => "[date]",
});
}
#[test]
fn map_channel_playlists() {
let json_path = Path::new("testfiles/channel/channel_playlists.json");
@ -585,7 +454,7 @@ mod tests {
let channel: response::Channel =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Channel<Paginator<ChannelPlaylist>>> = channel
let map_res: MapResult<Channel<Paginator<PlaylistItem>>> = channel
.map_response("UC2DjFE7Xf11URZqWBigcVOQ", Language::En, None)
.unwrap();
@ -597,25 +466,6 @@ mod tests {
insta::assert_ron_snapshot!("map_channel_playlists", map_res.c);
}
#[test]
fn map_channel_playlists_cont() {
let json_path = Path::new("testfiles/channel/channel_playlists_cont.json");
let json_file = File::open(json_path).unwrap();
let channel: response::ChannelCont =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Paginator<ChannelPlaylist>> = channel
.map_response("UC2DjFE7Xf11URZqWBigcVOQ", Language::En, None)
.unwrap();
assert!(
map_res.warnings.is_empty(),
"deserialization/mapping warnings: {:?}",
map_res.warnings
);
insta::assert_ron_snapshot!("map_channel_playlists_cont", map_res.c);
}
#[test]
fn map_channel_info() {
let json_path = Path::new("testfiles/channel/channel_info.json");