Skip to content

Commit 09f0284

Browse files
authored
Merge pull request #3 from zusorio/v3
Update for Overwatch 2
2 parents 83dc882 + bae0bb9 commit 09f0284

7 files changed

Lines changed: 120 additions & 157 deletions

File tree

.github/workflows/codeql-analysis.yml

Lines changed: 0 additions & 54 deletions
This file was deleted.

LICENSE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020 Tobias Messner
3+
Copyright (c) 2023 Tobias Messner
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
21+
SOFTWARE.

README.md

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# pyowapi
22
`pyowapi` is an asynchronous wrapper around an unofficial Overwatch api (https://ow-api.com) using aiohttp.
33

4-
Things have changed considerably from v1 to v2, make sure to update your code before upgrading.
4+
Things have changed considerably from v2 to v3, make sure to update your code before upgrading.
55

66
# Example usage
77

@@ -13,19 +13,19 @@ import pyowapi
1313
# For a single player
1414
player = pyowapi.get_player("Jayne#1447")
1515
print(player.success)
16-
print(player.actual_level)
1716
print(player.private)
1817
print(player.competitive_tank)
1918

2019
# For multiple players
2120
player_list = ["Jayne#1447", "Krusher#9999"]
2221
players = pyowapi.get_player(player_list)
2322
for single_player in players:
24-
print(player.actual_level)
23+
print(player.competitive_tank.group)
24+
print(player.competitive_tank.tier)
2525

2626
# For different platforms
2727
player = pyowapi.get_player("Krusher99", platform="psn") # platform can be pc, xbox, ps4 and nintendo-switch
28-
print(player.actual_level)
28+
print(player.competitive_tank)
2929

3030
# If the player name is from user input sometimes it can be capitalized wrong or use the wrong discriminator
3131
# You can pass correct_player = True and if the player name is incorrect pyowapi will attempt to find the correct one
@@ -38,35 +38,22 @@ print(player.success) # True
3838

3939
# If an event loop already exists you need to call get_player_async instead
4040
player = await pyowapi.get_player_async("Jayne#1447")
41-
print(player.actual_level)
4241
print(player.private)
4342
print(player.competitive_tank)
4443

4544
# A player has the following properties
4645
print(player.player_name) # The name of the Player (battletag or other)
4746
print(player.original_player_name) # If a player name was corrected this is the misspelled version
4847
print(player.success) # If the request was successful
49-
print(player.level) # The number in Overwatch without stars calculated in
50-
print(player.prestige) # The number of stars in Overwatch
51-
print(player.actual_level) # The full level with stars calculated in
5248
print(player.private) # If the player profile is private
5349
print(player.endorsement) # The player endorsement level
54-
print(player.competitive_tank) # Player Tank SR. False if unplaced
55-
print(player.competitive_damage) # Player Damage SR. False if unplaced
56-
print(player.competitive_support) # Player Support SR. False if unplaced
50+
print(player.competitive_tank.tier) # Player Tank Tier (5-1)
51+
print(player.competitive_tank.group) # Player Tank Group (Bronze, Silver, Gold, Platinum, Diamond, Master, Grandmaster)
52+
print(player.competitive_damage) # Player Damage Rating (similar to Tank)
53+
print(player.competitive_support) # Player Support Rating (similar to Tank)
5754
print(player.quickplay_stats) # Dictionary containing all quickplay stats
58-
print(player.quickplay_cards)
59-
print(player.quickplay_medals)
60-
print(player.quickplay_medals_bronze)
61-
print(player.quickplay_medals_silver)
62-
print(player.quickplay_medals_gold)
6355
print(player.quickplay_games_won)
6456
print(player.competitive_stats) # Dictionary containing all competitive stats
65-
print(player.competitive_cards)
66-
print(player.competitive_medals)
67-
print(player.competitive_medals_bronze)
68-
print(player.competitive_medals_silver)
69-
print(player.competitive_medals_gold)
7057
print(player.competitive_games_played)
7158
print(player.competitive_games_won)
7259
```

pyowapi/__init__.py

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,122 @@
11
import asyncio
22
import 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

639
class 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

Comments
 (0)