-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ff50099
Showing
11 changed files
with
572 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
*.vscode | ||
__pycache__ | ||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# OverwatchPy | ||
|
||
A Python wrapper for [https://overfast-api.tekrop.fr/](https://overfast-api.tekrop.fr/) | ||
|
||
I wrote this very fast so it's not very good, but it works. | ||
|
||
Please open a PR if you want to improve it or add more features. | ||
|
||
## Requirements | ||
|
||
- Python 3.11 (3.9+ should work) | ||
- Poetry | ||
|
||
## Installation | ||
|
||
```bash | ||
poetry install | ||
``` | ||
|
||
## Usage | ||
|
||
```python | ||
from overwatchpy import Overwatch | ||
|
||
search: Overwatch.player_search = Overwatch.player_search("twizy", "quickplay", "pc", "public") | ||
|
||
for player in search: | ||
print(player.name) | ||
|
||
ow = Overwatch(battletag="Twizy#11358", gamemode="quickplay", platform="pc") | ||
|
||
print(ow.maps()) | ||
``` | ||
|
||
## License | ||
[MIT](https://choosealicense.com/licenses/mit/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .core import Overwatch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
from __future__ import absolute_import | ||
|
||
import logging | ||
from enum import Enum | ||
from typing import Callable | ||
|
||
import requests | ||
from requests.adapters import HTTPAdapter | ||
from requests.packages.urllib3.util.retry import Retry | ||
|
||
from .const import locale | ||
from .errors import OverwatchAPIError | ||
|
||
logger = logging.getLogger(__name__) | ||
logger.addHandler(logging.NullHandler()) | ||
logger.setLevel(logging.DEBUG) | ||
|
||
__version__: str = "0.0.1" | ||
|
||
class EndPoint(Enum): | ||
domain: str = "overfast-api.tekrop.fr" | ||
scheme: str = "https" | ||
api_base: str = "{scheme}://{domain}/".format(scheme=scheme, domain=domain) | ||
player_url: str = api_base + "players" # with query params | ||
all_player_data_url: str = api_base + "players/{battletag}" | ||
player_summary_url: str = api_base + "players/{battletag}/summary" | ||
player_stats_summary_url: str = api_base + "players/{battletag}/stats/summary" | ||
player_career_url: str = api_base + "players/{battletag}/stats/career" | ||
map_url: str = api_base + "maps" | ||
gamemodes_url: str = api_base + "gamemodes" | ||
heroes_url: str = api_base + "heroes" | ||
|
||
|
||
class Client: | ||
""" | ||
The main class for the Overwatch API wrapper | ||
""" | ||
|
||
def __init__( | ||
self, | ||
use_retry: bool = True, | ||
timeout: int = 30, | ||
) -> None: | ||
""" | ||
Parameters | ||
---------- | ||
use_retry : bool | ||
default: True | ||
Whether to retry on HTTP status codes 500, 502, 503, 504 | ||
timeout : int | ||
default: 30 | ||
The timeout for the requests | ||
""" | ||
self.session: requests.session = requests.session() | ||
self.session.headers["User-Agent"] = "overwatchpy/%s" % __version__ | ||
self.session.headers["Accept"] = "application/json" | ||
self.timeout: int = timeout | ||
if use_retry: | ||
# Retry maximum 10 times, backoff on each retry | ||
# Sleeps 1s, 2s, 4s, 8s, etc to a maximum of 120s between retries | ||
# Retries on HTTP status codes 500, 502, 503, 504 | ||
retries: Retry = Retry( | ||
total=10, backoff_factor=1, status_forcelist=[500, 502, 503, 504] | ||
) | ||
self.session.mount("https://", HTTPAdapter(max_retries=retries)) | ||
|
||
self.local: list = locale | ||
|
||
def close(self): | ||
self.session.close() | ||
|
||
def request( | ||
self, | ||
path, | ||
method: str = "GET", | ||
params: dict = None, | ||
headers: dict = None, | ||
raw: bool = False, | ||
allow_redirects: bool = True, | ||
timeout: int = None, | ||
) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Wrapper around requests.request() | ||
""" | ||
if not headers: | ||
headers: dict = {} | ||
|
||
if not params: | ||
params: dict = {} | ||
|
||
if not timeout: | ||
timeout: int = self.timeout | ||
|
||
response = self.session.request( | ||
method, | ||
path, | ||
params=params, | ||
headers=headers, | ||
allow_redirects=allow_redirects, | ||
timeout=self.timeout, | ||
) | ||
logger.debug("Response: %s", response) | ||
if response.status_code != 200: | ||
raise OverwatchAPIError(response.status_code, response.text) | ||
|
||
if raw: | ||
return response | ||
|
||
return response.json() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
locale: list = [ | ||
"de-de", | ||
"en-gb", | ||
"en-us", | ||
"es-es", | ||
"es-mx", | ||
"fr-fr", | ||
"it-it", | ||
"ja-jp", | ||
"ko-kr", | ||
"pl-pl", | ||
"pt-br", | ||
"ru-ru", | ||
"zh-tw", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
from __future__ import absolute_import | ||
|
||
import logging | ||
import re | ||
from typing import Callable, Literal, Optional | ||
|
||
try: | ||
from urllib import urlencode | ||
except ImportError: | ||
from urllib.parse import urlencode | ||
|
||
from .api import Client, EndPoint | ||
from .errors import (InvalidBattletag, InvalidGamemode, InvalidOrderBy, | ||
InvalidPrivacySettings, OverwatchAPIError, | ||
PlatformNotRecognized) | ||
|
||
logger = logging.getLogger(__name__) | ||
logger.addHandler(logging.NullHandler()) | ||
|
||
__version__ = "0.0.1" | ||
|
||
|
||
class Overwatch(Client): | ||
|
||
client = Client() | ||
|
||
def __init__( | ||
self, | ||
battletag: str = None, | ||
gamemode: Literal["quickplay", "competitive"] = None, | ||
platform: Literal["pc", "console"] = None, | ||
_data: Optional[dict] = None, | ||
) -> None: | ||
super().__init__() | ||
self.battletag = battletag | ||
self.gamemode = gamemode | ||
self.platform = platform | ||
if _data is not None: | ||
self._data = _data | ||
self.battletag = _data.get("player_id") | ||
self.privacy = _data.get("privacy") | ||
self.name = _data.get("name") | ||
|
||
def ping(self) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the ping | ||
""" | ||
return self.client.request(EndPoint.api_base.value) | ||
|
||
@classmethod | ||
def player_search( | ||
cls, | ||
battletag: str, | ||
gamemode: Literal["quickplay", "competitive"], | ||
platform: Literal["pc", "console"], | ||
privacy: Literal["public", "private"], | ||
order_by: Optional[ | ||
Literal[ | ||
"player_id:asc", | ||
"player_id:desc", | ||
"name:asc", | ||
"name:desc", | ||
"privacy:asc", | ||
"privacy:desc", | ||
] | ||
] = "name:asc", | ||
offset: Optional[int] = 0, | ||
limit: Optional[int] = 20, | ||
) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Search for a player | ||
""" | ||
reg = r"^[a-zA-Z0-9]{3,12}#[0-9]{4,5}$" # this could be improved | ||
if battletag == re.match(reg, battletag): | ||
raise InvalidBattletag("Invalid battletag") | ||
|
||
if privacy not in ["public", "private"]: | ||
raise InvalidPrivacySettings("Privacy must be either 'public', 'private'") | ||
|
||
if platform not in ["pc", "console"]: | ||
raise PlatformNotRecognized("Platform must be either 'pc', 'console'") | ||
|
||
if gamemode not in ["quickplay", "competitive"]: | ||
raise InvalidGamemode("Gamemode must be either 'quickplay', 'competitive'") | ||
|
||
if order_by not in [ | ||
"player_id:asc", | ||
"player_id:desc", | ||
"name:asc", | ||
"name:desc", | ||
"privacy:asc", | ||
"privacy:desc", | ||
]: | ||
raise InvalidOrderBy( | ||
"Order by must be either 'player_id:asc', 'player_id:desc', 'name:asc', 'name:desc', 'privacy:asc', 'privacy:desc'" | ||
) | ||
params = { | ||
"name": battletag, | ||
"privacy": privacy, | ||
"platform": platform, | ||
"gamemode": gamemode, | ||
"order_by": order_by, | ||
"offset": offset, | ||
"limit": limit, | ||
} | ||
response = cls.client.request(path=EndPoint.player_url.value, params=urlencode(params)) | ||
|
||
return [cls(_data=data) for data in response['results']] | ||
|
||
def player_summary(self, battletag: Optional[str] = None) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the player's summary | ||
""" | ||
if battletag is None: | ||
battletag = self.battletag | ||
return self.client.request( | ||
EndPoint.player_summary_url.value.format(battletag=battletag) | ||
) | ||
|
||
def player_all_data(self, battletag: Optional[str] = None) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the player's all data | ||
""" | ||
if battletag is None: | ||
battletag = self.battletag | ||
return self.client.request( | ||
EndPoint.domain.all_player_data_url.value.format(battletag=self.battletag) | ||
) | ||
|
||
def player_stats(self, battletag: Optional[str] = None, gamemode: Optional[Literal],platform: Optional[Literal["pc", "console"]] = None) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the player's stats | ||
""" | ||
params = { | ||
"gamemode": self.gamemode, | ||
"platform": self.platform, | ||
} | ||
return self.client.request( | ||
EndPoint.player_stats_summary_url.value.format(battletag=self.battletag), | ||
params=urlencode(params), | ||
) | ||
|
||
def player_career(self, hero: Optional[str]) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the player's career | ||
""" | ||
params = { | ||
"gamemode": self.gamemode, | ||
"platform": self.platform, | ||
"hero": "all-heroes" if hero is None else hero, | ||
} | ||
return self.client.request( | ||
EndPoint.player_career_url.value.format(battletag=self.battletag), | ||
params=urlencode(params), | ||
) | ||
|
||
def maps(self) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the maps | ||
""" | ||
return self.client.request(EndPoint.map_url.value) | ||
|
||
def gamemodes(self) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the gamemodes | ||
""" | ||
return self.client.request(EndPoint.gamemodes_url.value) | ||
|
||
def heroes( | ||
self, | ||
role: Literal["damage", "support", "tank"], | ||
locale: Optional[str] = "en-us", | ||
) -> Callable[[dict], OverwatchAPIError]: | ||
""" | ||
Returns the heroes | ||
""" | ||
if role not in ["damage", "support", "tank"]: | ||
raise InvalidGamemode("Role must be either 'damage', 'support', 'tank'") | ||
params = { | ||
"role": role, | ||
"locale": locale if locale in self.local else "en-us", | ||
} | ||
return self.client.request(EndPoint.heroes_url.value, params=urlencode(params)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
class OverwatchAPIError(Exception): | ||
"""Base exception class for overwatchpy""" | ||
|
||
... | ||
|
||
|
||
class InvalidBattletag(Exception): | ||
""" | ||
Raise when 'battletag' key word argument is none | ||
""" | ||
|
||
... | ||
|
||
|
||
class InvalidGamemode(Exception): | ||
""" | ||
Raise when 'gamemode' key word argument is not recognized | ||
""" | ||
|
||
... | ||
|
||
|
||
class PlatformNotRecognized(Exception): | ||
""" | ||
Raise when 'platform' key word argument is not recognized | ||
""" | ||
|
||
... | ||
|
||
|
||
class InvalidPrivacySettings(Exception): | ||
""" | ||
Raise when 'privacy' key word argument is not recognized | ||
""" | ||
|
||
... | ||
|
||
|
||
class InvalidOrderBy(Exception): | ||
""" | ||
Raise when 'order_by' key word argument is not recognized | ||
""" | ||
|
||
... |
Oops, something went wrong.