2021-07-14 14:47:20 +00:00
import logging , time
2021-03-10 16:51:27 +00:00
from modules import util
from modules . util import Failed
from retrying import retry
logger = logging . getLogger ( " Plex Meta Manager " )
2021-03-30 05:50:53 +00:00
builders = [
2021-07-23 19:44:21 +00:00
" anilist_genre " , " anilist_id " , " anilist_popular " , " anilist_relations " ,
" anilist_season " , " anilist_studio " , " anilist_tag " , " anilist_top_rated "
2021-03-30 05:50:53 +00:00
]
2021-07-23 19:44:21 +00:00
pretty_names = { " score " : " Average Score " , " popular " : " Popularity " }
2021-07-14 14:47:20 +00:00
base_url = " https://graphql.anilist.co "
2021-05-07 19:53:54 +00:00
tag_query = " query { MediaTagCollection {name} } "
genre_query = " query {GenreCollection} "
2021-03-30 05:50:53 +00:00
2021-06-14 15:24:11 +00:00
class AniList :
2021-03-10 16:51:27 +00:00
def __init__ ( self , config ) :
self . config = config
2021-03-20 04:22:51 +00:00
self . tags = { }
self . genres = { }
2021-05-07 19:53:54 +00:00
self . tags = { t [ " name " ] . lower ( ) : t [ " name " ] for t in self . _request ( tag_query , { } ) [ " data " ] [ " MediaTagCollection " ] }
self . genres = { g . lower ( ) : g for g in self . _request ( genre_query , { } ) [ " data " ] [ " GenreCollection " ] }
2021-03-10 16:51:27 +00:00
@retry ( stop_max_attempt_number = 2 , retry_on_exception = util . retry_if_not_failed )
2021-05-07 19:53:54 +00:00
def _request ( self , query , variables ) :
2021-07-14 14:47:20 +00:00
response = self . config . post ( base_url , json = { " query " : query , " variables " : variables } )
2021-03-10 16:51:27 +00:00
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
2021-05-07 19:53:54 +00:00
def _validate ( self , anilist_id ) :
2021-04-15 20:45:35 +00:00
query = " query ($id: Int) { Media(id: $id) { id title { romaji english}}} "
2021-05-07 19:53:54 +00:00
media = self . _request ( query , { " id " : anilist_id } ) [ " data " ] [ " Media " ]
2021-04-15 20:45:35 +00:00
if media [ " id " ] :
return media [ " id " ] , media [ " title " ] [ " english " if media [ " title " ] [ " english " ] else " romaji " ]
raise Failed ( f " AniList Error: No AniList ID found for { anilist_id } " )
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _pagenation ( self , query , limit = 0 , variables = None ) :
2021-04-15 20:45:35 +00:00
anilist_ids = [ ]
2021-03-10 16:51:27 +00:00
count = 0
page_num = 0
if variables is None :
2021-03-17 13:49:34 +00:00
variables = { }
2021-03-10 16:51:27 +00:00
next_page = True
while next_page :
page_num + = 1
2021-03-17 13:49:34 +00:00
variables [ " page " ] = page_num
2021-05-07 19:53:54 +00:00
json_obj = self . _request ( query , variables )
2021-03-10 16:51:27 +00:00
next_page = json_obj [ " data " ] [ " Page " ] [ " pageInfo " ] [ " hasNextPage " ]
for media in json_obj [ " data " ] [ " Page " ] [ " media " ] :
2021-04-15 20:45:35 +00:00
if media [ " id " ] :
anilist_ids . append ( media [ " id " ] )
2021-03-10 16:51:27 +00:00
count + = 1
if 0 < limit == count :
break
if 0 < limit == count :
break
2021-04-15 20:45:35 +00:00
return anilist_ids
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _top_rated ( self , limit ) :
2021-03-10 16:51:27 +00:00
query = """
query ( $ page : Int ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
2021-04-15 20:45:35 +00:00
media ( averageScore_greater : 3 , sort : SCORE_DESC , type : ANIME ) { id }
2021-03-10 16:51:27 +00:00
}
}
"""
2021-05-07 19:53:54 +00:00
return self . _pagenation ( query , limit = limit )
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _popular ( self , limit ) :
2021-03-10 16:51:27 +00:00
query = """
query ( $ page : Int ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
2021-04-15 20:45:35 +00:00
media ( popularity_greater : 1000 , sort : POPULARITY_DESC , type : ANIME ) { id }
2021-03-10 16:51:27 +00:00
}
}
"""
2021-05-07 19:53:54 +00:00
return self . _pagenation ( query , limit = limit )
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _season ( self , season , year , sort , limit ) :
2021-03-10 16:51:27 +00:00
query = """
2021-03-17 13:49:34 +00:00
query ( $ page : Int , $ season : MediaSeason , $ year : Int , $ sort : [ MediaSort ] ) {
2021-03-10 16:51:27 +00:00
Page ( page : $ page ) {
pageInfo { hasNextPage }
2021-04-15 20:45:35 +00:00
media ( season : $ season , seasonYear : $ year , type : ANIME , sort : $ sort ) { id }
2021-03-10 16:51:27 +00:00
}
}
"""
variables = { " season " : season . upper ( ) , " year " : year , " sort " : " SCORE_DESC " if sort == " score " else " POPULARITY_DESC " }
2021-05-07 19:53:54 +00:00
return self . _pagenation ( query , limit = limit , variables = variables )
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _genre ( self , genre , sort , limit ) :
2021-03-20 04:22:51 +00:00
query = """
query ( $ page : Int , $ genre : String , $ sort : [ MediaSort ] ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
2021-04-15 20:45:35 +00:00
media ( genre : $ genre , sort : $ sort ) { id }
2021-03-20 04:22:51 +00:00
}
}
"""
variables = { " genre " : genre , " sort " : " SCORE_DESC " if sort == " score " else " POPULARITY_DESC " }
2021-05-07 19:53:54 +00:00
return self . _pagenation ( query , limit = limit , variables = variables )
2021-03-20 04:22:51 +00:00
2021-05-07 19:53:54 +00:00
def _tag ( self , tag , sort , limit ) :
2021-03-20 04:22:51 +00:00
query = """
query ( $ page : Int , $ tag : String , $ sort : [ MediaSort ] ) {
Page ( page : $ page ) {
pageInfo { hasNextPage }
2021-04-15 20:45:35 +00:00
media ( tag : $ tag , sort : $ sort ) { id }
2021-03-20 04:22:51 +00:00
}
}
"""
variables = { " tag " : tag , " sort " : " SCORE_DESC " if sort == " score " else " POPULARITY_DESC " }
2021-05-07 19:53:54 +00:00
return self . _pagenation ( query , limit = limit , variables = variables )
2021-03-20 04:22:51 +00:00
2021-05-07 19:53:54 +00:00
def _studio ( self , studio_id ) :
2021-03-10 16:51:27 +00:00
query = """
query ( $ page : Int , $ id : Int ) {
Studio ( id : $ id ) {
name
media ( page : $ page ) {
2021-04-15 20:45:35 +00:00
nodes { id type }
2021-03-10 16:51:27 +00:00
pageInfo { hasNextPage }
}
}
}
"""
2021-04-15 20:45:35 +00:00
anilist_ids = [ ]
2021-03-10 16:51:27 +00:00
page_num = 0
next_page = True
name = None
while next_page :
page_num + = 1
2021-05-07 19:53:54 +00:00
json_obj = self . _request ( query , { " id " : studio_id , " page " : page_num } )
2021-03-10 16:51:27 +00:00
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 " ] :
2021-04-15 20:45:35 +00:00
if media [ " id " ] and media [ " type " ] == " ANIME " :
anilist_ids . append ( media [ " id " ] )
return anilist_ids , name
2021-03-10 16:51:27 +00:00
2021-05-07 19:53:54 +00:00
def _relations ( self , anilist_id , ignore_ids = None ) :
2021-03-10 16:51:27 +00:00
query = """
query ( $ id : Int ) {
Media ( id : $ id ) {
2021-04-15 20:45:35 +00:00
id
2021-03-10 16:51:27 +00:00
relations {
2021-04-15 20:45:35 +00:00
edges { node { id type } relationType }
nodes { id type }
2021-03-10 16:51:27 +00:00
}
}
}
"""
2021-04-15 20:45:35 +00:00
new_anilist_ids = [ ]
2021-03-10 16:51:27 +00:00
anilist_ids = [ ]
name = " "
if not ignore_ids :
ignore_ids = [ anilist_id ]
2021-05-07 19:53:54 +00:00
anilist_id , name = self . _validate ( anilist_id )
2021-04-15 20:45:35 +00:00
anilist_ids . append ( anilist_id )
2021-05-07 19:53:54 +00:00
json_obj = self . _request ( query , { " id " : anilist_id } )
2021-03-10 16:51:27 +00:00
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 " ] :
2021-04-15 20:45:35 +00:00
if media [ " id " ] and media [ " id " ] not in ignore_ids and media [ " id " ] in edges and media [ " type " ] == " ANIME " :
new_anilist_ids . append ( media [ " id " ] )
2021-03-10 16:51:27 +00:00
ignore_ids . append ( media [ " id " ] )
2021-04-15 20:45:35 +00:00
anilist_ids . append ( media [ " id " ] )
2021-03-10 16:51:27 +00:00
2021-04-15 20:45:35 +00:00
for next_id in new_anilist_ids :
2021-05-07 19:53:54 +00:00
new_relation_ids , ignore_ids , _ = self . _relations ( next_id , ignore_ids = ignore_ids )
2021-04-15 20:45:35 +00:00
anilist_ids . extend ( new_relation_ids )
2021-03-10 16:51:27 +00:00
2021-04-15 20:45:35 +00:00
return anilist_ids , ignore_ids , name
2021-03-10 16:51:27 +00:00
2021-03-20 04:22:51 +00:00
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 " )
2021-03-10 16:51:27 +00:00
def validate_anilist_ids ( self , anilist_ids , studio = False ) :
2021-07-21 17:40:05 +00:00
anilist_id_list = util . get_int_list ( anilist_ids , " AniList ID " )
2021-03-10 16:51:27 +00:00
anilist_values = [ ]
2021-07-26 19:03:17 +00:00
for anilist_id in anilist_id_list :
2021-03-10 16:51:27 +00:00
if studio : query = " query ($id: Int) { Studio(id: $id) {name} } "
2021-04-15 20:45:35 +00:00
else : query = " query ($id: Int) { Media(id: $id) {id} } "
2021-03-10 16:51:27 +00:00
try :
2021-05-07 19:53:54 +00:00
self . _request ( query , { " id " : anilist_id } )
2021-03-10 16:51:27 +00:00
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 } " )
2021-05-09 05:37:45 +00:00
def get_items ( self , method , data ) :
2021-03-10 16:51:27 +00:00
pretty = util . pretty_names [ method ] if method in util . pretty_names else method
if method == " anilist_id " :
2021-05-07 19:53:54 +00:00
anilist_id , name = self . _validate ( data )
2021-04-15 20:45:35 +00:00
anilist_ids = [ anilist_id ]
2021-05-09 05:37:45 +00:00
logger . info ( f " Processing { pretty } : ( { data } ) { name } " )
2021-03-10 16:51:27 +00:00
elif method in [ " anilist_popular " , " anilist_top_rated " ] :
2021-05-07 19:53:54 +00:00
anilist_ids = self . _popular ( data ) if method == " anilist_popular " else self . _top_rated ( data )
2021-05-09 05:37:45 +00:00
logger . info ( f " Processing { pretty } : { data } Anime " )
2021-03-10 16:51:27 +00:00
elif method == " anilist_season " :
2021-05-07 19:53:54 +00:00
anilist_ids = self . _season ( data [ " season " ] , data [ " year " ] , data [ " sort_by " ] , data [ " limit " ] )
2021-05-09 05:37:45 +00:00
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 ' ] ] } " )
2021-03-20 04:22:51 +00:00
elif method == " anilist_genre " :
2021-05-07 19:53:54 +00:00
anilist_ids = self . _genre ( data [ " genre " ] , data [ " sort_by " ] , data [ " limit " ] )
2021-05-09 05:37:45 +00:00
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 ' ] ] } " )
2021-03-20 04:22:51 +00:00
elif method == " anilist_tag " :
2021-05-07 19:53:54 +00:00
anilist_ids = self . _tag ( data [ " tag " ] , data [ " sort_by " ] , data [ " limit " ] )
2021-05-09 05:37:45 +00:00
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 ' ] ] } " )
2021-03-10 16:51:27 +00:00
elif method in [ " anilist_studio " , " anilist_relations " ] :
2021-05-07 19:53:54 +00:00
if method == " anilist_studio " : anilist_ids , name = self . _studio ( data )
else : anilist_ids , _ , name = self . _relations ( data )
2021-05-09 05:37:45 +00:00
logger . info ( f " Processing { pretty } : ( { data } ) { name } ( { len ( anilist_ids ) } Anime) " )
2021-03-10 16:51:27 +00:00
else :
raise Failed ( f " AniList Error: Method { method } not supported " )
2021-05-08 23:49:55 +00:00
movie_ids , show_ids = self . config . Convert . anilist_to_ids ( anilist_ids )
2021-05-24 03:38:46 +00:00
logger . debug ( " " )
2021-07-03 01:47:09 +00:00
logger . debug ( f " { len ( anilist_ids ) } AniList IDs Found: { anilist_ids } " )
logger . debug ( f " { len ( movie_ids ) } TMDb IDs Found: { movie_ids } " )
logger . debug ( f " { len ( show_ids ) } TVDb IDs Found: { show_ids } " )
2021-03-10 16:51:27 +00:00
return movie_ids , show_ids