2022-02-13 16:33:57 +00:00
import time
2021-03-10 16:51:27 +00:00
from modules import util
from modules . util import Failed
2022-02-13 16:33:57 +00:00
logger = util . logger
2021-03-10 16:51:27 +00:00
2022-03-13 21:06:31 +00:00
builders = [ " anilist_id " , " anilist_popular " , " anilist_trending " , " anilist_relations " , " anilist_studio " , " anilist_top_rated " , " anilist_search " , " anilist_userlist " ]
pretty_names = {
" score " : " Average Score " , " popular " : " Popularity " , " trending " : " Trending " , " CURRENT " : " Currently Watching " ,
" COMPLETED " : " Completed " , " PAUSED " : " Paused " , " DROPPED " : " Dropped " , " PLANNING " : " Planning " , " SCORE " : " Score " ,
" UPDATED_TIME " : " Updated Time " , " STARTED_ON " : " Start Date " , " MEDIA_TITLE_NATIVE " : " Title "
}
2021-08-26 18:24:30 +00:00
attr_translation = {
" year " : " seasonYear " , " adult " : " isAdult " , " start " : " startDate " , " end " : " endDate " , " tag_category " : " tagCategory " ,
" score " : " averageScore " , " min_tag_percent " : " minimumTagRank " , " country " : " countryOfOrigin " ,
}
2021-08-14 22:59:35 +00:00
mod_translation = { " " : " in " , " not " : " not_in " , " before " : " greater " , " after " : " lesser " , " gt " : " greater " , " gte " : " greater " , " lt " : " lesser " , " lte " : " lesser " }
mod_searches = [
" start.before " , " start.after " , " end.before " , " end.after " ,
" format " , " format.not " , " status " , " status.not " , " genre " , " genre.not " , " tag " , " tag.not " , " tag_category " , " tag_category.not " ,
" episodes.gt " , " episodes.gte " , " episodes.lt " , " episodes.lte " , " duration.gt " , " duration.gte " , " duration.lt " , " duration.lte " ,
" score.gt " , " score.gte " , " score.lt " , " score.lte " , " popularity.gt " , " popularity.gte " , " popularity.lt " , " popularity.lte "
]
2021-08-26 18:24:30 +00:00
no_mod_searches = [ " search " , " season " , " year " , " adult " , " min_tag_percent " , " limit " , " sort_by " , " source " , " country " ]
2021-08-14 22:59:35 +00:00
searches = mod_searches + no_mod_searches
2021-08-26 18:24:30 +00:00
sort_options = { " score " : " SCORE_DESC " , " popular " : " POPULARITY_DESC " , " trending " : " TRENDING_DESC " }
2022-03-13 21:06:31 +00:00
userlist_sort_options = [ " score " , " last_updated " , " title " , " start_date " ]
userlist_sort_translation = {
" score " : " SCORE " , " last_updated " : " UPDATED_TIME " ,
" title " : " MEDIA_TITLE_NATIVE " , " start_date " : " STARTED_ON "
}
userlist_status = [ " watching " , " completed " , " paused " , " dropped " , " planning " ]
userlist_status_translation = {
" watching " : " CURRENT " , " completed " : " COMPLETED " ,
" paused " : " PAUSED " , " dropped " : " DROPPED " ,
" planning " : " PLANNING "
}
2021-08-14 22:59:35 +00:00
media_season = { " winter " : " WINTER " , " spring " : " SPRING " , " summer " : " SUMMER " , " fall " : " FALL " }
media_format = { " tv " : " TV " , " short " : " TV_SHORT " , " movie " : " MOVIE " , " special " : " SPECIAL " , " ova " : " OVA " , " ona " : " ONA " , " music " : " MUSIC " }
media_status = { " finished " : " FINISHED " , " airing " : " RELEASING " , " not_yet_aired " : " NOT_YET_RELEASED " , " cancelled " : " CANCELLED " , " hiatus " : " HIATUS " }
2021-08-26 18:24:30 +00:00
media_source = {
" original " : " ORIGINAL " , " manga " : " MANGA " , " light_novel " : " LIGHT_NOVEL " , " visual_novel " : " VISUAL_NOVEL " ,
" video_game " : " VIDEO_GAME " , " other " : " OTHER " , " novel " : " NOVEL " , " doujinshi " : " DOUJINSHI " , " anime " : " ANIME "
}
2021-07-14 14:47:20 +00:00
base_url = " https://graphql.anilist.co "
2021-08-13 14:18:05 +00:00
tag_query = " query { MediaTagCollection { name, category}} "
2021-05-07 19:53:54 +00:00
genre_query = " query {GenreCollection} "
2021-08-26 18:24:30 +00:00
country_codes = [
" af " , " ax " , " al " , " dz " , " as " , " ad " , " ao " , " ai " , " aq " , " ag " , " ar " , " am " , " aw " , " au " , " at " , " az " , " bs " , " bh " , " bd " ,
" bb " , " by " , " be " , " bz " , " bj " , " bm " , " bt " , " bo " , " bq " , " ba " , " bw " , " bv " , " br " , " io " , " bn " , " bg " , " bf " , " bi " , " cv " ,
" kh " , " cm " , " ca " , " ky " , " cf " , " td " , " cl " , " cn " , " cx " , " cc " , " co " , " km " , " cg " , " cd " , " ck " , " cr " , " ci " , " hr " , " cu " ,
" cw " , " cy " , " cz " , " dk " , " dj " , " dm " , " do " , " ec " , " eg " , " sv " , " gq " , " er " , " ee " , " sz " , " et " , " fk " , " fo " , " fj " , " fi " ,
" fr " , " gf " , " pf " , " tf " , " ga " , " gm " , " ge " , " de " , " gh " , " gi " , " gr " , " gl " , " gd " , " gp " , " gu " , " gt " , " gg " , " gn " , " gw " ,
" gy " , " ht " , " hm " , " va " , " hn " , " hk " , " hu " , " is " , " in " , " id " , " ir " , " iq " , " ie " , " im " , " il " , " it " , " jm " , " jp " , " je " ,
" jo " , " kz " , " ke " , " ki " , " kp " , " kr " , " kw " , " kg " , " la " , " lv " , " lb " , " ls " , " lr " , " ly " , " li " , " lt " , " lu " , " mo " , " mg " ,
" mw " , " my " , " mv " , " ml " , " mt " , " mh " , " mq " , " mr " , " mu " , " yt " , " mx " , " fm " , " md " , " mc " , " mn " , " me " , " ms " , " ma " , " mz " ,
" mm " , " na " , " nr " , " np " , " nl " , " nc " , " nz " , " ni " , " ne " , " ng " , " nu " , " nf " , " mk " , " mp " , " no " , " om " , " pk " , " pw " , " ps " ,
" pa " , " pg " , " py " , " pe " , " ph " , " pn " , " pl " , " pt " , " pr " , " qa " , " re " , " ro " , " ru " , " rw " , " bl " , " sh " , " kn " , " lc " , " mf " ,
" pm " , " vc " , " ws " , " sm " , " st " , " sa " , " sn " , " rs " , " sc " , " sl " , " sg " , " sx " , " sk " , " si " , " sb " , " so " , " za " , " gs " , " ss " ,
" es " , " lk " , " sd " , " sr " , " sj " , " se " , " ch " , " sy " , " tw " , " tj " , " tz " , " th " , " tl " , " tg " , " tk " , " to " , " tt " , " tn " , " tr " ,
" tm " , " tc " , " tv " , " ug " , " ua " , " ae " , " gb " , " us " , " um " , " uy " , " uz " , " vu " , " ve " , " vn " , " vg " , " vi " , " wf " , " eh " , " ye " ,
" zm " , " zw " ,
]
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-12-12 07:38:59 +00:00
self . _options = None
@property
def options ( self ) :
if self . _options :
return self . _options
self . _options = {
2021-08-14 22:59:35 +00:00
" Tag " : { } , " Tag Category " : { } ,
" Genre " : { g . lower ( ) . replace ( " " , " - " ) : g for g in self . _request ( genre_query , { } ) [ " data " ] [ " GenreCollection " ] } ,
2021-08-26 18:24:30 +00:00
" Country " : { c : c . upper ( ) for c in country_codes } ,
" Season " : media_season , " Format " : media_format , " Status " : media_status , " Source " : media_source ,
2021-08-14 22:59:35 +00:00
}
2021-08-13 14:18:05 +00:00
for media_tag in self . _request ( tag_query , { } ) [ " data " ] [ " MediaTagCollection " ] :
2021-12-12 07:38:59 +00:00
self . _options [ " Tag " ] [ media_tag [ " name " ] . lower ( ) . replace ( " " , " - " ) ] = media_tag [ " name " ]
self . _options [ " Tag Category " ] [ media_tag [ " category " ] . lower ( ) . replace ( " " , " - " ) ] = media_tag [ " category " ]
return self . _options
2021-03-10 16:51:27 +00:00
2021-08-14 03:32:26 +00:00
def _request ( self , query , variables , level = 1 ) :
2021-11-03 14:38:43 +00:00
if self . config . trace_mode :
logger . debug ( f " Query: { query } " )
logger . debug ( f " Variables: { 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 ( )
2021-11-03 14:38:43 +00:00
if self . config . trace_mode :
logger . debug ( f " Response: { json_obj } " )
2021-03-10 16:51:27 +00:00
if " errors " in json_obj :
if json_obj [ ' errors ' ] [ 0 ] [ ' message ' ] == " Too Many Requests. " :
2021-08-14 03:32:26 +00:00
wait_time = int ( response . headers [ " Retry-After " ] ) if " Retry-After " in response . headers else 0
time . sleep ( wait_time if wait_time > 0 else 10 )
if level < 6 :
return self . _request ( query , variables , level = level + 1 )
raise Failed ( f " AniList Error: Connection Failed " )
2021-03-10 16:51:27 +00:00
else :
raise Failed ( f " AniList Error: { json_obj [ ' errors ' ] [ 0 ] [ ' message ' ] } " )
else :
2021-08-14 03:32:26 +00:00
time . sleep ( 60 / 90 )
2021-03-10 16:51:27 +00:00
return json_obj
2021-08-13 14:18:05 +00:00
def _validate_id ( 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-08-14 22:59:35 +00:00
def _search ( self , * * kwargs ) :
2021-08-28 05:17:14 +00:00
media_vars = f " sort: { sort_options [ kwargs [ ' sort_by ' ] ] } , type: ANIME "
2021-08-26 18:24:30 +00:00
variables = { " sort " : sort_options [ kwargs [ ' sort_by ' ] ] }
2021-08-13 14:18:05 +00:00
for key , value in kwargs . items ( ) :
2021-08-14 22:59:35 +00:00
if key not in [ " sort_by " , " limit " ] :
if " . " in key :
attr , mod = key . split ( " . " )
else :
attr = key
mod = " "
ani_attr = attr_translation [ attr ] if attr in attr_translation else attr
final = ani_attr if attr in no_mod_searches else f " { ani_attr } _ { mod_translation [ mod ] } "
if attr in [ " start " , " end " ] :
value = int ( util . validate_date ( value , f " anilist_search { key } " , return_as = " % Y % m %d " ) )
2021-08-26 18:24:30 +00:00
elif attr in [ " format " , " status " , " genre " , " tag " , " tag_category " ] :
2021-09-13 03:25:30 +00:00
temp_value = [ self . options [ attr . replace ( ' _ ' , ' ' ) . title ( ) ] [ v . lower ( ) . replace ( ' / ' , ' - ' ) . replace ( ' ' , ' - ' ) ] for v in value ]
if attr in [ " format " , " status " ] :
value = f " [ { ' , ' . join ( temp_value ) } ] "
else :
temp = ' " , " ' . join ( temp_value )
value = f ' [ " { temp } " ] '
2021-08-26 18:24:30 +00:00
elif attr in [ " season " , " source " , " country " ] :
value = self . options [ attr . replace ( " _ " , " " ) . title ( ) ] [ value ]
2021-08-14 22:59:35 +00:00
if mod == " gte " :
value - = 1
elif mod == " lte " :
value + = 1
2021-08-28 05:17:14 +00:00
media_vars + = f " , { final } : { value } "
query = f " query ($page: Int) {{ Page(page: $page) {{ pageInfo {{ hasNextPage }} media( { media_vars } ) {{ id }} }} }} "
2021-08-14 22:59:35 +00:00
logger . debug ( query )
return self . _pagenation ( query , limit = kwargs [ " 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-08-13 14:18:05 +00:00
anilist_id , name = self . _validate_id ( 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
2022-03-13 21:06:31 +00:00
def _userlist ( self , username , status , sort_by ) :
query = " query ($userName: String, $status: MediaListStatus) { MediaListCollection (userName: $userName, status: $status, type: ANIME) { lists { status entries { media { id title { romaji english}}}}}} "
variables = { " userName " : username , " status " : status , " sort " : sort_by }
json_obj = self . _request ( query , variables )
lists = json_obj [ ' data ' ] [ ' MediaListCollection ' ] [ ' lists ' ]
anilist_ids = [ ]
for list in lists :
for media in list [ ' entries ' ] :
anilist_ids . append ( media [ ' media ' ] [ ' id ' ] )
return anilist_ids
2021-08-14 22:59:35 +00:00
def validate ( self , name , data ) :
valid = [ ]
for d in util . get_list ( data ) :
2021-08-25 12:16:45 +00:00
if d . lower ( ) . replace ( " / " , " - " ) . replace ( " " , " - " ) in self . options [ name ] :
2021-08-16 13:13:30 +00:00
valid . append ( d )
2021-08-14 22:59:35 +00:00
if len ( valid ) > 0 :
return valid
raise Failed ( f " AniList Error: { name } : { data } does not exist \n Options: { ' , ' . join ( [ v for k , v in self . options [ name ] . items ( ) ] ) } " )
2021-03-20 04:22:51 +00:00
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-08-13 14:18:05 +00:00
query = f " query ($id: Int) {{ { ' Studio(id: $id) {name} ' if studio else ' Media(id: $id) {id} ' } }} "
2021-07-26 19:03:17 +00:00
for anilist_id in anilist_id_list :
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-08-07 06:01:21 +00:00
def get_anilist_ids ( self , method , data ) :
2021-03-10 16:51:27 +00:00
if method == " anilist_id " :
2021-08-01 04:35:42 +00:00
logger . info ( f " Processing AniList ID: { data } " )
2021-08-13 14:18:05 +00:00
anilist_id , name = self . _validate_id ( data )
2021-04-15 20:45:35 +00:00
anilist_ids = [ anilist_id ]
2021-08-01 04:35:42 +00:00
elif method == " anilist_studio " :
anilist_ids , name = self . _studio ( data )
logger . info ( f " Processing AniList Studio: ( { data } ) { name } ( { len ( anilist_ids ) } Anime) " )
elif method == " anilist_relations " :
anilist_ids , _ , name = self . _relations ( data )
logger . info ( f " Processing AniList Relations: ( { data } ) { name } ( { len ( anilist_ids ) } Anime) " )
2022-03-13 21:06:31 +00:00
elif method == " anilist_userlist " :
anilist_ids = self . _userlist ( data [ " username " ] , data [ " status " ] , data [ ' sort_by ' ] )
logger . info ( f " Processing AniList Userlist: Anime from { data [ ' username ' ] } ' s { pretty_names [ data [ ' status ' ] ] } list sorted by { pretty_names [ data [ ' sort_by ' ] ] } " )
2021-03-10 16:51:27 +00:00
else :
2021-08-14 22:59:35 +00:00
if method == " anilist_popular " :
data = { " limit " : data , " popularity.gt " : 3 , " sort_by " : " popular " }
2021-08-26 18:24:30 +00:00
elif method == " anilist_trending " :
data = { " limit " : data , " sort_by " : " trending " }
2021-08-14 22:59:35 +00:00
elif method == " anilist_top_rated " :
data = { " limit " : data , " score.gt " : 3 , " sort_by " : " score " }
elif method not in builders :
raise Failed ( f " AniList Error: Method { method } not supported " )
2021-08-26 18:24:30 +00:00
message = f " Processing { method . replace ( ' _ ' , ' ' ) . title ( ) . replace ( ' Anilist ' , ' AniList ' ) } : \n \t Sort By { pretty_names [ data [ ' sort_by ' ] ] } "
2021-08-14 22:59:35 +00:00
if data [ ' limit ' ] > 0 :
2021-08-26 18:24:30 +00:00
message + = f " \n \t Limit to { data [ ' limit ' ] } Anime "
2021-08-14 22:59:35 +00:00
for key , value in data . items ( ) :
2021-08-26 18:24:30 +00:00
if key not in [ " limit " , " sort_by " ] :
if " . " in key :
attr , mod = key . split ( " . " )
mod = f " . { mod } "
else :
attr = key
mod = " "
message + = f " \n \t { attr . replace ( ' _ ' , ' ' ) . title ( ) } { util . mod_displays [ mod ] } { value } "
2022-02-13 16:33:57 +00:00
logger . info ( message )
2021-08-14 22:59:35 +00:00
anilist_ids = self . _search ( * * data )
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 } " )
2021-08-07 06:01:21 +00:00
return anilist_ids