import logging , requests , time
from modules import util
from modules . util import Failed
from retrying import retry
logger = logging . getLogger ( " Plex Meta Manager " )
builders = [
" anilist_genre " ,
" anilist_id " ,
" anilist_popular " ,
" anilist_relations " ,
" anilist_season " ,
" anilist_studio " ,
" anilist_tag " ,
" anilist_top_rated "
]
pretty_names = {
" score " : " Average Score " ,
" popular " : " Popularity "
}
class AniListAPI :
def __init__ ( self , config ) :
self . config = config
self . url = " https://graphql.anilist.co "
self . tags = { }
self . genres = { }
for tag in self . send_request ( " query { MediaTagCollection {name} } " , { } ) [ " data " ] [ " MediaTagCollection " ] :
self . tags [ tag [ " name " ] . lower ( ) ] = tag [ " name " ]
for genre in self . send_request ( " query {GenreCollection} " , { } ) [ " data " ] [ " GenreCollection " ] :
self . genres [ genre . lower ( ) ] = genre
@retry ( stop_max_attempt_number = 6 , wait_fixed = 10000 )
def post ( self , query , variables ) :
return requests . post ( self . url , json = { " query " : query , " variables " : variables } )
@retry ( stop_max_attempt_number = 2 , retry_on_exception = util . retry_if_not_failed )
def send_request ( self , query , variables ) :
response = self . post ( query , variables )
json_obj = response . json ( )
if " errors " in json_obj :
if json_obj [ ' errors ' ] [ 0 ] [ ' message ' ] == " Too Many Requests. " :
if " Retry-After " in response . headers :
time . sleep ( int ( response . headers [ " Retry-After " ] ) )
raise ValueError
else :
raise Failed ( f " AniList Error: { json_obj [ ' errors ' ] [ 0 ] [ ' message ' ] } " )
else :
time . sleep ( 0.4 )
return json_obj
def anilist_id ( self , anilist_id ) :
query = " query ($id: Int) { Media(id: $id) { idMal title { romaji english}}} "
media = self . send_request ( query , { " id " : anilist_id } ) [ " data " ] [ " Media " ]
if media [ " idMal " ] :
return media [ " idMal " ] , media [ " title " ] [ " english " if media [ " title " ] [ " english " ] else " romaji " ]
raise Failed ( f " AniList Error: No MyAnimeList ID found for { anilist_id } " )
def get_pagenation ( self , query , limit = 0 , variables = None ) :
mal_ids = [ ]
count = 0
page_num = 0
if variables is None :
variables = { }
next_page = True
while next_page :
page_num + = 1
variables [ " page " ] = page_num
json_obj = self . send_request ( query , variables )
next_page = json_obj [ " data " ] [ " Page " ] [ " pageInfo " ] [ " hasNextPage " ]
for media in json_obj [ " data " ] [ " Page " ] [ " media " ] :
if media [ " idMal " ] :
mal_ids . append ( media [ " idMal " ] )
count + = 1
if 0 < limit == count :
break
if 0 < limit == count :
break
return mal_ids
def top_rated ( self , limit ) :
query = """
query ( $ page : Int ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
media ( averageScore_greater : 3 , sort : SCORE_DESC , type : ANIME ) { idMal }
}
}
"""
return self . get_pagenation ( query , limit = limit )
def popular ( self , limit ) :
query = """
query ( $ page : Int ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
media ( popularity_greater : 1000 , sort : POPULARITY_DESC , type : ANIME ) { idMal }
}
}
"""
return self . get_pagenation ( query , limit = limit )
def season ( self , season , year , sort , limit ) :
query = """
query ( $ page : Int , $ season : MediaSeason , $ year : Int , $ sort : [ MediaSort ] ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
media ( season : $ season , seasonYear : $ year , type : ANIME , sort : $ sort ) { idMal }
}
}
"""
variables = { " season " : season . upper ( ) , " year " : year , " sort " : " SCORE_DESC " if sort == " score " else " POPULARITY_DESC " }
return self . get_pagenation ( query , limit = limit , variables = variables )
def genre ( self , genre , sort , limit ) :
query = """
query ( $ page : Int , $ genre : String , $ sort : [ MediaSort ] ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
media ( genre : $ genre , sort : $ sort ) { idMal }
}
}
"""
variables = { " genre " : genre , " sort " : " SCORE_DESC " if sort == " score " else " POPULARITY_DESC " }
return self . get_pagenation ( query , limit = limit , variables = variables )
def tag ( self , tag , sort , limit ) :
query = """
query ( $ page : Int , $ tag : String , $ sort : [ MediaSort ] ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
media ( tag : $ tag , sort : $ sort ) { idMal }
}
}
"""
variables = { " tag " : tag , " sort " : " SCORE_DESC " if sort == " score " else " POPULARITY_DESC " }
return self . get_pagenation ( query , limit = limit , variables = variables )
def studio ( self , studio_id ) :
query = """
query ( $ page : Int , $ id : Int ) {
Studio ( id : $ id ) {
name
media ( page : $ page ) {
nodes { idMal type }
pageInfo { hasNextPage }
}
}
}
"""
mal_ids = [ ]
page_num = 0
next_page = True
name = None
while next_page :
page_num + = 1
json_obj = self . send_request ( query , { " id " : studio_id , " page " : page_num } )
if not name :
name = json_obj [ " data " ] [ " Studio " ] [ " name " ]
next_page = json_obj [ " data " ] [ " Studio " ] [ " media " ] [ " pageInfo " ] [ " hasNextPage " ]
for media in json_obj [ " data " ] [ " Studio " ] [ " media " ] [ " nodes " ] :
if media [ " idMal " ] and media [ " type " ] == " ANIME " :
mal_ids . append ( media [ " idMal " ] )
return mal_ids , name
def relations ( self , anilist_id , ignore_ids = None ) :
query = """
query ( $ id : Int ) {
Media ( id : $ id ) {
idMal
relations {
edges { node { id idMal type } relationType }
nodes { id idMal type }
}
}
}
"""
anilist_ids = [ ]
mal_ids = [ ]
name = " "
if not ignore_ids :
ignore_ids = [ anilist_id ]
mal_id , name = self . anilist_id ( anilist_id )
mal_ids . append ( mal_id )
json_obj = self . send_request ( query , { " id " : anilist_id } )
edges = [ media [ " node " ] [ " id " ] for media in json_obj [ " data " ] [ " Media " ] [ " relations " ] [ " edges " ]
if media [ " relationType " ] not in [ " CHARACTER " , " OTHER " ] and media [ " node " ] [ " type " ] == " ANIME " ]
for media in json_obj [ " data " ] [ " Media " ] [ " relations " ] [ " nodes " ] :
if media [ " idMal " ] and media [ " id " ] not in ignore_ids and media [ " id " ] in edges and media [ " type " ] == " ANIME " :
anilist_ids . append ( media [ " id " ] )
ignore_ids . append ( media [ " id " ] )
mal_ids . append ( media [ " idMal " ] )
for next_id in anilist_ids :
new_mal_ids , ignore_ids , _ = self . relations ( next_id , ignore_ids = ignore_ids )
mal_ids . extend ( new_mal_ids )
return mal_ids , ignore_ids , name
def validate_genre ( self , genre ) :
if genre . lower ( ) in self . genres :
return self . genres [ genre . lower ( ) ]
raise Failed ( f " AniList Error: Genre: { genre } does not exist " )
def validate_tag ( self , tag ) :
if tag . lower ( ) in self . tags :
return self . tags [ tag . lower ( ) ]
raise Failed ( f " AniList Error: Tag: { tag } does not exist " )
def validate_anilist_ids ( self , anilist_ids , studio = False ) :
anilist_values = [ ]
for anilist_id in anilist_ids :
if studio : query = " query ($id: Int) { Studio(id: $id) {name} } "
else : query = " query ($id: Int) { Media(id: $id) {idMal} } "
try :
self . send_request ( query , { " id " : anilist_id } )
anilist_values . append ( anilist_id )
except Failed as e : logger . error ( e )
if len ( anilist_values ) > 0 :
return anilist_values
raise Failed ( f " AniList Error: No valid AniList IDs in { anilist_ids } " )
def get_items ( self , method , data , status_message = True ) :
if status_message :
logger . debug ( f " Data: { data } " )
pretty = util . pretty_names [ method ] if method in util . pretty_names else method
if method == " anilist_id " :
mal_id , name = self . anilist_id ( data )
mal_ids = [ mal_id ]
if status_message :
logger . info ( f " Processing { pretty } : ( { data } ) { name } " )
elif method in [ " anilist_popular " , " anilist_top_rated " ] :
mal_ids = self . popular ( data ) if method == " anilist_popular " else self . top_rated ( data )
if status_message :
logger . info ( f " Processing { pretty } : { data } Anime " )
elif method == " anilist_season " :
mal_ids = self . season ( data [ " season " ] , data [ " year " ] , data [ " sort_by " ] , data [ " limit " ] )
if status_message :
logger . info ( f " Processing { pretty } : { data [ ' limit ' ] if data [ ' limit ' ] > 0 else ' All ' } Anime from { util . pretty_seasons [ data [ ' season ' ] ] } { data [ ' year ' ] } sorted by { pretty_names [ data [ ' sort_by ' ] ] } " )
elif method == " anilist_genre " :
mal_ids = self . genre ( data [ " genre " ] , data [ " sort_by " ] , data [ " limit " ] )
if status_message :
logger . info ( f " Processing { pretty } : { data [ ' limit ' ] if data [ ' limit ' ] > 0 else ' All ' } Anime from the Genre: { data [ ' genre ' ] } sorted by { pretty_names [ data [ ' sort_by ' ] ] } " )
elif method == " anilist_tag " :
mal_ids = self . tag ( data [ " tag " ] , data [ " sort_by " ] , data [ " limit " ] )
if status_message :
logger . info ( f " Processing { pretty } : { data [ ' limit ' ] if data [ ' limit ' ] > 0 else ' All ' } Anime from the Tag: { data [ ' tag ' ] } sorted by { pretty_names [ data [ ' sort_by ' ] ] } " )
elif method in [ " anilist_studio " , " anilist_relations " ] :
if method == " anilist_studio " : mal_ids , name = self . studio ( data )
else : mal_ids , _ , name = self . relations ( data )
if status_message :
logger . info ( f " Processing { pretty } : ( { data } ) { name } ( { len ( mal_ids ) } Anime) " )
else :
raise Failed ( f " AniList Error: Method { method } not supported " )
show_ids = [ ]
movie_ids = [ ]
for mal_id in mal_ids :
try :
ids = self . config . MyAnimeListIDList . find_mal_ids ( mal_id )
if " thetvdb_id " in ids and int ( ids [ " thetvdb_id " ] ) > 0 : show_ids . append ( int ( ids [ " thetvdb_id " ] ) )
elif " themoviedb_id " in ids and int ( ids [ " themoviedb_id " ] ) > 0 : movie_ids . append ( int ( ids [ " themoviedb_id " ] ) )
else : raise Failed ( f " MyAnimeList Error: MyAnimeList ID: { mal_id } has no other IDs associated with it " )
except Failed as e :
if status_message :
logger . error ( e )
if status_message :
logger . debug ( f " MyAnimeList IDs Found: { mal_ids } " )
logger . debug ( f " Shows Found: { show_ids } " )
logger . debug ( f " Movies Found: { movie_ids } " )
return movie_ids , show_ids