feat: add channel video/playlist continuation

This commit is contained in:
ThetaDev 2022-09-26 21:26:22 +02:00
parent 6f1a1c4440
commit 6ac5bc3782
14 changed files with 19775 additions and 29 deletions

View file

@ -1,4 +1,4 @@
use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
use reqwest::Method;
use serde::Serialize;
use url::Url;
@ -14,7 +14,7 @@ use crate::{
use super::{
response::{self, IsLive, IsShort},
ClientType, MapResponse, RustyPipeQuery, YTContext,
ClientType, MapResponse, QContinuation, RustyPipeQuery, YTContext,
};
#[derive(Debug, Serialize)]
@ -75,6 +75,27 @@ impl RustyPipeQuery {
.await
}
pub async fn channel_videos_continuation(
&self,
ctoken: &str,
) -> Result<Paginator<ChannelVideo>> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QContinuation {
context,
continuation: ctoken.to_owned(),
};
self.execute_request::<response::ChannelCont, _, _>(
ClientType::Desktop,
"channel_videos_continuation",
ctoken,
Method::POST,
"browse",
&request_body,
)
.await
}
pub async fn channel_playlists(
&self,
channel_id: &str,
@ -97,6 +118,27 @@ impl RustyPipeQuery {
.await
}
pub async fn channel_playlists_continuation(
&self,
ctoken: &str,
) -> Result<Paginator<ChannelPlaylist>> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QContinuation {
context,
continuation: ctoken.to_owned(),
};
self.execute_request::<response::ChannelCont, _, _>(
ClientType::Desktop,
"channel_videos_continuation",
ctoken,
Method::POST,
"browse",
&request_body,
)
.await
}
pub async fn channel_info(&self, channel_id: &str) -> Result<Channel<ChannelInfo>> {
let context = self.get_context(ClientType::Desktop, true).await;
let request_body = QChannel {
@ -235,6 +277,44 @@ 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>>> {
let mut actions = self.on_response_received_actions;
let res = some_or_bail!(
actions.try_swap_remove(0),
Err(anyhow!("no received action"))
)
.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>>> {
let mut actions = self.on_response_received_actions;
let res = some_or_bail!(
actions.try_swap_remove(0),
Err(anyhow!("no received action"))
)
.append_continuation_items_action
.continuation_items;
Ok(map_playlists(res))
}
}
fn map_videos(
res: MapResult<Vec<response::VideoListItem>>,
lang: Language,
@ -413,7 +493,9 @@ mod tests {
use crate::{
client::{response, MapResponse, RustyPipe},
model::{Channel, ChannelOrder, ChannelVideo, Language, Paginator},
model::{
Channel, ChannelInfo, ChannelOrder, ChannelPlaylist, ChannelVideo, Language, Paginator,
},
serializer::MapResult,
};
@ -456,6 +538,12 @@ mod tests {
)
}
}
let next = channel.content.next(rp.query()).await.unwrap().unwrap();
assert!(
!next.is_exhausted() && !next.items.is_empty(),
"no more videos"
);
}
#[tokio::test]
@ -474,6 +562,12 @@ mod tests {
!channel.content.items.is_empty() && !channel.content.is_exhausted(),
"got no playlists"
);
let next = channel.content.next(rp.query()).await.unwrap().unwrap();
assert!(
!next.is_exhausted() && !next.items.is_empty(),
"no more playlists"
);
}
#[tokio::test]
@ -563,4 +657,82 @@ mod tests {
".content.items[].publish_date" => "[date]",
});
}
#[test]
fn t_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 t_map_channel_playlists() {
let json_path = Path::new("testfiles/channel/channel_playlists.json");
let json_file = File::open(json_path).unwrap();
let channel: response::Channel =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Channel<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", map_res.c);
}
#[test]
fn t_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 t_map_channel_info() {
let json_path = Path::new("testfiles/channel/channel_info.json");
let json_file = File::open(json_path).unwrap();
let channel: response::Channel =
serde_json::from_reader(BufReader::new(json_file)).unwrap();
let map_res: MapResult<Channel<ChannelInfo>> = 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_info", map_res.c);
}
}