Plex-Meta-Manager/modules/anidb.py

205 lines
8.4 KiB
Python
Raw Normal View History

2022-09-19 20:46:54 +00:00
import json, time
2022-03-15 00:11:16 +00:00
from datetime import datetime
2021-01-20 21:37:59 +00:00
from modules import util
from modules.util import Failed
logger = util.logger
2021-01-20 21:37:59 +00:00
2021-07-13 20:53:49 +00:00
builders = ["anidb_id", "anidb_relation", "anidb_popular", "anidb_tag"]
2021-07-14 14:47:20 +00:00
base_url = "https://anidb.net"
2022-09-19 20:46:54 +00:00
api_url = "http://api.anidb.net:9001/httpapi"
2021-07-14 14:47:20 +00:00
urls = {
"anime": f"{base_url}/anime",
"popular": f"{base_url}/latest/anime/popular/?h=1",
"relation": "/relation/graph",
"tag": f"{base_url}/tag",
"login": f"{base_url}/perl-bin/animedb.pl"
}
2022-03-15 00:11:16 +00:00
class AniDBObj:
2022-09-19 20:46:54 +00:00
def __init__(self, anidb, anidb_id, data):
self._anidb = anidb
2022-03-15 00:11:16 +00:00
self.anidb_id = anidb_id
2022-09-19 20:46:54 +00:00
self._data = data
2022-03-15 00:11:16 +00:00
2022-09-19 20:46:54 +00:00
def _parse(attr, xpath, is_list=False, is_dict=False, is_float=False, is_date=False, fail=False):
2022-03-15 00:11:16 +00:00
try:
2022-09-19 20:46:54 +00:00
if isinstance(data, dict):
if is_list:
2022-09-20 19:56:03 +00:00
return data[attr].split("|") if data[attr] else []
2022-09-19 20:46:54 +00:00
elif is_dict:
return json.loads(data[attr])
elif is_float:
return util.check_num(data[attr], is_int=False)
elif is_date:
return datetime.strptime(data[attr], "%Y-%m-%d")
else:
return data[attr]
parse_results = data.xpath(xpath)
2022-03-15 00:11:16 +00:00
if len(parse_results) > 0:
parse_results = [r.strip() for r in parse_results if len(r) > 0]
if parse_results:
if is_list:
return parse_results
2022-09-19 20:46:54 +00:00
elif is_dict:
return {ta.get("xml:lang"): ta.text_content() for ta in parse_results}
2022-03-15 00:11:16 +00:00
elif is_float:
return float(parse_results[0])
elif is_date:
2022-09-19 20:46:54 +00:00
return datetime.strptime(parse_results[0], "%Y-%m-%d")
2022-03-15 00:11:16 +00:00
else:
return parse_results[0]
except (ValueError, TypeError):
pass
if fail:
raise Failed(f"AniDB Error: No Anime Found for AniDB ID: {self.anidb_id}")
elif is_list:
return []
2022-09-19 20:46:54 +00:00
elif is_dict:
return {}
2022-03-15 00:11:16 +00:00
else:
return None
2022-09-19 20:46:54 +00:00
self.main_title = _parse("main_title", "//anime/titles/title[@type='main']/text()", fail=True)
self.titles = _parse("titles", "//anime/titles/title[@type='official']", is_dict=True)
self.official_title = self.titles[self._anidb.language] if self._anidb.language in self.titles else self.main_title
self.rating = _parse("rating", "//anime/ratings/permanent/text()", is_float=True)
self.average = _parse("average", "//anime/ratings/temporary/text()", is_float=True)
self.score = _parse("score", "//anime/ratings/review/text()", is_float=True)
self.released = _parse("released", "//anime/startdate/text()", is_date=True)
self.tags = _parse("tags", "//anime/tags/tag[@infobox='true']/name/text()", is_list=True)
2022-03-15 00:11:16 +00:00
2021-06-14 15:24:11 +00:00
class AniDB:
2022-09-19 20:46:54 +00:00
def __init__(self, config, data):
2021-03-05 20:33:24 +00:00
self.config = config
2022-09-19 20:46:54 +00:00
self.language = data["language"]
self.expiration = 60
self.client = None
self.version = None
self.username = None
self.password = None
2022-09-19 20:46:54 +00:00
self._delay = None
def authorize(self, client, version, expiration):
self.client = client
self.version = version
self.expiration = expiration
2022-09-21 15:34:57 +00:00
logger.secret(self.client)
if self.config.Cache:
value1, value2, success = self.config.Cache.query_testing("anidb_login")
if str(value1) == str(client) and str(value2) == str(version) and success:
return
2022-09-19 20:46:54 +00:00
try:
self.get_anime(69, ignore_cache=True)
2022-09-21 15:34:57 +00:00
if self.config.Cache:
self.config.Cache.update_testing("anidb_login", self.client, self.version, "True")
2022-09-19 20:46:54 +00:00
except Failed:
self.client = None
self.version = None
2022-09-21 15:34:57 +00:00
if self.config.Cache:
self.config.Cache.update_testing("anidb_login", self.client, self.version, "False")
2022-09-19 20:46:54 +00:00
raise
@property
def is_authorized(self):
return self.client is not None
def login(self, username, password):
2022-09-20 21:29:57 +00:00
logger.secret(username)
logger.secret(password)
data = {"show": "main", "xuser": username, "xpass": password, "xdoautologin": "on"}
if not self._request(urls["login"], data=data).xpath("//li[@class='sub-menu my']/@title"):
2021-08-04 14:20:52 +00:00
raise Failed("AniDB Error: Login failed")
2022-09-20 21:29:57 +00:00
self.username = username
self.password = password
2021-07-11 08:12:52 +00:00
2022-09-19 20:46:54 +00:00
def _request(self, url, params=None, data=None):
2022-09-07 19:32:52 +00:00
logger.trace(f"URL: {url}")
2021-11-03 14:38:43 +00:00
if data:
2022-09-20 00:16:59 +00:00
return self.config.post_html(url, data=data, headers=util.header(self.language))
2021-07-14 14:47:20 +00:00
else:
2022-09-19 20:46:54 +00:00
return self.config.get_html(url, params=params, headers=util.header(self.language))
2021-07-11 08:12:52 +00:00
2022-03-15 00:11:16 +00:00
def _popular(self):
response = self._request(urls["popular"])
2022-07-15 13:56:54 +00:00
return util.get_int_list(response.xpath("//td[@class='thumb anime']/a/@href"), "AniDB ID")
2021-01-20 21:37:59 +00:00
2022-03-15 00:11:16 +00:00
def _relations(self, anidb_id):
response = self._request(f"{urls['anime']}/{anidb_id}{urls['relation']}")
2021-05-07 19:53:54 +00:00
return util.get_int_list(response.xpath("//area/@href"), "AniDB ID")
2022-03-15 00:11:16 +00:00
def _validate(self, anidb_id):
response = self._request(f"{urls['anime']}/{anidb_id}")
2021-02-24 06:44:06 +00:00
ids = response.xpath(f"//*[text()='a{anidb_id}']/text()")
2021-01-20 21:37:59 +00:00
if len(ids) > 0:
return util.regex_first_int(ids[0], "AniDB ID")
2021-02-24 06:44:06 +00:00
raise Failed(f"AniDB Error: AniDB ID: {anidb_id} not found")
2021-01-20 21:37:59 +00:00
2022-03-15 00:11:16 +00:00
def validate_anidb_ids(self, anidb_ids):
2021-07-21 17:40:05 +00:00
anidb_list = util.get_int_list(anidb_ids, "AniDB ID")
2021-01-20 21:37:59 +00:00
anidb_values = []
for anidb_id in anidb_list:
try:
2022-03-15 00:11:16 +00:00
anidb_values.append(self._validate(anidb_id))
2021-01-20 21:37:59 +00:00
except Failed as e:
logger.error(e)
if len(anidb_values) > 0:
return anidb_values
2021-02-24 06:44:06 +00:00
raise Failed(f"AniDB Error: No valid AniDB IDs in {anidb_list}")
2021-01-20 21:37:59 +00:00
2022-03-15 00:11:16 +00:00
def _tag(self, tag, limit):
2021-07-11 08:12:52 +00:00
anidb_ids = []
2021-07-14 14:47:20 +00:00
current_url = f"{urls['tag']}/{tag}"
2021-07-13 20:53:49 +00:00
while True:
2022-03-15 00:11:16 +00:00
response = self._request(current_url)
2021-07-14 14:47:20 +00:00
anidb_ids.extend(util.get_int_list(response.xpath("//td[@class='name main anime']/a/@href"), "AniDB ID"))
2021-07-11 08:12:52 +00:00
next_page_list = response.xpath("//li[@class='next']/a/@href")
2021-07-13 20:53:49 +00:00
if len(anidb_ids) >= limit or len(next_page_list) == 0:
break
time.sleep(2)
2021-07-14 14:47:20 +00:00
current_url = f"{base_url}{next_page_list[0]}"
2021-07-13 20:53:49 +00:00
return anidb_ids[:limit]
2021-07-11 08:12:52 +00:00
2022-09-19 20:46:54 +00:00
def get_anime(self, anidb_id, ignore_cache=False):
expired = None
anidb_dict = None
if self.config.Cache and not ignore_cache:
anidb_dict, expired = self.config.Cache.query_anidb(anidb_id, self.expiration)
if expired or not anidb_dict:
time_check = time.time()
if self._delay is not None:
while time_check - self._delay < 2:
time_check = time.time()
anidb_dict = self._request(api_url, params={
"client": self.client,
"clientver": self.version,
"protover": 1,
"request": "anime",
"aid": anidb_id
})
self._delay = time.time()
obj = AniDBObj(self, anidb_id, anidb_dict)
if self.config.Cache and not ignore_cache:
2022-09-20 12:04:49 +00:00
self.config.Cache.update_anidb(expired, anidb_id, obj, self.expiration)
2022-09-19 20:46:54 +00:00
return obj
2022-03-15 00:11:16 +00:00
def get_anidb_ids(self, method, data):
2021-04-16 05:45:43 +00:00
anidb_ids = []
2021-01-20 21:37:59 +00:00
if method == "anidb_popular":
2021-08-01 04:35:42 +00:00
logger.info(f"Processing AniDB Popular: {data} Anime")
2022-03-15 00:11:16 +00:00
anidb_ids.extend(self._popular()[:data])
2021-07-11 08:12:52 +00:00
elif method == "anidb_tag":
2021-08-01 04:35:42 +00:00
logger.info(f"Processing AniDB Tag: {data['limit'] if data['limit'] > 0 else 'All'} Anime from the Tag ID: {data['tag']}")
2022-03-15 00:11:16 +00:00
anidb_ids = self._tag(data["tag"], data["limit"])
2021-08-01 04:35:42 +00:00
elif method == "anidb_id":
logger.info(f"Processing AniDB ID: {data}")
anidb_ids.append(data)
elif method == "anidb_relation":
logger.info(f"Processing AniDB Relation: {data}")
2022-03-15 00:11:16 +00:00
anidb_ids.extend(self._relations(data))
2021-01-20 21:37:59 +00:00
else:
2021-08-01 04:35:42 +00:00
raise Failed(f"AniDB Error: Method {method} not supported")
2021-05-24 03:38:46 +00:00
logger.debug("")
2021-07-03 01:47:09 +00:00
logger.debug(f"{len(anidb_ids)} AniDB IDs Found: {anidb_ids}")
2021-08-07 06:01:21 +00:00
return anidb_ids