manga-dlp/mangadlp/api/mangadex.py
Ivan Schaller e5fd6790d1
All checks were successful
ci/woodpecker/push/tests Pipeline was successful
change logging
2022-07-06 22:19:40 +02:00

250 lines
9.2 KiB
Python

import logging
import re
import sys
from time import sleep
from typing import Any
import requests
import mangadlp.utils as utils
class Mangadex:
# api information
api_base_url = "https://api.mangadex.org"
img_base_url = "https://uploads.mangadex.org"
# get infos to initiate class
def __init__(self, url_uuid: str, language: str, forcevol: bool):
# static info
self.url_uuid = url_uuid
self.language = language
self.forcevol = forcevol
# api stuff
self.api_content_ratings = "contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic"
self.api_language = f"translatedLanguage[]={self.language}"
self.api_additions = f"{self.api_language}&{self.api_content_ratings}"
# infos from functions
self.manga_uuid = self.get_manga_uuid()
self.manga_data = self.get_manga_data()
self.manga_title = self.get_manga_title()
self.manga_chapter_data = self.get_chapter_data()
self.chapter_list = self.create_chapter_list()
# make initial request
def get_manga_data(self) -> requests.Response:
logging.verbose(f"Getting manga data for: {self.manga_uuid}") # type: ignore
counter = 1
while counter <= 3:
try:
manga_data = requests.get(
f"{self.api_base_url}/manga/{self.manga_uuid}"
)
except:
if counter >= 3:
logging.error("Maybe the MangaDex API is down?")
sys.exit(1)
else:
logging.error("Mangadex API not reachable. Retrying")
sleep(2)
counter += 1
else:
break
# check if manga exists
if manga_data.json()["result"] != "ok":
logging.error("Manga not found")
sys.exit(1)
return manga_data
# get the uuid for the manga
def get_manga_uuid(self) -> str:
# isolate id from url
uuid_regex: Any = re.compile(
"[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}"
)
# check for new mangadex id
if not uuid_regex.search(self.url_uuid):
logging.error("No valid UUID found")
sys.exit(1)
manga_uuid = uuid_regex.search(self.url_uuid)[0]
return manga_uuid
# get the title of the manga (and fix the filename)
def get_manga_title(self) -> str:
logging.verbose(f"Getting manga title for: {self.manga_uuid}") # type: ignore
manga_data = self.manga_data.json()
try:
title = manga_data["data"]["attributes"]["title"][self.language]
except:
# search in alt titles
try:
alt_titles = {}
for title in manga_data["data"]["attributes"]["altTitles"]:
alt_titles.update(title)
title = alt_titles[self.language]
except: # no title on requested language found
logging.error("Chapter in requested language not found.")
sys.exit(1)
return utils.fix_name(title)
# check if chapters are available in requested language
def check_chapter_lang(self) -> int:
logging.verbose( # type: ignore
f"Checking for chapters in specified language for: {self.manga_uuid}"
)
r = requests.get(
f"{self.api_base_url}/manga/{self.manga_uuid}/feed?limit=0&{self.api_additions}"
)
try:
total_chapters = r.json()["total"]
except:
logging.error(
"Error retrieving the chapters list. Did you specify a valid language code?"
)
return 0
else:
if total_chapters == 0:
logging.error("No chapters available to download!")
return 0
return total_chapters
# get chapter data like name, uuid etc
def get_chapter_data(self) -> dict:
logging.verbose(f"Getting chapter data for: {self.manga_uuid}") # type: ignore
api_sorting = "order[chapter]=asc&order[volume]=asc"
# check for chapters in specified lang
total_chapters = self.check_chapter_lang()
if total_chapters == 0:
sys.exit(1)
chapter_data = {}
last_chapter = ["", ""]
offset = 0
while offset < total_chapters: # if more than 500 chapters
r = requests.get(
f"{self.api_base_url}/manga/{self.manga_uuid}/feed?{api_sorting}&limit=500&offset={offset}&{self.api_additions}"
)
for chapter in r.json()["data"]:
# chapter infos from feed
chapter_num = chapter["attributes"]["chapter"]
chapter_vol = chapter["attributes"]["volume"]
chapter_uuid = chapter["id"]
chapter_name = chapter["attributes"]["title"]
chapter_external = chapter["attributes"]["externalUrl"]
# check for chapter title and fix it
if chapter_name is None:
chapter_name = "No Title"
else:
chapter_name = utils.fix_name(chapter_name)
# check if the chapter is external (can't download them)
if chapter_external is not None:
continue
# name chapter "oneshot" if there is no chapter number
if chapter_num is None:
chapter_num = "Oneshot"
# check if its duplicate from the last entry
if last_chapter[0] == chapter_vol and last_chapter[1] == chapter_num:
continue
# export chapter data as a dict
chapter_index = (
chapter_num if not self.forcevol else f"{chapter_vol}:{chapter_num}"
)
chapter_data[chapter_index] = [
chapter_uuid,
chapter_vol,
chapter_num,
chapter_name,
]
# add last chapter to duplicate check
last_chapter = [chapter_vol, chapter_num]
# increase offset for mangas with more than 500 chapters
offset += 500
return chapter_data
# get images for the chapter (mangadex@home)
def get_chapter_images(self, chapter: str, wait_time: float) -> list:
logging.verbose(f"Getting chapter images for: {self.manga_uuid}") # type: ignore
athome_url = f"{self.api_base_url}/at-home/server"
chapter_uuid = self.manga_chapter_data[chapter][0]
# retry up to two times if the api applied ratelimits
api_error = False
counter = 1
while counter <= 3:
try:
r = requests.get(f"{athome_url}/{chapter_uuid}")
api_data = r.json()
if api_data["result"] != "ok":
logging.error(f"No chapter with the id {chapter_uuid} found")
api_error = True
raise IndexError
elif api_data["chapter"]["data"] is None:
logging.error(f"No chapter data found for chapter {chapter_uuid}")
api_error = True
raise IndexError
else:
api_error = False
break
except:
if counter >= 3:
api_error = True
logging.error(f"Retrying in a few seconds")
counter += 1
sleep(wait_time + 2)
# check if result is ok
else:
if api_error:
return []
chapter_hash = api_data["chapter"]["hash"]
chapter_img_data = api_data["chapter"]["data"]
# get list of image urls
image_urls = []
for image in chapter_img_data:
image_urls.append(f"{self.img_base_url}/data/{chapter_hash}/{image}")
sleep(wait_time)
return image_urls
# create list of chapters
def create_chapter_list(self) -> list:
logging.verbose(f"Creating chapter list for: {self.manga_uuid}") # type: ignore
chapter_list = []
for chapter in self.manga_chapter_data.items():
chapter_info = self.get_chapter_infos(chapter[0])
chapter_number = chapter_info["chapter"]
volume_number = chapter_info["volume"]
if self.forcevol:
chapter_list.append(f"{volume_number}:{chapter_number}")
else:
chapter_list.append(chapter_number)
return chapter_list
# create easy to access chapter infos
def get_chapter_infos(self, chapter: str) -> dict:
logging.debug(
f"Getting chapter infos for: {self.manga_chapter_data[chapter][0]}"
)
chapter_uuid = self.manga_chapter_data[chapter][0]
chapter_vol = self.manga_chapter_data[chapter][1]
chapter_num = self.manga_chapter_data[chapter][2]
chapter_name = self.manga_chapter_data[chapter][3]
return {
"uuid": chapter_uuid,
"volume": chapter_vol,
"chapter": chapter_num,
"name": chapter_name,
}