11import asyncio
22import aiohttp
3- from typing import List , Union , Optional
3+ from typing import List , Union , Optional , Literal
4+ from dataclasses import dataclass
5+
6+
7+ @dataclass
8+ class Rank :
9+ group : Literal ["Bronze" , "Silver" , "Gold" , "Platinum" , "Diamond" , "Master" , "Grandmaster" ]
10+ tier : Literal [1 , 2 , 3 , 4 , 5 ]
11+
12+ _group_order = ["Bronze" , "Silver" , "Gold" , "Platinum" , "Diamond" , "Master" , "Grandmaster" ]
13+
14+ def __gt__ (self , other ):
15+ if isinstance (other , Rank ):
16+ if self .group == other .group :
17+ return self .tier < other .tier
18+ else :
19+ return self ._group_order .index (self .group ) > self ._group_order .index (other .group )
20+ else :
21+ raise TypeError ("Cannot compare Rank to non-Rank object" )
22+
23+ def __lt__ (self , other ):
24+ if isinstance (other , Rank ):
25+ if self .group == other .group :
26+ return self .tier > other .tier
27+ else :
28+ return self ._group_order .index (self .group ) < self ._group_order .index (other .group )
29+ else :
30+ raise TypeError ("Cannot compare Rank to non-Rank object" )
31+
32+ def __eq__ (self , other ):
33+ if isinstance (other , Rank ):
34+ return self .group == other .group and self .tier == other .tier
35+ else :
36+ raise TypeError ("Cannot compare Rank to non-Rank object" )
437
538
639class Player :
7- def __init__ (self , player_name : str , response : dict , platform : str = "pc" , original_player_name : Optional [str ] = None ,):
40+ def __init__ (self , player_name : str , response : dict , original_player_name : Optional [str ] = None , ):
841 self .player_name : str = player_name
942 self .original_player_name : Optional [str ] = original_player_name
10- self .platform : str = platform
1143 self .success : bool = "error" not in response
1244 if self .success :
13- self .level : Optional [int ] = response ["level" ]
14- self .prestige : Optional [int ] = response ["prestige" ]
15- self .actual_level : Optional [int ] = self .prestige * 100 + self .level
1645 self .private : Optional [bool ] = response ["private" ]
1746 self .endorsement : Optional [int ] = response ["endorsement" ]
1847
1948 if not self .private :
2049 self .quickplay_stats : Optional [dict ] = response ["competitiveStats" ]
21- self .quickplay_cards : Optional [int ] = self .quickplay_stats ["awards" ]["cards" ]
22- self .quickplay_medals : Optional [int ] = self .quickplay_stats ["awards" ]["medals" ]
23- self .quickplay_medals_bronze : Optional [int ] = self .quickplay_stats ["awards" ]["medalsBronze" ]
24- self .quickplay_medals_silver : Optional [int ] = self .quickplay_stats ["awards" ]["medalsSilver" ]
25- self .quickplay_medals_gold : Optional [int ] = self .quickplay_stats ["awards" ]["medalsGold" ]
2650 self .quickplay_games_won : Optional [int ] = self .quickplay_stats ["games" ]["won" ]
2751
2852 self .competitive_stats : Optional [dict ] = response ["competitiveStats" ]
29- self .competitive_cards : Optional [int ] = self .competitive_stats ["awards" ]["cards" ]
30- self .competitive_medals : Optional [int ] = self .competitive_stats ["awards" ]["medals" ]
31- self .competitive_medals_bronze : Optional [int ] = self .competitive_stats ["awards" ]["medalsBronze" ]
32- self .competitive_medals_silver : Optional [int ] = self .competitive_stats ["awards" ]["medalsSilver" ]
33- self .competitive_medals_gold : Optional [int ] = self .competitive_stats ["awards" ]["medalsGold" ]
3453 self .competitive_games_played : Optional [int ] = self .competitive_stats ["games" ]["played" ]
3554 self .competitive_games_won : Optional [int ] = self .competitive_stats ["games" ]["won" ]
3655
37- self .competitive_tank : Union [ bool , int ] = False
38- self .competitive_damage : Union [ bool , int ] = False
39- self .competitive_support : Union [ bool , int ] = False
56+ self .competitive_tank : Optional [ Rank ] = None
57+ self .competitive_damage : Optional [ Rank ] = None
58+ self .competitive_support : Optional [ Rank ] = None
4059
4160 if response .get ("ratings" ):
4261 for rating in response ["ratings" ]:
4362 if rating ["role" ] == "tank" :
44- self .competitive_tank = rating ["level" ]
63+ self .competitive_tank = Rank ( rating ["group" ], rating [ "tier" ])
4564 if rating ["role" ] == "damage" :
46- self .competitive_damage = rating ["level" ]
65+ self .competitive_damage = Rank ( rating ["group" ], rating [ "tier" ])
4766 if rating ["role" ] == "support" :
48- self .competitive_support = rating ["level" ]
67+ self .competitive_support = Rank ( rating ["group" ], rating [ "tier" ])
4968
5069 def __repr__ (self ):
5170 return f"<Player { self .player_name } success: { self .success } >"
5271
5372
54- async def _correct_player_internal (session : aiohttp .ClientSession , incorrect_player_name : str , platform : str = "pc" ) -> Optional [Player ]:
73+ async def _correct_player_internal (session : aiohttp .ClientSession , incorrect_player_name : str ) -> Optional [Player ]:
5574 """
5675 Attempts to find the corrected player name and returns a Player object if it does
5776 :param session: An aiohttp ClientSession
5877 :param incorrect_player_name: The incorrect player name to correct
59- :param platform: Platform, can be pc, xbox, ps4 and nintendo-switch
6078 :return: A player object if a corrected battletag is found
6179 """
62- search_terms = []
63- if platform == "pc" :
64- # Use full name to correct wrong capitalization
65- player_name_with_discriminator = incorrect_player_name .replace ('#' , '%23' )
66- # Try short version to account for a wrong discriminator
67- player_name_short = incorrect_player_name .split ('#' )[0 ]
68- search_terms .append (player_name_with_discriminator )
69- search_terms .append (player_name_short )
70- else :
71- # Use full player name to correct wrong capitalization, there are no discriminators on console
72- search_terms .append (incorrect_player_name )
80+
81+ # Use full name to correct wrong capitalization
82+ player_name_with_discriminator = incorrect_player_name .replace ('#' , '%23' )
83+ # Try short version to account for a wrong discriminator
84+ player_name_short = incorrect_player_name .split ('#' )[0 ]
85+
86+ search_terms = [player_name_with_discriminator , player_name_short ]
7387
7488 for search_term in search_terms :
7589 async with session .get (f"https://playoverwatch.com/en-us/search/account-by-name/{ search_term } " ) as r :
7690 if r .status == 200 :
7791 profiles = await r .json ()
78- # Filter the data to only include names from the platform
79- profiles = [profile for profile in profiles if profile ["platform" ] == platform ]
8092 # If we have one match that must be it
8193 if len (profiles ) == 1 :
82- new_player = await _get_player_internal (session , profiles [0 ]["name" ], platform )
94+ new_player = await _get_player_internal (session , profiles [0 ]["battleTag" ] )
8395 if new_player .success :
8496 new_player .original_player_name = incorrect_player_name
8597 return new_player
8698
8799
88- async def _get_player_internal (session : aiohttp .ClientSession , player_name : str , platform : str = "pc" , correct_player : bool = False ) -> Player :
100+ async def _get_player_internal (session : aiohttp .ClientSession , player_name : str ,
101+ correct_player : bool = False ) -> Player :
89102 """
90103 Uses an aiohttp session to get a player. This is a coroutine and must be awaited.
91104 :param session: An aiohttp ClientSession
92105 :param player_name: String that is the players name (battletag or other)
93- :param platform: Platform, can be pc, xbox, ps4 and nintendo-switch
94106 :param correct_player: If True and the lookup fails a correction will be attempted.
95107 :return: A Player object
96108 """
97109 try :
98110 async with session .get (
99- f"https://ow-api.com/v2/stats/{ platform } /{ player_name .replace ('#' , '-' )} /profile" ) as resp :
111+ f"https://ow-api.com/v2/stats/pc /{ player_name .replace ('#' , '-' )} /profile" ) as resp :
100112 data = await resp .json ()
101113 player = Player (player_name , data )
102114 if not correct_player :
103115 return player
104116 elif player .success :
105117 return player
106118 else :
107- new_player = await _correct_player_internal (session , player_name , platform )
119+ new_player = await _correct_player_internal (session , player_name )
108120 if new_player :
109121 return new_player
110122 else :
@@ -114,31 +126,38 @@ async def _get_player_internal(session: aiohttp.ClientSession, player_name: str,
114126 return Player (player_name , {"error" : "timeout" })
115127
116128
117- async def get_player_async (player_names : Union [str , List [str ]], platform : str = "pc" , correct_player : bool = False ) -> Union [Player , List [Player ]]:
129+ async def get_player_async (player_names : Union [str , List [str ]], correct_player : bool = False ) -> Union [
130+ Player , List [Player ]]:
118131 """
119132 This is a coroutine and must be awaited.
120133 :param player_names: String that is the players name (battletag or other)
121- :param platform: Platform, can be pc, xbox, ps4 and nintendo-switch
122134 :param correct_player: If True and the lookup fails a correction will be attempted.
123135 :return: A Player object
124136 """
125137 async with aiohttp .ClientSession () as session :
126138 if isinstance (player_names , list ):
127- result = await asyncio .gather (* [_get_player_internal (session , player , platform ) for player in player_names ])
139+ result = await asyncio .gather (* [_get_player_internal (session , player ) for player in player_names ])
128140 return result
129141 else :
130- result = await _get_player_internal (session , player_names , platform , correct_player )
142+ result = await _get_player_internal (session , player_names , correct_player )
131143 return result
132144
133145
134- def get_player (player_names : Union [str , List [str ]], platform : str = "pc" , correct_player : bool = False ) -> Union [Player , List [Player ]]:
146+ def get_player (player_names : Union [str , List [str ]], correct_player : bool = False ) -> Union [Player , List [Player ]]:
135147 """
136148 Automatically creates event loop. Does not work with programs that already have an event loop, await _get_player instead
137149 :param player_names: String that is the players name (battletag or other)
138- :param platform: Platform, can be pc, xbox, ps4 and nintendo-switch
139150 :param correct_player: If True and the lookup fails a correction will be attempted.
140151 :return: A Player object
141152 """
142- loop = asyncio .get_event_loop ()
143- result = loop .run_until_complete (get_player_async (player_names , platform , correct_player ))
153+ loop = asyncio .new_event_loop ()
154+ asyncio .set_event_loop (loop )
155+
156+ result = loop .run_until_complete (get_player_async (player_names , correct_player ))
157+
158+ # Wait 250 ms for the underlying SSL connections to close
159+ # See https://docs.aiohttp.org/en/stable/client_advanced.html#graceful-shutdown
160+ loop .run_until_complete (asyncio .sleep (0.250 ))
161+ loop .close ()
162+
144163 return result
0 commit comments