pub mod video;
pub use self::video::Video;
use crate::{
youtube::{
self,
browse::{
self,
playlist::{PlaylistSidebarPrimaryInfoRenderer, PlaylistSidebarSecondaryInfoRenderer},
},
innertube::Browse,
},
Client, Thumbnail,
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Id(pub(crate) String);
impl std::str::FromStr for Id {
type Err = crate::error::Id<0>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
const ID_PREFIXES: &[&str] = &["PL", "RD", "UL", "UU", "PU", "OL", "LL", "FL", "WL"];
let id = youtube::strip_url_prefix(s)
.strip_prefix("playlist?list=")
.unwrap_or(s);
if id.chars().all(crate::id::validate_char)
&& ID_PREFIXES.iter().any(|prefix| id.starts_with(prefix))
{
Ok(Self(id.to_string()))
} else {
Err(crate::error::Id::InvalidId(s.to_string()))
}
}
}
impl std::fmt::Display for Id {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Clone)]
pub struct Playlist {
client: Client,
response: browse::playlist::Ok,
}
impl Playlist {
pub(crate) async fn get(client: crate::Client, id: Id) -> crate::Result<Self> {
let response: browse::playlist::Result = client.api.browse(Browse::Playlist(id)).await?;
let response = response.into_std()?;
Ok(Self { client, response })
}
fn microformat(&self) -> &browse::playlist::MicroformatDataRenderer {
&self.response.microformat.microformat_data_renderer
}
fn primary_sidebar(&self) -> &PlaylistSidebarPrimaryInfoRenderer {
&self
.response
.sidebar
.playlist_sidebar_renderer
.items
.0
.playlist_sidebar_primary_info_renderer
}
fn secondary_sidebar(&self) -> Option<&PlaylistSidebarSecondaryInfoRenderer> {
self.response
.sidebar
.playlist_sidebar_renderer
.items
.1
.as_ref()
.map(|x| &x.playlist_sidebar_secondary_info_renderer)
}
pub fn id(&self) -> Id {
self.microformat()
.url_canonical
.clone()
.split_off(37)
.parse()
.expect("Id returned from YouTube was not parsable")
}
pub fn title(&self) -> &str {
&self.microformat().title
}
pub fn description(&self) -> &str {
&self.microformat().description
}
pub fn channel(&self) -> Option<Channel<'_>> {
let sec = &self.secondary_sidebar()?.video_owner.video_owner_renderer;
Some(Channel {
client: &self.client,
id: sec.id(),
name: sec.name(),
})
}
pub fn unlisted(&self) -> bool {
self.microformat().unlisted
}
pub fn thumbnails(&self) -> &Vec<Thumbnail> {
&self.microformat().thumbnail.thumbnails
}
pub fn views(&self) -> u64 {
self.primary_sidebar().stats.1.as_number()
}
pub fn length(&self) -> u64 {
self.primary_sidebar().stats.0.as_number()
}
pub fn videos(&self) -> impl futures_core::Stream<Item = Result<Video, video::Error>> {
let contents = self.response.contents.clone();
let client = self.client.clone();
async_stream::stream! {
let mut videos: Box<dyn Iterator<Item = browse::playlist::PlaylistItem> + Send + Sync> =
Box::new(contents.into_videos());
while let Some(video) = videos.next() {
match video {
browse::playlist::PlaylistItem::PlaylistVideoRenderer(video) => {
yield Video::new(client.clone(), video);
}
browse::playlist::PlaylistItem::ContinuationItemRenderer(continuation) => {
assert!(
videos.next().is_none(),
"Found a continuation in the middle of videos!"
);
let response: browse::playlist::Continuation = client
.api
.browse(Browse::Continuation(continuation.get()))
.await
.expect("Continuation request failed");
videos = Box::new(response.into_videos());
}
}
}
}
}
}
impl std::fmt::Debug for Playlist {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Playlist")
.field("title", &self.title())
.field("description", &self.description())
.field("unlisted", &self.unlisted())
.field("thumbnails", &self.thumbnails())
.finish()
}
}
impl PartialEq for Playlist {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for Playlist {}
#[derive(Clone)]
pub struct Channel<'a> {
client: &'a Client,
id: crate::channel::Id,
name: &'a str,
}
impl<'a> Channel<'a> {
pub fn id(&self) -> crate::channel::Id {
self.id
}
pub fn name(&self) -> &str {
self.name
}
pub async fn upgrade(&self) -> crate::Result<crate::Channel> {
self.client.channel(self.id).await
}
}
impl<'a> std::fmt::Debug for Channel<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Channel")
.field("id", &self.id)
.field("name", &self.name)
.finish()
}
}
impl<'a> PartialEq for Channel<'a> {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl<'a> Eq for Channel<'a> {}