Esempio n. 1
0
 def __init__(self, username=None, password=None, state=None):
     self._state = {
         'username': username,
         'password': password,
     }
     self._ajax_api = AjaxApi()
     self._android_api = AndroidApi()
     if state is not None:
         self.set_state(state)
Esempio n. 2
0
 def __init__(self, username=None, password=None, state=None):
     self._state = {
         'username': username,
         'password': password,
     }
     self._ajax_api = AjaxApi()
     self._android_api = AndroidApi()
     self._manga_api = AndroidMangaApi()
     if state is not None:
         self.set_state(state)
Esempio n. 3
0
class MetaApi(ApiInterface):
    """High level interface to crunchyroll
    """
    def __init__(self, username=None, password=None, state=None):
        self._state = {
            'username': username,
            'password': password,
        }
        self._ajax_api = AjaxApi()
        self._android_api = AndroidApi()
        if state is not None:
            self.set_state(state)

    @property
    def session_started(self):
        return self._ajax_api.session_started and \
            self._android_api.session_started

    @property
    def logged_in(self):
        return self._ajax_api.logged_in and self._android_api.logged_in

    @property
    def has_credentials(self):
        return self._state['username'] is not None and \
            self._state['password'] is not None

    def get_state(self):
        return json.dumps({
            'meta': self._state,
            'ajax': self._ajax_api.get_state(),
            'android': self._android_api.get_state(),
        })

    def set_state(self, state):
        # TODO: error handling here
        decoded_state = json.loads(state)
        self._state = decoded_state['meta']
        self._ajax_api.set_state(decoded_state['ajax'])
        self._android_api.set_state(decoded_state['android'])

    @optional_android_logged_in
    def is_premium(self, media_type):
        """Get if the user is premium for a given media type

        @param str media_type       should be one of ANDROID.MEDIA_TYPE_*
        @return bool
        """
        return self._android_api.is_premium(media_type)

    def start_session(self):
        """Start the underlying APIs sessions

        Calling this is not required, it will be called automatically if
        a method that needs a session is called

        @return bool
        """
        self._android_api.start_session()
        return self.session_started

    @require_session_started
    def login(self, username, password):
        """Login with the given username/email and password

        Calling this method is not required if credentials were provided in
        the constructor, but it could be used to switch users or something maybe

        @return bool
        """
        # we could get stuck in an inconsistent state if got an exception while
        # trying to login with different credentials than what is stored so
        # we rollback the state to prevent that
        state_snapshot = self._state.copy()
        try:
            self._ajax_api.User_Login(name=username, password=password)
            self._android_api.login(account=username, password=password)
        except Exception as err:
            # something went wrong, rollback
            self._state = state_snapshot
            raise err
        self._state['username'] = username
        self._state['password'] = password
        return self.logged_in

    @optional_android_logged_in
    @return_collection(Series)
    def list_anime_series(self,
                          sort=META.SORT_ALPHA,
                          limit=META.MAX_SERIES,
                          offset=0):
        """Get a list of anime series

        @param str sort     pick how results should be sorted, should be one
                                of META.SORT_*
        @param int limit    limit number of series to return, there doesn't
                                seem to be an upper bound
        @param int offset   list series starting from this offset, for pagination
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_ANIME,
            filter=sort,
            limit=limit,
            offset=offset)
        return result

    @optional_android_logged_in
    @return_collection(Series)
    def list_drama_series(self,
                          sort=META.SORT_ALPHA,
                          limit=META.MAX_SERIES,
                          offset=0):
        """Get a list of drama series

        @param str sort     pick how results should be sorted, should be one
                                of META.SORT_*
        @param int limit    limit number of series to return, there doesn't
                                seem to be an upper bound
        @param int offset   list series starting from this offset, for pagination
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_DRAMA,
            filter=sort,
            limit=limit,
            offset=offset)
        return result

    @optional_android_logged_in
    @return_collection(Series)
    def search_anime_series(self, query_string):
        """Search anime series list by series name, case-sensitive

        @param str query_string     string to search for, note that the search
                                        is very simplistic and only matches against
                                        the start of the series name, ex) search
                                        for "space" matches "Space Brothers" but
                                        wouldn't match "Brothers Space"
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_ANIME,
            filter=ANDROID.FILTER_PREFIX + query_string)
        return result

    @optional_android_logged_in
    @return_collection(Series)
    def search_drama_series(self, query_string):
        """Search drama series list by series name, case-sensitive

        @param str query_string     string to search for, note that the search
                                        is very simplistic and only matches against
                                        the start of the series name, ex) search
                                        for "space" matches "Space Brothers" but
                                        wouldn't match "Brothers Space"
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_DRAMA,
            filter=ANDROID.FILTER_PREFIX + query_string)
        return result

    @optional_android_logged_in
    @return_collection(Media)
    def list_media(self,
                   series,
                   sort=META.SORT_DESC,
                   limit=META.MAX_MEDIA,
                   offset=0):
        """List media for a given series or collection

        @param crunchyroll.models.Series series the series to search for
        @param str sort                         choose the ordering of the
                                                    results, only META.SORT_DESC
                                                    is known to work
        @param int limit                        limit size of results
        @param int offset                       start results from this index,
                                                    for pagination
        @return list<crunchyroll.models.Media>
        """
        params = {
            'sort': sort,
            'offset': offset,
            'limit': limit,
        }
        params.update(self._get_series_query_dict(series))
        result = self._android_api.list_media(**params)
        return result

    @optional_android_logged_in
    @return_collection(Media)
    def search_media(self, series, query_string):
        """Search for media from a series starting with query_string, case-sensitive

        @param crunchyroll.models.Series series     the series to search in
        @param str query_string                     the search query, same restrictions
                                                        as `search_anime_series`
        @return list<crunchyroll.models.Media>
        """
        params = {
            'sort': ANDROID.FILTER_PREFIX + query_string,
        }
        params.update(self._get_series_query_dict(series))
        result = self._android_api.list_media(**params)
        return result

    @optional_ajax_logged_in
    def get_media_stream(self, media_item, format, quality):
        """Get the stream data for a given media item

        @param crunchyroll.models.Media media_item
        @param int format
        @param int quality
        @return crunchyroll.models.MediaStream
        """
        result = self._ajax_api.VideoPlayer_GetStandardConfig(
            media_id=media_item.media_id,
            video_format=format,
            video_quality=quality)
        return MediaStream(result)

    @optional_ajax_logged_in
    def get_stream_info(self, media_item, format, quality):
        result = self._ajax_api.VideoEncode_GetStreamInfo(
            media_id=media_item.media_id,
            video_format=format,
            video_encode_quality=quality)
        return StreamInfo(result)

    @return_collection(SubtitleStub)
    def get_subtitle_stubs(self, media_item):
        result = self._ajax_api.Subtitle_GetListing(
            media_id=media_item.media_id)
        return XmlModel(result)['subtitle']

    def unfold_subtitle_stub(self, subtitle_stub):
        """Turn a SubtitleStub into a full Subtitle object

        @param crunchyroll.models.SubtitleStub subtitle_stub
        @return crunchyroll.models.Subtitle
        """
        return Subtitle(
            self._ajax_api.Subtitle_GetXml(
                subtitle_script_id=int(subtitle_stub.id)))

    @optional_ajax_logged_in
    def get_stream_formats(self, media_item):
        """Get the available media formats for a given media item

        @param crunchyroll.models.Media
        @return dict
        """
        scraper = ScraperApi(self._ajax_api._connector)
        formats = scraper.get_media_formats(media_item.media_id)
        return formats

    @require_android_logged_in
    @return_collection(Series)
    def list_queue(self, media_types=[META.TYPE_ANIME, META.TYPE_DRAMA]):
        """List the series in the queue, optionally filtering by type of media

        @param list<str> media_types    a list of media types to filter the queue
                                            with, should be of META.TYPE_*
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.queue(media_types='|'.join(media_types))
        return [queue_item['series'] for queue_item in result]

    @require_android_logged_in
    def add_to_queue(self, series):
        """Add a series to the queue

        @param crunchyroll.models.Series series
        @return bool
        """
        result = self._android_api.add_to_queue(series_id=series.series_id)
        return result

    @require_android_logged_in
    def remove_from_queue(self, series):
        """Remove a series from the queue

        @param crunchyroll.models.Series series
        @return bool
        """
        result = self._android_api.remove_from_queue(
            series_id=series.series_id)
        return result

    def _get_series_query_dict(self, series):
        """Pick between collection_id and series_id params in series models for the
        Android API

        @param crunchyroll.models.Series series
        @return dict
        """
        if hasattr(series, 'series_id'):
            return {'series_id': series.series_id}
        else:
            return {'collection_id': series.collection_id}
Esempio n. 4
0
class MetaApi(ApiInterface):
    """High level interface to crunchyroll
    """

    def __init__(self, username=None, password=None, state=None):
        self._state = {
            'username': username,
            'password': password,
        }
        self._ajax_api = AjaxApi()
        self._android_api = AndroidApi()
        self._manga_api = AndroidMangaApi()
        if state is not None:
            self.set_state(state)

    @property
    def session_started(self):
        return self._ajax_api.session_started and \
            self._android_api.session_started and \
            self._manga_api.session_started

    @property
    def logged_in(self):
        return self._ajax_api.logged_in and self._android_api.logged_in and \
            self._manga_api.logged_in

    @property
    def has_credentials(self):
        return self._state['username'] is not None and \
            self._state['password'] is not None

    def get_state(self):
        return json.dumps({
            'meta':     self._state,
            'ajax':     self._ajax_api.get_state(),
            'android':  self._android_api.get_state(),
            'manga':    self._manga_api.get_state(),
        })

    def set_state(self, state):
        # TODO: error handling here
        decoded_state = json.loads(state)
        self._state = decoded_state['meta']
        self._ajax_api.set_state(decoded_state['ajax'])
        self._android_api.set_state(decoded_state['android'])
        self._manga_api.set_state(decoded_state['manga'])

    @optional_android_logged_in
    def is_premium(self, media_type):
        """Get if the user is premium for a given media type

        @param str media_type       should be one of ANDROID.MEDIA_TYPE_*
        @return bool
        """
        return self._android_api.is_premium(media_type)

    def start_session(self):
        """Start the underlying APIs sessions

        Calling this is not required, it will be called automatically if
        a method that needs a session is called

        @return bool
        """
        self._android_api.start_session()
        self._manga_api.cr_start_session()
        return self.session_started

    @require_session_started
    def login(self, username, password):
        """Login with the given username/email and password

        Calling this method is not required if credentials were provided in
        the constructor, but it could be used to switch users or something maybe

        @return bool
        """
        # we could get stuck in an inconsistent state if got an exception while
        # trying to login with different credentials than what is stored so
        # we rollback the state to prevent that
        state_snapshot = self._state.copy()
        try:
            self._ajax_api.User_Login(name=username, password=password)
            self._android_api.login(account=username, password=password)
            self._manga_api.cr_login(account=username, password=password)
        except Exception as err:
            # something went wrong, rollback
            self._state = state_snapshot
            raise err
        self._state['username'] = username
        self._state['password'] = password
        return self.logged_in

    @optional_android_logged_in
    @return_collection(Series)
    def list_anime_series(self, sort=META.SORT_ALPHA, limit=META.MAX_SERIES, offset=0):
        """Get a list of anime series

        @param str sort     pick how results should be sorted, should be one
                                of META.SORT_*
        @param int limit    limit number of series to return, there doesn't
                                seem to be an upper bound
        @param int offset   list series starting from this offset, for pagination
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_ANIME,
            filter=sort,
            limit=limit,
            offset=offset)
        return result

    @optional_android_logged_in
    @return_collection(Series)
    def list_drama_series(self, sort=META.SORT_ALPHA, limit=META.MAX_SERIES, offset=0):
        """Get a list of drama series

        @param str sort     pick how results should be sorted, should be one
                                of META.SORT_*
        @param int limit    limit number of series to return, there doesn't
                                seem to be an upper bound
        @param int offset   list series starting from this offset, for pagination
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_DRAMA,
            filter=sort,
            limit=limit,
            offset=offset)
        return result

    @require_session_started
    @return_collection(Series)
    def list_manga_series(self, filter=None, content_type='jp_manga'):
        """Get a list of manga series
        """

        result = self._manga_api.list_series(filter, content_type)
        return result

    @optional_android_logged_in
    @return_collection(Series)
    def search_anime_series(self, query_string):
        """Search anime series list by series name, case-sensitive

        @param str query_string     string to search for, note that the search
                                        is very simplistic and only matches against
                                        the start of the series name, ex) search
                                        for "space" matches "Space Brothers" but
                                        wouldn't match "Brothers Space"
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_ANIME,
            filter=ANDROID.FILTER_PREFIX + query_string)
        return result

    @optional_android_logged_in
    @return_collection(Series)
    def search_drama_series(self, query_string):
        """Search drama series list by series name, case-sensitive

        @param str query_string     string to search for, note that the search
                                        is very simplistic and only matches against
                                        the start of the series name, ex) search
                                        for "space" matches "Space Brothers" but
                                        wouldn't match "Brothers Space"
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.list_series(
            media_type=ANDROID.MEDIA_TYPE_DRAMA,
            filter=ANDROID.FILTER_PREFIX + query_string)
        return result

    @optional_manga_logged_in
    @return_collection(Series)
    def search_manga_series(self, query_string):
        """Search the manga series list by name, case-insensitive

        @param str query_string

        @return list<crunchyroll.models.Series>
        """

        result = self._manga_api.list_series()
        return [series for series in result \
            if series['locale']['enUS']['name'].lower().startswith(
                query_string.lower())]

    @optional_android_logged_in
    @return_collection(Media)
    def list_media(self, series, sort=META.SORT_DESC, limit=META.MAX_MEDIA, offset=0):
        """List media for a given series or collection

        @param crunchyroll.models.Series series the series to search for
        @param str sort                         choose the ordering of the
                                                    results, only META.SORT_DESC
                                                    is known to work
        @param int limit                        limit size of results
        @param int offset                       start results from this index,
                                                    for pagination
        @return list<crunchyroll.models.Media>
        """
        params = {
            'sort': sort,
            'offset': offset,
            'limit': limit,
        }
        params.update(self._get_series_query_dict(series))
        result = self._android_api.list_media(**params)
        return result

    @optional_manga_logged_in
    @return_collection(Chapter)
    def list_chapters(self, series):
        """
        """
        if self.logged_in:
            result = self._manga_api.list_chapters(
                series_id=series.series_id, user_id=self._manga_api._user_data['user_id'])
        else:
            result = self._manga_api.list_chapters(series_id=series.series_id)
        return result['chapters']

    @optional_manga_logged_in
    @return_collection(Page)
    def list_pages(self, chapter):
        """
        """

        result = self._manga_api.list_chapter(chapter_id=chapter.chapter_id)
        return result['pages']

    @optional_manga_logged_in
    def get_page_stream(self, page, locale='enUS'):
        req = self._manga_api._connector.get(
            page.locale[locale].encrypted_composed_image_url, stream=True)
        return decrypt_image_stream(req)

    @optional_android_logged_in
    @return_collection(Media)
    def search_media(self, series, query_string):
        """Search for media from a series starting with query_string, case-sensitive

        @param crunchyroll.models.Series series     the series to search in
        @param str query_string                     the search query, same restrictions
                                                        as `search_anime_series`
        @return list<crunchyroll.models.Media>
        """
        params = {
            'sort': ANDROID.FILTER_PREFIX + query_string,
        }
        params.update(self._get_series_query_dict(series))
        result = self._android_api.list_media(**params)
        return result

    @optional_ajax_logged_in
    def get_media_stream(self, media_item, format, quality):
        """Get the stream data for a given media item

        @param crunchyroll.models.Media media_item
        @param int format
        @param int quality
        @return crunchyroll.models.MediaStream
        """
        result = self._ajax_api.VideoPlayer_GetStandardConfig(
            media_id=media_item.media_id,
            video_format=format,
            video_quality=quality)
        return MediaStream(result)

    @optional_ajax_logged_in
    def get_stream_info(self, media_item, format, quality):
        result = self._ajax_api.VideoEncode_GetStreamInfo(
            media_id=media_item.media_id,
            video_format=format,
            video_encode_quality=quality)
        return StreamInfo(result)

    @return_collection(SubtitleStub)
    def get_subtitle_stubs(self, media_item):
        result = self._ajax_api.Subtitle_GetListing(media_id=media_item.media_id)
        return XmlModel(result)['subtitle']

    def unfold_subtitle_stub(self, subtitle_stub):
        """Turn a SubtitleStub into a full Subtitle object

        @param crunchyroll.models.SubtitleStub subtitle_stub
        @return crunchyroll.models.Subtitle
        """
        return Subtitle(self._ajax_api.Subtitle_GetXml(
            subtitle_script_id=int(subtitle_stub.id)))

    @optional_ajax_logged_in
    def get_stream_formats(self, media_item):
        """Get the available media formats for a given media item

        @param crunchyroll.models.Media
        @return dict
        """
        scraper = ScraperApi(self._ajax_api._connector)
        formats = scraper.get_media_formats(media_item.media_id)
        return formats

    @require_android_logged_in
    @return_collection(Series)
    def list_queue(self, media_types=[META.TYPE_ANIME, META.TYPE_DRAMA]):
        """List the series in the queue, optionally filtering by type of media

        @param list<str> media_types    a list of media types to filter the queue
                                            with, should be of META.TYPE_*
        @return list<crunchyroll.models.Series>
        """
        result = self._android_api.queue(media_types='|'.join(media_types))
        return [queue_item['series'] for queue_item in result]

    @require_android_logged_in
    def add_to_queue(self, series):
        """Add a series to the queue

        @param crunchyroll.models.Series series
        @return bool
        """
        result = self._android_api.add_to_queue(series_id=series.series_id)
        return result

    @require_android_logged_in
    def remove_from_queue(self, series):
        """Remove a series from the queue

        @param crunchyroll.models.Series series
        @return bool
        """
        result = self._android_api.remove_from_queue(series_id=series.series_id)
        return result

    def _get_series_query_dict(self, series):
        """Pick between collection_id and series_id params in series models for the
        Android API

        @param crunchyroll.models.Series series
        @return dict
        """
        if hasattr(series, 'series_id'):
            return {'series_id': series.series_id}
        else:
            return {'collection_id': series.collection_id}