Added Anidb integration soft rate limits (#2556)

This commit is contained in:
Anderson Shindy Oki 2024-07-02 09:43:36 +09:00 committed by GitHub
parent 023f59dfb9
commit 3cbfe08116
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 59 additions and 4 deletions

View File

@ -4,11 +4,13 @@
import logging import logging
import requests import requests
from collections import namedtuple from collections import namedtuple
from datetime import timedelta from datetime import datetime, timedelta
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from app.config import settings from app.config import settings
from subliminal import Episode, region from subliminal import Episode, region
from subliminal.cache import REFINER_EXPIRATION_TIME
from subliminal_patch.exceptions import TooManyRequests
try: try:
from lxml import etree from lxml import etree
@ -22,12 +24,29 @@ refined_providers = {'animetosho'}
api_url = 'http://api.anidb.net:9001/httpapi' api_url = 'http://api.anidb.net:9001/httpapi'
cache_key_refiner = "anidb_refiner"
# Soft Limit for amount of requests per day
daily_limit_request_count = 200
class AniDBClient(object): class AniDBClient(object):
def __init__(self, api_client_key=None, api_client_ver=1, session=None): def __init__(self, api_client_key=None, api_client_ver=1, session=None):
self.session = session or requests.Session() self.session = session or requests.Session()
self.api_client_key = api_client_key self.api_client_key = api_client_key
self.api_client_ver = api_client_ver self.api_client_ver = api_client_ver
self.cache = region.get(cache_key_refiner, expiration_time=timedelta(days=1).total_seconds())
@property
def is_throttled(self):
return self.cache and self.cache.get('is_throttled')
@property
def daily_api_request_count(self):
if not self.cache:
return 0
return self.cache.get('daily_api_request_count', 0)
AnimeInfo = namedtuple('AnimeInfo', ['anime', 'episode_offset']) AnimeInfo = namedtuple('AnimeInfo', ['anime', 'episode_offset'])
@ -84,8 +103,11 @@ class AniDBClient(object):
return series_id, int(episodes.find(f".//episode[epno='{episode_no}']").attrib.get('id')) return series_id, int(episodes.find(f".//episode[epno='{episode_no}']").attrib.get('id'))
@region.cache_on_arguments(expiration_time=timedelta(days=1).total_seconds()) @region.cache_on_arguments(expiration_time=REFINER_EXPIRATION_TIME)
def get_episodes(self, series_id): def get_episodes(self, series_id):
if self.daily_api_request_count >= 200:
raise TooManyRequests('Daily API request limit exceeded')
r = self.session.get( r = self.session.get(
api_url, api_url,
params={ params={
@ -102,10 +124,12 @@ class AniDBClient(object):
response_code = xml_root.attrib.get('code') response_code = xml_root.attrib.get('code')
if response_code == '500': if response_code == '500':
raise HTTPError('AniDB API Abuse detected. Banned status.') raise TooManyRequests('AniDB API Abuse detected. Banned status.')
elif response_code == '302': elif response_code == '302':
raise HTTPError('AniDB API Client error. Client is disabled or does not exists.') raise HTTPError('AniDB API Client error. Client is disabled or does not exists.')
self.increment_daily_quota()
episode_elements = xml_root.find('episodes') episode_elements = xml_root.find('episodes')
if not episode_elements: if not episode_elements:
@ -113,6 +137,22 @@ class AniDBClient(object):
return etree.tostring(episode_elements, encoding='utf8', method='xml') return etree.tostring(episode_elements, encoding='utf8', method='xml')
def increment_daily_quota(self):
daily_quota = self.daily_api_request_count + 1
if not self.cache:
region.set(cache_key_refiner, {'daily_api_request_count': daily_quota})
return
self.cache['daily_api_request_count'] = daily_quota
region.set(cache_key_refiner, self.cache)
@staticmethod
def mark_as_throttled():
region.set(cache_key_refiner, {'is_throttled': True})
def refine_from_anidb(path, video): def refine_from_anidb(path, video):
if not isinstance(video, Episode) or not video.series_tvdb_id: if not isinstance(video, Episode) or not video.series_tvdb_id:
@ -129,7 +169,22 @@ def refine_anidb_ids(video):
season = video.season if video.season else 0 season = video.season if video.season else 0
anidb_series_id, anidb_episode_id = anidb_client.get_series_episodes_ids(video.series_tvdb_id, season, video.episode) if anidb_client.is_throttled:
logging.warning(f'API daily limit reached. Skipping refinement for {video.series}')
return video
try:
anidb_series_id, anidb_episode_id = anidb_client.get_series_episodes_ids(
video.series_tvdb_id,
season, video.episode,
)
except TooManyRequests:
logging.error(f'API daily limit reached while refining {video.series}')
anidb_client.mark_as_throttled()
return video
if not anidb_episode_id: if not anidb_episode_id:
logging.error(f'Could not find anime series {video.series}') logging.error(f'Could not find anime series {video.series}')