crunchyroll 0.1.0

Pure Rust implementation of the crunchyroll beta api.
Documentation
#![cfg(feature = "parse")]

use crate::error::CrunchyrollError;
use crate::{Crunchyroll, Result};
use regex::Regex;

/// Types of Crunchyroll urls, pointing to series, episodes or movies.
#[derive(Clone, Debug)]
pub enum UrlType {
    /// The parsed url points to a beta series. Use [`Crunchyroll::series_from_id`] with the value
    /// of this field to get a usable struct out of it.
    BetaSeries(String),
    /// The parsed url points to a beta movie listing. Use [`Crunchyroll::movie_listing_from_id`]
    /// with the value of this field to get a usable struct out of it. This kind of url might not
    /// exist in Crunchyroll beta at all but to be api compatible it's included anyway.
    BetaMovieListing(String),
    /// The parsed url points to a beta episode or movie. You can either try
    /// [`Crunchyroll::episode_from_id`] and [`Crunchyroll::movie_from_id`] to guess if it's a
    /// episode or movie (in 99.9% of the time it will be a episode, because (at the time of writing)
    /// Crunchyroll has only 3 movies which are listed as movies. All other movies are listed as
    /// episodes. Makes sense I know) or use [`crate::Media::from_id`]. The value of this field is
    /// the id you have to use in all shown methods.
    BetaEpisodeOrMovie(String),

    /// The parsed url points to a classic series or movie listing. Because classic urls are
    /// structured poorly they cannot be parsed very accurate. You gave to search for the series /
    /// movie listing which is the value of this field and hope that the Crunchyroll api returns the
    /// correct series / movie listing.
    ///
    /// _Please just use Crunchyroll beta urls_.
    ClassicSeriesOrMovieListing(String),
    /// The parsed url points to a classic episode. Because classic urls are structured poorly they
    /// cannot be parsed very accurate. You have to search for the
    /// [`UrlType::ClassicEpisodeOrMovie::series_name`] series and then look up the episode which has
    /// [`UrlType::ClassicEpisodeOrMovie::episode_name`] in one of the episode name fields. You can
    /// also (in addition) check if [`UrlType::ClassicEpisodeOrMovie::number`] which represents the
    /// episode number matches with the episode number you got from the looked up episodes. But
    /// [`UrlType::ClassicEpisodeOrMovie::number`] is not so accurate as it seems, for example
    /// episode number 24.9 gets converted to 249.
    ///
    /// _Please just use Crunchyroll beta urls_.
    ClassicEpisode {
        series_name: String,
        episode_name: String,
        number: String,
    },
    /// Just like [`UrlType::ClassicEpisode`] but without episode number and movie instead of episode.
    ///
    /// _Please just use Crunchyroll beta urls_.
    ClassicMovie {
        movie_listing_name: String,
        movie_name: String,
    },
}

impl Crunchyroll {
    /// Extract information out of Crunchyroll urls which are pointing to episodes / movies /
    /// series.
    ///
    /// Note: It is recommended to use only Crunchyroll beta urls (`beta.crunchyroll.com`) for this
    /// function. Classic urls cannot be parsed properly to guarantee that the results this function
    /// delivers really is what the url points to.
    pub fn parse_url<S: AsRef<str>>(url: S) -> Result<UrlType> {
        // the regex calls are pretty ugly but for performance reasons it's the best to define them
        // only if needed. once the std lazy api is stabilized they can be moved to the root of this
        // file to make it look cleaner. an external crate to call the regexes lazy would also be an
        // option but it would a little overload if it's only used here

        if let Some(capture) = Regex::new(r"^https?://beta\.crunchyroll\.com/([a-zA-Z]{2}/)?(?P<type>series|movie_listing)/(?P<id>[a-zA-Z]+).*$")
            .unwrap()
            .captures(url.as_ref())
        {
            let id = capture.name("id").unwrap().as_str().to_string();
            match capture.name("type").unwrap().as_str() {
                "series" => Ok(UrlType::BetaSeries(id)),
                "movie_listing" => Ok(UrlType::BetaMovieListing(id)),
                _ => Err(CrunchyrollError::Internal("could not get url type. this should never happen".into()))
            }
        } else if let Some(capture) = Regex::new(r"^https?://beta\.crunchyroll\.com/([a-zA-Z]{2}/)?watch/(?P<id>[a-zA-Z]+).*$")
            .unwrap()
            .captures(url.as_ref())
        {
            Ok(UrlType::BetaEpisodeOrMovie(capture.name("id").unwrap().as_str().to_string()))
        } else if let Some(aaargh_please_just_use_beta_urls) = Regex::new(r"^https?://(www\.)?crunchyroll\.com/([a-zA-Z]{2}/)?(?P<series_or_movie_name>[^/]+)(/videos)?/?$")
            .unwrap()
            .captures(url.as_ref())
        {
            Ok(UrlType::ClassicSeriesOrMovieListing(aaargh_please_just_use_beta_urls.name("series_or_movie_name").unwrap().as_str().to_string()))
        } else if let Some(why_do_i_have_to_still_support_this) = Regex::new(r"^https?://(www\.)?crunchyroll\.com/([a-zA-Z]{2}/)?(?P<series_name>[^/]+)/episode-(?P<number>[0-9]+)-(?P<episode_name>.+)-.*$")
            .unwrap()
            .captures(url.as_ref())
        {
            Ok(UrlType::ClassicEpisode {
                series_name: why_do_i_have_to_still_support_this.name("series_name").unwrap().as_str().to_string(),
                episode_name: why_do_i_have_to_still_support_this.name("episode_name").unwrap().as_str().to_string(),
                number: why_do_i_have_to_still_support_this.name("number").unwrap().as_str().to_string(),
            })
        } else if let Some(plsss) = Regex::new(r"^https?://(www\.)?crunchyroll\.com/([a-zA-Z]{2}/)?(?P<movie_listing_name>[^/]+)/(?P<movie_name>.+)-.*$")
            .unwrap()
            .captures(url.as_ref())
        {
            Ok(UrlType::ClassicMovie {
                movie_listing_name: plsss.name("movie_listing_name").unwrap().as_str().to_string(),
                movie_name: plsss.name("movie_name").unwrap().as_str().to_string(),
            })
        } else {
            Err(CrunchyrollError::Input("invalid url".into()))
        }
    }
}