Metadata-Version: 2.4
Name: anime_parsers_ru
Version: 1.16.2
Summary: Python package for parsing russian anime players
Author-email: YaNesyTortiK <ya.nesy.tortik.email@gmail.com>
Maintainer-email: YaNesyTortiK <ya.nesy.tortik.email@gmail.com>
Project-URL: Homepage, https://github.com/YaNesyTortiK/AnimeParsers
Project-URL: Issues, https://github.com/YaNesyTortiK/AnimeParsers/issues
Keywords: anime,parser,kodik,parsing,aniboom,animego,cvh,jutsu,shikimori,kodikapi,kodik api,аниме,парсинг,кодик,парсер,анибум,анимего,джутсу,шикимори,кодик апи
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: beautifulsoup4>=4.14.0
Requires-Dist: requests[socks]>=2.32.0
Provides-Extra: async
Requires-Dist: aiohttp>=3.10.0; extra == "async"
Requires-Dist: aiohttp-socks>=0.11.0; extra == "async"
Provides-Extra: lxml
Requires-Dist: lxml>=6.0.0; extra == "lxml"
Dynamic: license-file

# AnimeParsers
[![PyPI - Downloads](https://img.shields.io/pypi/dm/anime-parsers-ru?label=PyPI%20downloads)
]() [![PyPI - Version](https://img.shields.io/pypi/v/anime-parsers-ru)
]()
## Описание
Данный проект нацелен на создание наиболее широкого спектра парсеров на python для различных аниме-плееров в русскоязычном/снг сегменте

Актуальная стабильная версия доступна на [pypi](https://pypi.org/project/anime-parsers-ru/) или в [релизах](https://github.com/YaNesyTortiK/AnimeParsers/releases) на гитхабе 

## Что есть на данный момент
- [x] Парсер Kodik (__требуется api ключ__) (+ асинхронный)
- [x] Парсер Animego (api ключ не требуется)(+ асинхронный)
    - [x] Парсер плеера Aniboom
    - [x] Парсер плеера CVH
- [x] Парсер JutSu (без функции поиска, не требует api ключей) (доступно через прокси)
- [x] Парсер Shikimori (с возможностью использовать псевдо-api, не требует api ключей) (+ асинхронный)

## Установка
- Стандартная установка:
    ```commandline
    pip install anime-parsers-ru
    ```
- Установка с lxml:
    ```commandline
    pip install anime-parsers-ru[lxml]
    ```
    Для использования lxml при инициализации парсера установите параметр `use_lxml = True`
- Установка с асинхронными библиотеками (без lxml):
    ```commandline
    pip install anime-parsers-ru[async]
    ```

Установка lxml вручную:
```commandline
pip install lxml
```

# Инструкция к парсерам

## Оглавление
- [Kodik инструкция](#kodik-инструкция)
- [AnimeGO инструкция](#animego-инструкция)
- [JutSu инструкция](#jutsu-инструкция)
- [Shikimori инструкция](#shikimori-инструкция)
- [Типы Исключений](#типы-исключений)

## Kodik инструкция

> [!IMPORTANT]
> Если вы хотите использовать функции библиотеки для апи кодика, то вся документация расположена в файле [KODIK_API.md](KODIK_API.md) 

> [!WARNING]
> Токен получаемый с помощью функции `get_token` НЕ работает для функций base_search, base_search_by_id, get_list и search
> По умолчанию данная функция не используется и класс требует от пользователя указать корректный токен.
> Если вы хотите использовать ограниченный функционал библиотеки, то можете при инициализации указать параметры 
> `token=KodikParser.get_token(), validate_token=False` (Для асинхронного параметр `token=KodikParserAsync.get_token_sync()`)

> [!TIP]
> В большинстве случаев в комментариях к функциям описаны шаблоны и возможные значения возвращаемых данных

0. Установите и импортируйте библиотеку
    
    Стандартно:
    ```commandline
    pip install anime-parsers-ru
    ```
    С lxml:
    ```commandline
    pip install anime-parsers-ru[lxml]
    ```
    ```python
    from anime_parsers_ru import KodikParser

    parser = KodikParser(<ваш api ключ>)
    ```

    __Для асинхронного кода__:
    ```commandline
    pip install anime-parsers-ru[async]
    ```
    (Установка без lxml)
    ```python
    from anime_parsers_ru import KodikParserAsync

    parser = KodikParserAsync(<ваш api ключ>)
    ```
    Для асинхронного парсера, после окончания работы следует вызвать метод
    ```python
    parser.close_async_session()
    ```
    Иначе выйдет ошибка о незакрытой сессии


> [!IMPORTANT]
> Если у вас нет токена для кодика, то оставьте параметр token при инициализации равным None. Парсер попробует получить токен автоматически.
> После получения токена, настоятельно рекомендуется сохранить этот токен и, для избегания задержек на получение токена и проверки, при следующей инициализации парсера, указать этот токен.
> Как это работает вы можете прочитать здесь: [TOKENS.md](kdk_tokns/TOKENS.md)

Также, если при запросах вы получаете ошибку о недоступности кодика в вашем регионе, можете при инициализации указать параметр proxy="http://host:port" для проксирования всех запросов к кодику. (Также поддерживается socks5)

1. Поиск аниме по названию
    ```python
    parser.search(title="Наруто", limit=None, include_material_data=True, anime_status=None, strict=False, only_anime=False) # список словарей
    # title - Название аниме/фильма/сериала
    # limit - количество результатов выдачи (int) (результатов будет сильно меньше чем указанное число, так как в выдаче результаты повторяются)
    # include_material_data - Добавлять дополнительные данные об элементе
    # anime_status - Статус выхода аниме (доступно: released, ongoing, None - если ищется не аниме или любой статус)
    # strict - Исключение названий далеких от оригинального
    # only_anime - возвращать только элементы где type in ['anime', 'anime-serial']
    ```
    Возвращает:
    ```json
    [
    {
        "title": "Название",
        "type": "тип мультимедиа (anime, film, ...)",
        "year": "Год выпуска фильма",
        "screenshots": [
            "ссылки на скриншоты"
        ],
        "shikimori_id": "Id шикимори, если нет - None",
        "kinopoisk_id": "Id кинопоиска, если нет - None",
        "imdb_id": "Id imdb, если нет - None",
        "worldart_link": "ссылка на worldart, если нет - None",
        "additional_data": {
            "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом"
        },
        "material_data": { 
            "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False)
            В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое."
        },
        "link": "ссылка на kodikplayer.com (Пример: //kodikplayer.com/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)"
    },
    ]
    ```

2. Поиск аниме по id
    ```python
    parser.search_by_id(id="20", id_type="shikimori", limit=None)
    # id - id аниме на одном из сайтов
    # id_type - с какого сайта id (возможные: shikimori, kinopoisk, imdb, mdl, kodik, worldart_animation, worldart_cinema) (Для кодика нужно указывать в формате: serial-1234 / movie-1234)
    # limit - количество результатов выдачи (int) (результатов будет сильно меньше чем указанное число, так как в выдаче результаты повторяются)
    ```
    Параметр include_material_data включен по умолчанию. Если нужно использовать функцию без данного параметра, воспользуйтесь `base_search_by_id`
    Возвращает:
    ```json
    [
    {
        "title": "Название",
        "type": "тип мультимедиа (anime, film, ...)",
        "year": "Год выпуска фильма",
        "screenshots": [
            "ссылки на скриншоты"
        ],
        "shikimori_id": "Id шикимори, если нет - None",
        "kinopoisk_id": "Id кинопоиска, если нет - None",
        "imdb_id": "Id imdb, если нет - None",
        "worldart_link": "ссылка на worldart, если нет - None",
        "additional_data": {
            "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом"
        },
        "material_data": { 
            "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False)
            В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое."
        },
        "link": "ссылка на kodikplayer.com (Пример: //kodikplayer.com/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)"
    },
    ]
    ```

3. Получить список аниме
    ```python
    data = parser.get_list(limit_per_page=50, pages_to_parse=1, include_material_data=True, anime_status=None, only_anime=False, start_from=None)
    # limit_per_page - количество результатов на одной странице (итоговых результатов будет сильно меньше чем указан параметр)
    # pages_to_parse - количество страниц для обработки (каждая страница - отдельный запрос)
    # include_material_data - включить в результат дополнительные данные
    # anime_status - Статус выхода аниме (доступно: released, ongoing, None - если ищется не аниме или любой статус)
    # only_anime - возвращать только элементы где type in ['anime', 'anime-serial']
    # start_from - начать поиск со страницы под id (id возвращается вторым элементом результата функции)
    ```
    Возвращает:
    ```json
    {
        [
        {
            "title": "Название",
            "type": "тип мультимедиа (anime, film, ...)",
            "year": "Год выпуска фильма",
            "screenshots": [
                "ссылки на скриншоты"
            ],
            "shikimori_id": "Id шикимори, если нет - None",
            "kinopoisk_id": "Id кинопоиска, если нет - None",
            "imdb_id": "Id imdb, если нет - None",
            "worldart_link": "ссылка на worldart, если нет - None",
            "additional_data": {
                "Здесь будут находится все остальные данные выданные кодиком, не связанные с отдельным переводом"
            },
            "material_data": { 
                "Здесь будут все данные о сериале имеющиеся у кодика. (None если указан параметр include_material_data=False)
                В том числе оценки на шикимори, статус выхода, даты анонсов, выхода, все возможные названия, жанры, студии и многое другое."
            },
            "link": "ссылка на kodikplayer.com (Пример: //kodikplayer.com/video/20609/e8fd5bc1190b7eb1ee1a3e1c3aec5f62/720p)"
        },
        ],
        "next_page_id": "id следующей страницы (для последовательного парсинга нескольких страниц) (может быть None, если след. страниц нет)"
    }
    ```

4. Получить информацию об аниме
    1. Через id
        ```python
        parser.get_info(id="z20", id_type="shikimori")
        # id - id аниме на одном из сайтов
        # id_type - с какого сайта id (возможные: shikimori, kinopoisk, imdb, mdl, kodik, worldart_animation, worldart_cinema) (Для кодика нужно указывать в формате: serial-1234 / movie-1234)
        ```
        Возвращает:
        ```json
        {
            "series_count": 220, 
            "translations": [
                {"id": "735", "type": "Озвучка", "name": "2x2 (220 эп.)", "series_range": [1, 220]}, 
                {"id": "609", "type": "Озвучка", "name": "AniDUB (220 эп.)", "series_range": [1, 220]}, 
                {"id": "869", "type": "Субтитры", "name": "Субтитры (220 эп.)", "series_range": [1, 220]}, 
                {"id": "958", "type": "Озвучка", "name": "AniRise (135 эп.)", "series_range": [1, 135]}, 
                {"id": "2550", "type": "Озвучка", "name": "ANI.OMNIA (8 эп.)", "series_range": [1, 8]}
            ]
        }
        ```
        series_range - тип tuple

        - Получить отдельно кол-во серий:
            ```python
            parser.series_count("z20", "shikimori") # число
            ```
        - Получить отдельно переводы:
            ```python
            parser.translations("z20", "shikimori") # список словарей
            ```
        
    2. Через embed ссылку 
        (работает быстрее, так как при запросе через id сначала происходит поиск embed ссылки и потом вызывается эта функция)
        ```python
        parser.get_info_from_embed(
            embed_link="url" # Пример embed ссылки: https://kodikplayer.com/serial/73959/68e2e57cb95f7fb93655637acaca26c2/720p
        )
        ```
        Возвращает те же данные что и через id

    
5. Прямая ссылка на видеофайл
    ```python
    parser.get_link(
        id="z20", 
        id_type="shikimori", 
        seria_num=1, 
        translation_id="609") # Кортеж
    # id - id медиа
    # id_type - тип id (возможные: shikimori, kinopoisk, imdb, mdl, kodik, worldart_animation, worldart_cinema) (Для кодика нужно указывать в формате: serial-1234 / movie-1234)
    # seria_num - номер серии (если фильм или одно видео - 0, также 0 корректен если есть нулевой эпизод)
    # translation_id - id перевода (прим: Anilibria = 610, если неизвестно - 0)
    ```
    Возвращает кортеж: `('//cloud.solodcdn.com/useruploads/67b6e546-...-4d8bfbedc2d7/d10...257:20...09/',720)`
    
    1. Ссылка
    Пример: `//cloud.solodcdn.com/useruploads/67b6e546-...-4d8bfbedc2d7/d10...257:20...09/`
    К данной ссылке в начале нужно добавить `http:` или `https:`, а в конце качество.mp4 (`720.mp4`) (Обычно доступны следующие варианты качества: `360`, `480`, `720`)
    2. Максимально возможное качество
    Прим: `720` (1280x720)

6. Ссылка на m3u8 плейлист
    ```python
    parser.get_m3u8_playlist_link(
        id="z20", 
        id_type="shikimori", 
        seria_num=1, 
        translation_id="609",
        quality=480) # Для "Наруто" нет 720p, хотя сервер и возвращает 720 в списке источников
    # id - id медиа
    # id_type - тип id (возможные: shikimori, kinopoisk, imdb)
    # seria_num - номер серии (если фильм или одно видео - 0, также 0 корректен если есть нулевой эпизод)
    # translation_id - id перевода (прим: Anilibria = 610, если неизвестно - 0)
    # quality - Желаемое качество (360, 480, 720). Если указанное качество будет больше, чем максимально доступное, вернется ссылка с максимально доступным качеством. По умолчанию: 720
    ```
    Возвращает строку вида:
    `https://cloud.solodcdn.com/useruploads/67b6e546-...-4d8bfbedc2d7/d10...257:20...09/480.mp4:hls:manifest.m3u8`

7. Текстовое содержание m3u8 плейлиста
    ```python
    parser.get_m3u8_playlist(
        id="z20", 
        id_type="shikimori", 
        seria_num=1, 
        translation_id="609",
        quality=480) # Для "Наруто" нет 720p, хотя сервер и возвращает 720 в списке источников
    # id - id медиа
    # id_type - тип id (возможные: shikimori, kinopoisk, imdb)
    # seria_num - номер серии (если фильм или одно видео - 0, также 0 корректен если есть нулевой эпизод)
    # translation_id - id перевода (прим: Anilibria = 610, если неизвестно - 0)
    # quality - Желаемое качество (360, 480, 720). Если указанное качество будет больше, чем максимально доступное, вернется ссылка с максимально доступным качеством. По умолчанию: 720
    ```
    Возвращает строку вида:
    ```
    #EXTM3U
    #EXT-X-TARGETDURATION:6
    #EXT-X-ALLOW-CACHE:YES
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXT-X-VERSION:3
    #EXT-X-MEDIA-SEQUENCE:1
    #EXTINF:6.000,
    https://.../480.mp4:hls:seg-1-v1-a1.ts
    #EXTINF:6.000,
    https://.../480.mp4:hls:seg-2-v1-a1.ts
    #EXTINF:6.000,
    https://.../480.mp4:hls:seg-3-v1-a1.ts
    ```

    Ключевое отличие от плейлиста который скачивается просто по ссылке (полученной в п.6) в том, что данная функция добавляет полную ссылку до сегментов. В изначальном файле ссылки содержатся в виде `./480.mp4...` что будет работать если ссылка открыта, например, в браузере, но не будет работать если файл открыт локально. С добавлением полной ссылки можно сохранить файл локально и запускать любым плеером который поддерживает m3u8 плейлисты (например VLC).

8. Получение расписания выхода
    Скорее всего синхронизируется с шикимори с периодом синхронизации 1-3 часа. Не требуется Api ключ.
    Для синхронного варианта:
    ```python
    parser.get_calendar(
        proxy="http://{host}:{port}" # Не обязательный параметр
    )
    # Или (без инициализации экземпляра)
    KodikParser.get_calendar(
        proxy="http://{host}:{port}" # Не обязательный параметр
    )
    ```
    Для асинхронного варианта:
    ```python
    await parser.get_calendar()
    ```
    Возвращает список словарей вида:
    ```json
    [
        {
        "aired_on": "YYYY-mm-dd",
        "anime": {"id": "shikimori id",
                    "image": {"original": "ссылка на оригинальный постер на шикимори (jpeg)",
                            "preview": "ссылка на превью постера на шикимори (webp)",
                            "x24": "ссылка на уменьшенную версию постера на шикимори (webp)",
                            "x48": "ссылка на уменьшенную версию постера на шикимори (webp)",
                            "x96": "ссылка на уменьшенную версию постера на шикимори (webp)"},
                    "name": "Оригинальное название",
                    "russian": "Русское название"},
        "duration": 0,
        "episodes": 8,
        "episodes_aired": 0,
        "kind": "тип аниме (movie, tv, ...)",
        "next_episode": 1,
        "next_episode_at": "YYYY-mm-ddTHH:MM:SSZ",
        "released_on": "YYYY-mm-dd",
        "score": 0.0,
        "status": "статус (anons, ongoing)"
        },
        ...
    ]
    ```

9. Получить embed ссылку
    ```python
    parser.get_embed_link(
        id="z20", # id медиа
        id_type="shikimori", # тип id (возможные: shikimori, kinopoisk, imdb, mdl, kodik, worldart_animation, worldart_cinema) (Для кодика нужно указывать в формате: serial-1234 / movie-1234)
        https=True, # устанавливает будет ли в полученной ссылке https:// или http:// (По умолчанию True)
    )
    ```
    Возвращает ссылку. Пример:
    `https://kodikplayer.com/serial/73959/68e2e57cb95f7fb93655637acaca26c2/720p`

    Данную ссылку можно использовать в iframe, чтобы добавить плеер кодика на сайт.

> [!IMPORTANT]
> В случае, если аниме является фильмом или содержит только одну серию, в параметр `seria_num` указывается значение `0`. В случае если перевод/субтитры неизвестны или нет выбора, в параметр `translation_id` указывается значение `"0"`

9. Прямое обращение к апи кодика
    Рекомендуется использовать модули KodikSearch и KodikList для обращения к апи.
    ```python
    parser.api_request (
        endpoint="list",
        filters={
            "limit": 5
        },
        parameters={
            "with_episodes_data": True
        }
    )
    # endpoint - ссылка куда направляется запрос (доступно: "search", "list", "translations")
    # filters - фильтры запроса
    # parameters - дополнительные параметры (для удобства можно их записывать в один словарь с фильтрами)
    ```
    Возвращает необработанный ответ от сервера кодика.
    Для подробного списка фильтров, параметров и примеров смотрите [инструкцию](KODIK_API.md).


> [!IMPORTANT]
> Если при попытке получения токена будет отловлено исключение по SSL сертификату, то будет попытка получения без валидации сертификата и библиотека requests будет писать в консоль предупреждения. Для асинхронной версии функции нет возможности отключить валидацию на достаточном уровне, поэтому будет выходить исключение о необходимости использовать синхронный вариант.

10. Получить токен
    ```python
    parser.get_token() # строка
    # Или
    KodikParser.get_token()
    ```
    Как это работает вы можете прочитать здесь: [TOKENS.md](kdk_tokns/TOKENS.md)

## AnimeGO инструкция
0. Установите и импортируйте библиотеку
    ```commandline
    pip install anime-parsers-ru
    ```
    ```python
    from anime_parsers_ru import AnimegoParser

    parser = AnimegoParser(
        mirror="домен зеркало", # Не обязательный параметр, по умолчанию None
        proxy="`http://host:port` или 'socks5://user:pass@host:port'", # Не обязательный параметр, по умолчанию None
        use_lxml=False, # Использовать ли lxml парсер (требуется отдельная установка), по умолчанию False
        use_cache=True, # Использовать ли кэш для страниц плееров (рекомендуется использовать), по умолчанию True
        cache_maxsize=300, # Максимальное количество объектов в кэше, по умолчанию 300
        cache_ttl=36000, # Срок жизни кэша в секундах, по умолчанию 36000 (10 часов)
    )
    ```

1. Поиск по сайту
    ```python
    parser.search(
        query="Название"
    )
    ```
    Возвращает список словарей вида:
    ```json
    [
        {
            "link": "Ссылка на страницу аниме",
            "id": "Числовой ID аниме на animego.org",
            "slug": "slug-из-url (прим: naruto-shippuuden)",
            "title": "Название аниме",
            "original_title": "Оригинальное название (если есть, иначе None)",
            "image": "Ссылка на постер (если есть, иначе None)",
            "rating": "Оценка аниме (если есть, иначе None)"
        },
        ...
    ]
    ```

2. Информация об аниме
    ```python
    parser.anime_info(
        url="ссылка", # Ссылка на страницу аниме. Прим: https://animego.me/anime/kulinarnye-skitaniya-v-parallelnom-mire-2239
    )
    ```
    Возвращает словарь вида:
    Некоторые поля могут отсутствовать на странице аниме и в таком случае их значение будет установлено в None
    ```json
    {
        "aired_at": "даты выхода",
        "anime_season": "аниме сезон",
        "author": "автор",
        "description": "Описание",
        "director": "Режиссёр",
        "duration": "длительность",
        "episodes": "эпизодов (если онгоинг, то вышло / всего)",
        "genres": ["список жанров"],
        "image": "ссылка на картинку",
        "main_characters": [{"character": "Персонаж",
                            "voice_actor": "Актер озвучки в оригинале"}],
        "minimal_age": "минимальный возраст зрителя",
        "mpaa_rating": "рейтинг MPAA",
        "next_episode": "дата выхода следующего эпизода (если онгоинг)",
        "original_source": "первоисточник",
        "other_titles": "другие названия",
        "related": [{"image": "Ссылка на картинку",
                    "link": "Ссылка на animego",
                    "relation": "связь (предыстория, продолжение...)",
                    "title": "Название",
                    "type": "Тип",
                    "year": "Год выхода"}],
        "score": "Оценка на анимего",
        "screenshots": ["список ссылок на скриншоты"],
        "status": "Статус (Вышел, Онгоинг...)",
        "studio": "Студия анимации",
        "theme": "Тема",
        "title": "Название",
        "translations": ["Список студий озвучки"],
        "type": "Тип"
    }
    ```

3. Информация об эпизодах
    ```python
    parser.get_episodes_info(
        anime_id="id аниме", # Прим: 2239
    )
    ```
    Возвращает список словарей вида:
    ```json
    [
        {
            "air_date": "дата выхода",
            "is_released": false, // вышло или нет (bool),
            "seria": 1, // номер серии,
            "title": "Название серии (или --- если не указано)"
        }
    ]
    ```

4. Получить расписание аниме
    ```python
    parser.get_schedule()
    ```
    Возвращает словарь вида:
    ```json
    {
        "schedule": {
            "Понедельник": [
                {   "episode": "Номер эпизода",
                    "image": "Ссылка на картинку",
                    "link": "Ссылка на аниме на animego",
                    "time": "Время выхода",
                    "title": "Название"
                },
                ...
            ],
            "Вторник": ...
        },
        "schedule_dates": {
            "Понедельник": "дата (либо: Сегодня, Завтра)",
            "Вторник": // ...
        }
    }
    ```

5. Обновления аниме
    (Новые озвучки на сайте)
    ```python
    parser.get_anime_updates()
    ```
    Возвращает список словарей вида:
    ```json
    [
        {
            "episode": "номер эпизода",
            "image": "Ссылка на картинку",
            "link": "Ссылка на аниме на animego",
            "time": "Время", // Время когда озвучка была добавлена на сайт
            "title": "Название",
            "translation": "Студия озвучки/субтитры"
        },
        ...
    ]
    ```

6. Получить аниме из текущего сезона
    ```python
    parser.get_anime_from_current_season()
    ```
    Возвращает список словарей вида:
    ```json
    {   
        "image": "Ссылка на картинку",
        "link": "Ссылка на аниме на animego",
        "title": "Название",
        "other_title": "Оригинальное название",
        "score": "Оценка"
    },
    ```

7. Получить id из ссылки (staticmethod)
    ```python
    AnimegoParser.get_id_from_link(
        link="ссылка" # Ссылка на страницу аниме. Пример: https://animego.me/anime/kulinarnye-skitaniya-v-parallelnom-mire-2239 
    )
    ```
    Возвращает строку:
    ```python
    '2239' # Для ссылки: https://animego.me/anime/kulinarnye-skitaniya-v-parallelnom-mire-2239
    ```

8. Получить список доступных озвучек и эпизодов
    Общее число эпизодов возвращается только при запросе первого эпизода!
    Для эпизодов больше 1, если не включен кэш, могут быть задержки.
    ```python
    parser.get_voices(
        anime_id="id аниме", # Прим: '2239'
        episode=1, # Номер эпизода
    )
    ```
    Возвращает словарь вида:
    ```json
    {
        "voices": [
            {
                "label": "Название озвучки",
                "translation_id": "ID для передачи в aniboom_get_stream_for_voice()",
                "player": "Тип плеера (kodik, aniboom, cvh, ...)",
                "embed": "Ссылка на embed",
                "cvh_id": "id для получения плейлиста CVH (только для cvh-плеера, иначе None)"
            },
            ...
        ],
        "total_episodes": 12 // None если не удалось определить или не первая серия
    }
    ```

9. Получить поток aniboom для определенной озвучки
    ```python
    parser.aniboom_get_stream_for_voice(
        translation_id="id перевода", # translation_id из функции get_voices
        episode=1, # Номер эпизода
        anime_id='id аниме' # Прим: '2239'
    )
    ```
    Возвращает словарь вида:
    ```json
    {
        "url": "https://.../.mpd", // или "https://.../.m3u8"
        "content": "Содержимое файла"
    }
    ```

10. Получить поток aniboom через embed ссылку
    ```python
    parser.aniboom_get_stream(
        embed_url="embed ссылка" # Embed ссылка из функции get_voices
    )
    ```
    Два варианта ответа:
    * Для MPD (приоритетный формат)
        ```json
        {
            "content": "<содержимое MPD-файла с абсолютными URL сегментов>",
            "kind": "MPD"
        }
        ```
    * Для HLS (если mpd не доступен)
        ```json
        {
            "url": "https://.../.m3u8",
            "kind": "HLS"
        }
        ```
11. Получить cvh плейлист
    Получает список эпизодов для указанного cvh_id
    ```python
    parser.cvh_get_playlist(
        cvh_id="cvh_id", # Из функции get_voices
    )
    ```
    Возвращает словарь вида:
    ```json
    {
        1: { // Номер сезона
            1: [ // Номер серии
                {
                    "cvh_id": "019d...11",
                    "episode": 1, // Номер эпизода
                    "season": 1, // Номер сезона
                    "name": "Название серии (обычно пустое)",
                    "vkId": "ID видео в системе CVH, нужен для получения ссылок на потоки через cvh_get_stream()",
                    "voiceStudio": "Название студии озвучки (напр. AnilibriaTV)",
                    "voiceType": "Тип озвучки/субтитры"
                }
            ]
        }
    }
    ```

12. Получить поток cvh
    Если уже известен vkId (например если до этого был сделан запрос на получение плейлиста), то рекомендуется использовать получение потока по vkId вместо данной функции для быстродействия.
    ```python
    parser.cvh_get_stream(
        cvh_id="cvh_id", # Из функции get_voices или cvh_get_playlist
        season=1, # Номер сезона (если будет найден только один сезон, данное значение будет проигнорировано)
        episode=1, # Номер эпизода
        translation="Название студии озвучки" # Прим: Anilibria.TV (может быть не точным, проводится fuzzy сравнение)
    )
    ```
    Возвращает словарь вида:
    ```json
    {
        "HLS": "https://.../.m3u8", // или None
        "DASH": "https://.../.mpd", // или None,
        "MP4s": ["https://.../360p.mp4", "https://.../720p.mp4"] // или []
    }
    ```

13. Получить поток cvh по vkId
    ```python
    parser.cvh_get_stream_by_id(
        vkId="ID видео в системе CVH" # Из функции cvh_get_playlist
    )
    ```
    Возвращает словарь вида:
    ```json
    {
        "HLS": "https://.../.m3u8", // или None
        "DASH": "https://.../.mpd", // или None,
        "MP4s": ["https://.../360p.mp4", "https://.../720p.mp4"] // или []
    }
    ```

## JutSu инструкция
0. Установите и импортируйте библиотеку
    ```commandline
    pip install anime-parsers-ru
    ```
    ```python
    from anime_parsers_ru import JutsuParser

    parser = JutsuParser()
    # Если вы знаете что есть актуальное зеркало сайта, можете указать его домен в параметре `mirror` при инициализации класса
    ```

Также в параметрах инициализации класса доступен параметр proxy (`http://host:port` или `socks5://user:pass@host:port`) для проксирования запросов на сервер

1. Данные по аниме (по ссылке на страницу)
    ```python
    parser.get_anime_info("Ссылка на страницу")
    # Пример ссылки: https://jut.su/tondemo-skill/
    # Для аниме: Кулинарные скитания в параллельном мире
    ```
    Возвращает словарь:
    ```json
    {
        "title": "Название аниме",
        "origin_title": "Оригинальное название (транслит японского названия на английском)",
        "age_rating": "Возрастное ограничение",
        "description": "Описание",
        "years": ["Год выхода 1 сезона", "Год выхода 2 сезона"],
        "genres": ["Жанр 1", "Жанр 2"],
        "poster": "Ссылка на картинку (плохое качество)",
        "seasons": [
            [ // 1 сезон будет обязательно, даже если у аниме нет других сезонов
                "ссылка на 1 серию 1 сезона (страница с плеером)",
                "ссылка на 2 серию 1 сезона (страница с плеером)"
            ],
            [ // 2 сезон если есть
                "ссылка на 1 серию 2 сезона (страница с плеером)",
                "ссылка на 2 серию 2 сезона (страница с плеером)"
            ],
        ],
        "seasons_names": [ // Если у аниме только 1 сезон, этот список будет пустым
            "Название 1 сезона", 
            "Название 2 сезона"
        ],
        "films": [ // Если фильмов нет - список пустой
            "Ссылка на фильм 1 (страница с плеером)",
            "Ссылка на фильм 2 (страница с плеером)",
        ]
    }
    ```

2. Получить ссылку на mp4 файл
    ```python
    parser.get_mp4_link('ссылка на страницу с плеером')
    # Пример ссылки: https://jut.su/tondemo-skill/episode-1.html
    # Еще пример ссылки: https://jut.su/ookami-to-koshinryou/season-1/episode-1.html
    ```
    Возвращает словарь:
    ```json
    {
        "360": "ссылка на mp4 файл с качеством 360p",
    }
    ```

> [!IMPORTANT]
> Для разных аниме разное количество доступных качеств плеера. (Например для "Наруто" доступно только 360 и 480, для большинства новых аниме доступно качество до 1080)
> Также jutsu не позволяет выбрать озвучку для аниме.

> [!NOTE]
> Для jutsu нет функции поиска, потому что он использует поиск яндекса по сайту и из-за того что он "умный" он может работать абсолютно непредсказуемо.
> В качестве "поиска" вы можете использовать оригинальное название аниме. Так как ссылка формируется по следующей схеме:
> Название аниме: Волчица и пряности
> Оригинальное название: Ookami to Koushinryou
> Ссылка на страницу: https://jut.su/ookami-to-koshinryou/

## Shikimori инструкция
0. Установите и импортируйте библиотеку
    ```commandline
    pip install anime-parsers-ru
    ```
    ```python
    from anime_parsers_ru import ShikimoriParser

    parser = ShikimoriParser()
    # Если вы знаете что есть актуальное зеркало сайта, можете указать его домен в параметре `mirror` при инициализации класса
    ```
    __Для асинхронного кода__:
    ```commandline
    pip install anime-parsers-ru[async]
    ```
    ```python
    from anime_parsers_ru import ShikimoriParserAsync

    parser = ShikimoriParserAsync()
    # Далее перед всеми функциями дополнительно нужно прописывать await
    # Если вы знаете что есть актуальное зеркало сайта, можете указать его домен в параметре `mirror` при инициализации класса
    ```
    Для асинхронного парсера, после окончания работы следует вызвать метод
    ```python
    parser.close_async_session()
    ```
    Иначе выйдет ошибка о незакрытой сессии

> [!NOTE]
> Шикимори ограничивает частоту запросов на сервер.
> Если шикимори возвращает код ответа 429, парсер вернет exception TooManyRequests.
> Для избежания этой ошибки делайте задержку 1-5 секунд между запросами.

Также в параметрах инициализации класса доступен параметр proxy (`http://host:port` или `socks5://user:pass@host:port` для проксирования запросов на сервер)

1. Поиск аниме по названию
    ```python
    parser.search('Название аниме')
    ```
    Возвращает список словарей:
    ```json
    [
        {
            "genres": ["Жанр1", "Жанр2"],
            "link": "Ссылка на страницу аниме",
            "original_title": "Оригинальное название (транслит японского названия на английском)",
            "poster": "Ссылка на постер к аниме (плохое качество) (если есть, иначе None)",
            "shikimori_id": "id шикимори",
            "status": "статус (вышло, онгоинг, анонс) (если есть, иначе None)",
            "studio": "студия анимации (если есть, иначе None)",
            "title": "Название",
            "type": "тип аниме (TV сериал, OVA, ONA, ...) (если есть, иначе None)",
            "year": "год выхода (если есть, иначе None)"
        }
    ]
    ```

2. Информация об аниме
    ```python
    parser.anime_info('shikimori_link')
    # Ссылку на шикимори можно получить с помощью функции
    # parser.link_by_id
    ```
    Возвращает словарь:
    ```json
    {
        "dates": "Даты выхода",
        "description": "Описание",
        "episode_duration": "Средняя продолжительность серии",
        "episodes": "Количество эпизодов если статус 'вышло' или 'вышедших эпизодов / анонсировано эпизодов' или None (если фильм)",
        "genres": ["Жанр1", "Жанр2"],
        "licensed": "Кто лицензировал в РФ или None",
        "licensed_in_ru": "Название аниме как лицензировано в РФ или None",
        "next_episode": "Дата выхода следующего эпизода или None",
        "original_title": "Оригинальное название",
        "picture": "Ссылка на jpeg постер",
        "premiere_in_ru": "Дата премьеры в РФ или None",
        "rating": "возрастной рейтинг",
        "score": "оценка на шикимори",
        "status": "статус выхода",
        "studio": "студия анимации",
        "themes": ["Тема1", "Тема2"],
        "title": "Название на русском",
        "type": "тип аниме (TV Сериал, Фильм, т.п.)"
    }
    ```

3. Дополнительная информация об аниме (связанные аниме (продолжения, предыстории), авторы, главные персонажи, скриншоты, видео, похожие аниме)
    ```python
    parser.additional_anime_info('Ссылка на страницу шикимори')
    # прим: https://shikimori.one/animes/z20-naruto
    ```
    Возвращает словарь:
    ```json
    {
        "related": [
            {
                "date": "Даты выхода/сезон",
                "name": "Название",
                "picture": "Ссылка на картинку",
                "relation": "тип связи (продолжение, предыстория, адаптация и т.п.)",
                "type": "Тип (TV сериал, OVA, ONA, манга, ранобэ и т.д.)",
                "url": "Ссылка на страницу шикимори"
            }
        ],
        "staff": [
            {
                "name": "Имя человека (на русском)",
                "roles": ["Роль1", "Роль2"],
                "link": "ссылка шикимори на человека"
            }
        ],
        "main_characters": [
            {
                "name": "Имя персонажа",
                "picture": "Картинка (jpeg)"
            }
        ],
        "screenshots": ["Ссылка на скриншот 1", "Ссылка на скриншот 2"],
        "videos": [
            {
                "name": "Название видео",
                "link": "Ссылка на видео (обычно ютуб)"
            }
        ],
        "similar": [
            {
                "name": "Название аниме (похожего)",
                "picture": "Картинка (постер)",
                "link": "Ссылка на шикимори"
            }
        ]
    }
    ```

4. Получить список аниме с шикимори по фильтрам
    ```python
    parser.get_anime_list(status=['статус аниме1', 'статус аниме2'], anime_type=['тип аниме1', 'тип аниме2'], rating='возрастной рейтинг', genres=['Жанр1', 'Жанр2'], start_page='начальная страница', page_limit='количество страниц для парсинга', sort_by='принцип сортировки')
    ```
    Доступные фильтры:
    - status - текущие статусы выхода (по умолчанию пусто (не учитывается в фильтрах))
        <details>
        <summary>Список доступных статусов</summary>
        
        - ongoing - онгоинг
        - anons - анонс
        - released - вышло
        - latest - вышло недавно
        </details>
    - anime_type - типы аниме (по умолчанию пусто (не учитывается в фильтрах))
        <details>
        <summary>Список доступных типов</summary>
        
        - tv - TV Сериал
        - movie - Фильм
        - ova - OVA
        - ona - ONA
        - special - спецвыпуск
        - tv_special - TV спецвыпуск
        - music - клип
        - pv - проморолик
        - cm - реклама
        </details>
    - rating - возрастной рейтинг (по умолчанию None (не учитывается в фильтрах))
        <details>
        <summary>Список доступных возрастных рейтингов</summary>
        
        - g - нет возрастного ограничения
        - pg - рекомендуется присутствие родителей
        - pg_13 - детям до 13 просмотр не желателен
        - r - Лицам до 17 лет обязательно присутствие взрослого
        - r_plus - Лицам до 17 лет просмотр запрещен
        
        (Рейтинг rx - доступен только с аккаунтом (т.к. 18+) для поиска по такому рейтингу воспользуйтесь функцией deep_anime_search (описано ниже))
        </details>
    - genres - Список жанров аниме (сюда же темы) (по умолчанию пусто (не учитывается в фильтрах))
        <details>
        <summary>Список доступных жанров</summary>
        
        При передаче аргумента обязательно указывать жанр как указано в списке ниже
        (То есть "{номер}-{название на английском}")
        ```json
        {
            "1-Action": "Экшен",
            "2-Adventure": "Приключения",
            "3-Racing": "Гонки",
            "4-Comedy": "Комедия",
            "5-Avant-Garde": "Авангард",
            "6-Mythology": "Мифология",
            "7-Mystery": "Тайна",
            "8-Drama": "Драма",
            "9-Ecchi": "Этти",
            "10-Fantasy": "Фэнтези",
            "11-Strategy-Game": "Стратегические игры",
            "13-Historical": "Исторический",
            "14-Horror": "Ужасы",
            "15-Kids": "Детское",
            "17-Martial-Arts": "Боевые искусства",
            "18-Mecha": "Меха",
            "19-Music": "Музыка",
            "20-Parody": "Пародия",
            "21-Samurai": "Самураи",
            "22-Romance": "Романтика",
            "23-School": "Школа",
            "24-Sci-Fi": "Фантастика",
            "25-Shoujo": "Сёдзё",
            "27-Shounen": "Сёнен",
            "29-Space": "Космос",
            "30-Sports": "Спорт",
            "31-Super-Power": "Супер сила",
            "32-Vampire": "Вампиры",
            "35-Harem": "Гарем",
            "36-Slice-of-Life": "Повседневность",
            "37-Supernatural": "Сверхъестественное",
            "38-Military": "Военное",
            "39-Detective": "Детектив",
            "40-Psychological": "Психологическое",
            "42-Seinen": "Сэйнэн",
            "43-Josei": "Дзёсей",
            "102-Team-Sports": "Командный спорт",
            "103-Video-Game": "Видеоигры",
            "104-Adult-Cast": "Взрослые персонажи",
            "105-Gore": "Жестокость",
            "106-Reincarnation": "Реинкарнация",
            "107-Love-Polygon": "Любовный многоугольник",
            "108-Visual-Arts": "Изобразительное искусство",
            "111-Time-Travel": "Путешествие во времени",
            "112-Gag-Humor": "Гэг-юмор",
            "114-Award-Winning": "Удостоено наград",
            "117-Suspense": "Триллер",
            "118-Combat-Sports": "Спортивные единоборства",
            "119-CGDCT": "CGDCT",
            "124-Mahou-Shoujo": "Махо-сёдзё",
            "125-Reverse-Harem": "Реверс-гарем",
            "130-Isekai": "Исэкай",
            "131-Delinquents": "Хулиганы",
            "134-Childcare": "Забота о детях",
            "135-Magical-Sex-Shift": "Магическая смена пола",
            "136-Showbiz": "Шоу-бизнес",
            "137-Otaku-Culture": "Культура отаку",
            "138-Organized-Crime": "Организованная преступность",
            "139-Workplace": "Работа",
            "140-Iyashikei": "Иясикэй",
            "141-Survival": "Выживание",
            "142-Performing-Arts": "Исполнительское искусство",
            "143-Anthropomorphic": "Антропоморфизм",
            "144-Crossdressing": "Кроссдрессинг",
            "145-Idols-(Female)": "Идолы (Жен.)",
            "146-High-Stakes-Game": "Игра с высокими ставками",
            "147-Medical": "Медицина",
            "148-Pets": "Питомцы",
            "149-Educational": "Образовательное",
            "150-Idols-(Male)": "Идолы (Муж.)",
            "151-Romantic-Subtext": "Романтический подтекст",
            "543-Gourmet": "Гурман"
        }
        ```
        </details>
    - start_page - начальная страница (начиная с 1) (по умолчанию 1)
    - page_limit - какое количество страниц парсить (по умолчанию 3)
    - sort_by - тип сортировки (по умолчанию rating)
        <details>
        <summary>Список доступных сортировок</summary>
        
        - rating - по рэйтингу
        - popularity - по популярности
        - name - по алфавиту
        - aired_on - по дате выхода
        - ranked_random - случайно
        - id_desc - по id шикимори
        </details>

    [!] Если один из переданных параметров будет неверным (не содержится в списке доступных) - программа автоматически пропустит его.

    Возвращает список словарей:
    ```json
    {
        "original_title": "Оригинальное название (на английском)",
        "poster": "Ссылка на картинку-постер",
        "shikimori_id": "id шикимори",
        "title": "Название на русском",
        "type": "Тип аниме (TV Сериал, ONA, ...)",
        "url": "Ссылка на страницу аниме",
        "year": "год выхода аниме"
    }
    ```

5. Получение онгоингов со страницы /ongoings
    ```python
    parser.get_ongoing_calendar()
    ```
    Возвращает:
    ```json
    "<день недели>, <число> <месяц>": [
        {
            "episode": "номер эпизода (число) если не анонс и не релиз, иначе None",
            "id": "Shikimori id",
            "link": "Ссылка на страницу тайтла",
            "name_en": "Английское название",
            "name_ru": "Русское название",
            "picture": "Ссылка на картинку",
            "release_type": "тип релиза: анонс или релиз (или другое) если episode == None, иначе None",
            "time": "Расчетное время релиза (оригинала)"
        },
    ]
    ```

6. Вспомогательные функции
    - Ссылка на страницу шикимори по id
        ```python
        parser.link_by_id('shikimori_id')
        ```
        Возвращает ссылку
        (id: 53446 результат: https://shikimori.one/animes/53446-tondemo-skill-de-isekai-hourou-meshi)
    - Id по ссылке на шикимори
        ```python
        parser.id_by_link('ссылка на страницу')
        ```
        Возвращает shikimori_id
        (ссылка: https://shikimori.one/animes/53446-tondemo-skill-de-isekai-hourou-meshi id: 53446)

7. Поиск аниме и информации по аниме через псевдо api shikimori
    Данные функции используют предоставленную shikimori тестовую функцию для api. (https://shikimori.one/api/doc/graphql)
    Подробные примеры запросов и ответов вы можете посмотреть в файле [SHIKI_API.md](https://github.com/YaNesyTortiK/AnimeParsers/blob/main/SHIKI_API.md)
    
    - Поиск аниме
        ```python
        parser.deep_search(
            title='Название аниме', 
            search_parameters={'поисковый параметр 1': 'значение поискового параметра 1'},
            return_parameters=['Параметр результата 1', 'параметр результата 2']
        )
        ```
        Возвращает список словарей
    
    - Информация об аниме по id
        ```python
        parser.deep_anime_info(
            shikimori_id='id шикимори',
            return_parameters=['Параметр результата 1', 'параметр результата 2']
        )
        ```
        Возвращает словарь

## Типы исключений
В данной библиотеке добавлены следующие исключения:

- TokenError
    Обозначает неверный или отсутствующий токен в тех функциях где он требуется

- ServiceError
    Обозначает ошибку сервера. Если сервер не вернул один из ожидаемых кодов или, если нет ожидаемых, не вернул код 200

- PostArgumentError
    Обозначает ошибкув данных или их отсутсвие при передаче на сервер (обычно посредством POST запроса)

- NoResults
    Обозначает отсутсвие результатов

- UnexpectedBehaviour
    Обозначает непредвиденное поведение или ошибку, код-статус которой не был ожидаемым. (В некоторых случаях заменяет ServiceError)

- QualityNotFound
    Обозначает что запрашиваемое качество видео не найдено

- AgeRestricted
    Обозначает возрастную блокировку (требуется авторизация для доступа к этим данным)

- TooManyRequests
    Обозначает http статус 429. То есть сервер заблокировал запрос из-за слишком частого обращения

- ContentBlocked
    Обозначает что запрашиваемый контент или плеер заблокирован/недоступен

- ServiceIsOverloaded
    Обозначает http статус 520. То есть сервер перегружен и не может ответить на запрос

- DecryptionFailure
    При попытке дешифровать ссылку от Kodik возникла ошибка
