diff --git a/.vscode/settings.json b/.vscode/settings.json index 551e8c6..c9ab30f 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "python.pythonPath": "/bin/python3", - "workbench.colorTheme": "Atom One Dark" + "workbench.colorTheme": "One Dark Pro Darker" } \ No newline at end of file diff --git a/lib/binary_build.sh b/lib/binary_build.sh index f3516c1..6db5c18 100755 --- a/lib/binary_build.sh +++ b/lib/binary_build.sh @@ -12,8 +12,6 @@ mv -v ohmyoled ohmyoled-$VERSION/ cp -v lib/config/ohmyoled.conf ohmyoled-$VERSION -cp -v lib/weather/ecIcons_utf8.csv ohmyoled-$VERSION - cp -v install.sh ohmyoled-$VERSION cp -vr fonts/ ohmyoled-$VERSION diff --git a/lib/config/ohmyoled.conf b/lib/config/ohmyoled.conf index e92e4ad..ee3c77d 100644 --- a/lib/config/ohmyoled.conf +++ b/lib/config/ohmyoled.conf @@ -42,14 +42,9 @@ Run=False [sport] Run=False -# football -# baseball -# hockey -# basketball -# formula 1 -# sport= -# For Team IDs Baseball https://dashboard.api-football.com/baseball/ids/teams -# For Team IDs Basketball https://dashboard.api-football.com/basketball/ids/teams/USA -# Only Support USA -team_id = 6 +# sportsipy +# api-sports +api=api-sports +sport=basketball +team_id = # optional Will pull all data for all unless specified \ No newline at end of file diff --git a/lib/run.py b/lib/run.py index fa5a450..07af8bb 100755 --- a/lib/run.py +++ b/lib/run.py @@ -57,10 +57,10 @@ def __init__(self, config): super().__init__() self.config = config self.runner_logger = logger - if self.config['basic'].getboolean('testing'): + if not self.config['basic'].getboolean('testing'): self.logger.setLevel(self.config['basic'].getint('loglevel')) else: - self.logger.setLevel(10) + self.logger.setLevel(logging.DEBUG) self.logger.debug(f"Runner logger is set to {self.logger.getEffectiveLevel()}") def run_non_async_request(self, url) -> Response: diff --git a/lib/sports/apisports/baseball/baseball.py b/lib/sports/apisports/baseball/baseball.py index 0386edc..891f62c 100755 --- a/lib/sports/apisports/baseball/baseball.py +++ b/lib/sports/apisports/baseball/baseball.py @@ -1,6 +1,9 @@ from lib.run import Runner from datetime import datetime from lib.sports.apisports.result import SportApiResult +from lib.sports.logo import logo_map, Logo +from datetime import datetime +import lib.sports.sportbase as base class Baseball(Runner): def __init__(self, token, config, headers): @@ -20,19 +23,79 @@ def parse_args(self): args = ('standings', 'next_game') return args - def url_builder(self, args): + def url_builder(self, args, logo): self.headers.update({'x-apisports-host': 'v1.baseball.api-sports.io'}) urls = {} base = "https://v1.baseball.api-sports.io/" urls.update({'standings': base + f'standings?league=1&season=2021'}) - urls.update({'next_game': base + f"games?team={self.config.getint('team_id')}&league=1&season=2021&timezone=America/Chicago"}) + urls.update({'next_game': base + f"games?team={logo.apisportsid}&league=1&season=2021&timezone=America/Chicago"}) return urls - async def run(self): - self.logger.info('Running Baseball API') + async def run(self) -> SportApiResult: + self.logger.info('Running baseball API') parsed = self.parse_args() api_data = {} - for section, url in self.url_builder(parsed).items(): + logo = logo_map[self.config.get('team_id')] + for section, url in self.url_builder(parsed, logo).items(): api_data.update({section: await self.get_data(url, self.headers)}) api_data['sport'] = 'baseball' - return SportApiResult(api_data) + schedule = [ + base.Game( + team=base.Team( + name=logo.name, + logo=logo, + ), + timestamp=datetime.fromtimestamp(game['timestamp']), + status=base.determine_apisports_game_status( + game, + base.Team( + logo.name, + logo=logo + ), + api_data['sport'] + )[0], + opposing_team=base.Team( + name=game['teams']['home']['name'], + logo=logo_map[game['teams']['home']['name']], + ) if logo.name != game['teams']['home']['name'] else base.Team( + name=game['teams']['away']['name'], logo=logo_map[game['teams']['away']['name']] + ), + result=base.determine_apisports_game_status( + game, + base.Team( + logo.name, + logo=logo + ), + api_data['sport'] + )[1], + homeoraway="home" if logo.name == game['teams']['home']['name'] else "away", + score=base.Score( + team=game['scores']['home'] if logo.name == game['teams']['home']['name'] else game['scores']['away'], + opposing_team=game['scores']['home'] if logo.name != game['teams']['home']['name'] else game['scores']['away'], + ), + + ) for game in api_data['next_game']['response'] + ] + standings=[ + base.Team( + name=team['team']['name'], + logo=logo_map[team['team']['name']], + position=team['position'], + league=team['group']['name'] + ) + for team in api_data['standings']['response'][0] + api_data['standings']['response'][1] + ] + result = base.ModuleResult( + name=logo.name, + team=base.Team( + name=logo.name, + logo=logo, + ), + schedule=schedule, + standings = standings, + sport=base.SportStructure.Baseball, + games_played=len([game for game in schedule if game.status == base.GameStatus.Finished or game.status == base.GameStatus.Overtime]), + wins=len([game for game in schedule if game.result == base.GameResult.WIN]), + losses=len([game for game in schedule if game.result == base.GameResult.LOSS]), + ) + return SportApiResult(result) diff --git a/lib/sports/apisports/basketball/basketball.py b/lib/sports/apisports/basketball/basketball.py index 8d45298..c91cb96 100755 --- a/lib/sports/apisports/basketball/basketball.py +++ b/lib/sports/apisports/basketball/basketball.py @@ -1,14 +1,16 @@ from lib.run import Runner from datetime import datetime from lib.sports.apisports.result import SportApiResult - +from lib.sports.logo import logo_map, Logo +from datetime import datetime +import lib.sports.sportbase as base class Basketball(Runner): def __init__(self, token, config, headers): self.config = config self.token = token self.headers = headers self.year = datetime.now().year - + def parse_args(self): try: if self.config['type'] != '' or not self.config['type']: @@ -19,22 +21,81 @@ def parse_args(self): except KeyError: args = ('standings', 'next_game') return args - - def url_builder(self, args): + + def url_builder(self, args, logo): self.headers.update({'x-apisports-host': 'v1.basketball.api-sports.io'}) urls = {} base = "https://v1.basketball.api-sports.io/" if 'standings' in args: - urls.update({'standings': base + f'standings?league=12&season=2019-2020'}) + urls.update({'standings': base + f'standings?league=12&season=2021-2022'}) if 'next_game' in args: - urls.update({'next_game': base + f"games?team={self.config.getint('team_id')}&league=12&season=2019-2020&timezone=America/Chicago"}) + urls.update({'next_game': base + f"games?team={logo.apisportsid}&league=12&season=2021-2022&timezone=America/Chicago"}) return urls - async def run(self): + async def run(self) -> SportApiResult: self.logger.info('Running Basketball API') parsed = self.parse_args() api_data = {} - for section, url in self.url_builder(parsed).items(): + logo = logo_map[self.config.get('team_id')] + for section, url in self.url_builder(parsed, logo).items(): api_data.update({section: await self.get_data(url, self.headers)}) api_data['sport'] = 'basketball' - return SportApiResult(api_data) \ No newline at end of file + schedule = [ + base.Game( + team=base.Team( + name=logo.name, + logo=logo, + ), + timestamp=datetime.fromtimestamp(game['timestamp']), + status=base.determine_apisports_game_status( + game, + base.Team( + logo.name, + logo=logo + ), + api_data['sport'] + )[0], + opposing_team=base.Team( + name=game['teams']['home']['name'], + logo=logo_map[game['teams']['home']['name']], + ) if logo.name != game['teams']['home']['name'] else base.Team( + name=game['teams']['away']['name'], logo=logo_map[game['teams']['away']['name']] + ), + result=base.determine_apisports_game_status( + game, + base.Team( + logo.name, + logo=logo + ), + api_data['sport'] + )[1], + homeoraway="home" if logo.name == game['teams']['home']['name'] else "away", + score=base.Score( + team=game['scores']['home'] if logo.name == game['teams']['home']['name'] else game['scores']['away'], + opposing_team=game['scores']['home'] if logo.name != game['teams']['home']['name'] else game['scores']['away'], + ), + + ) for game in api_data['next_game']['response'] + ] + standings=[ + base.Team( + name=team['team']['name'], + logo=logo_map[team['team']['name']], + position=team['position'], + league=team['group']['name'] + ) for team in api_data['standings']['response'][0] + ] + result = base.ModuleResult( + name=logo.name, + team=base.Team( + name=logo.name, + logo=logo, + ), + schedule=schedule, + standings = standings, + sport=base.SportStructure.Basketball, + games_played=len([game for game in schedule if game.status == base.GameStatus.Finished or game.status == base.GameStatus.Overtime]), + wins=len([game for game in schedule if game.result == base.GameResult.WIN]), + losses=len([game for game in schedule if game.result == base.GameResult.LOSS]), + ) + return SportApiResult(result) \ No newline at end of file diff --git a/lib/sports/apisports/hockey/hockey.py b/lib/sports/apisports/hockey/hockey.py index a5b9cad..62dd0a6 100755 --- a/lib/sports/apisports/hockey/hockey.py +++ b/lib/sports/apisports/hockey/hockey.py @@ -1,6 +1,10 @@ from lib.run import Runner from datetime import datetime from lib.sports.apisports.result import SportApiResult +from lib.sports.logo import logo_map, Logo +from datetime import datetime +import lib.sports.sportbase as base + class Hockey(Runner): def __init__(self, token, config, headers): @@ -20,21 +24,81 @@ def parse_args(self): args = ('standings', 'next_game') return args - def url_builder(self, args): + def url_builder(self, args, logo): self.headers.update({'x-apisports-host': 'v1.hockey.api-sports.io'}) urls = {} base = "https://v1.hockey.api-sports.io/" if 'standings' in args: - urls.update({'standings': base + f'standings?league=57&season=2020'}) + urls.update({'standings': base + f'standings?league=57&season=2021'}) if 'next_game' in args: - urls.update({'next_game': base + f"games?team={self.config.getint('team_id')}&league=57&season=2020&timezone=America/Chicago"}) + urls.update({'next_game': base + f"games?team={logo.apisportsid}&league=57&season=2021&timezone=America/Chicago"}) return urls async def run(self) -> SportApiResult: self.logger.info('Running Hockey API') parsed = self.parse_args() api_data = {} - for section, url in self.url_builder(parsed).items(): + logo = logo_map[self.config.get('team_id')] + for section, url in self.url_builder(parsed, logo).items(): api_data.update({section: await self.get_data(url, self.headers)}) api_data['sport'] = 'hockey' - return SportApiResult(api_data) \ No newline at end of file + schedule = [ + base.Game( + team=base.Team( + name=logo.name, + logo=logo, + ), + timestamp=datetime.fromtimestamp(game['timestamp']), + status=base.determine_apisports_game_status( + game, + base.Team( + logo.name, + logo=logo + ), + api_data['sport'] + )[0], + opposing_team=base.Team( + name=game['teams']['home']['name'], + logo=logo_map[game['teams']['home']['name']], + ) if logo.name != game['teams']['home']['name'] else base.Team( + name=game['teams']['away']['name'], logo=logo_map[game['teams']['away']['name']] + ), + result=base.determine_apisports_game_status( + game, + base.Team( + logo.name, + logo=logo + ), + api_data['sport'] + )[1], + homeoraway="home" if logo.name == game['teams']['home']['name'] else "away", + score=base.Score( + team=game['scores']['home'] if logo.name == game['teams']['home']['name'] else game['scores']['away'], + opposing_team=game['scores']['home'] if logo.name != game['teams']['home']['name'] else game['scores']['away'], + ), + + ) for game in api_data['next_game']['response'] + ] + standings=[ + base.Team( + name=team['team']['name'], + logo=logo_map[team['team']['name']], + position=team['position'], + league=team['group']['name'] + ) + for team in api_data['standings']['response'][0] + api_data['standings']['response'][1] + ] + result = base.ModuleResult( + name=logo.name, + team=base.Team( + name=logo.name, + logo=logo, + ), + schedule=schedule, + standings = standings, + sport=base.SportStructure.Hockey, + games_played=len([game for game in schedule if game.status == base.GameStatus.Finished or game.status == base.GameStatus.Overtime]), + wins=len([game for game in schedule if game.result == base.GameResult.WIN]), + losses=len([game for game in schedule if game.result == base.GameResult.LOSS]), + ) + return SportApiResult(result) \ No newline at end of file diff --git a/lib/sports/apisports/result.py b/lib/sports/apisports/result.py index 97c771c..cb07d09 100644 --- a/lib/sports/apisports/result.py +++ b/lib/sports/apisports/result.py @@ -1,116 +1,75 @@ -from lib.sports.sportbase import SportResultBase - import json +from asyncio import Task +from typing import Dict, List, Tuple +from datetime import datetime +from enum import Enum +from sportsipy.nhl.schedule import Game +from sportsipy.nhl.teams import Team as nhl_team +from lib.sports.sportbase import SportResultBase, API +from lib.sports.logo import Logo, logo_map +import lib.sports.sportbase as base class SportApiResult(SportResultBase): - def __init__(self, api) -> None: + def __init__(self, api_result) -> None: super().__init__() - self.api_type = "" - self.api = api - self.main_sport = api - self._sport = api['sport'] - if len(self.main_sport['standings']['errors']) != 0: - self._error = self.set_error() + self.api_result = api_result + self._get_sport: Enum = api_result.sport + self._team: base.Team = api_result.team + self._schedule: base.SportStandings = base.SportStandings( + positions=api_result.schedule + ) + + self._api: Enum = API.APISPORTS + self._standings: List[base.Team] = api_result.standings + self._position = self._team.position + self._get_leagues = None + self._games_played: List[Game] = self._schedule.positions[:api_result.games_played] if api_result.games_played <= len(self._schedule.positions) else [] + self._get_wins: List[Game] = [game for game in self._games_played if base.GameResult.WIN == game.result] + self._win_percentage: float = api_result.wins/len(self._games_played) + self._losses: List[Game] = [game for game in self._games_played if base.GameResult.LOSS == game.result] + self._loss_percentage: float = api_result.losses/len(self._games_played) + if len(self._games_played) == len(self._schedule.positions): + self._next_game = [] else: - self._error = self.set_error() - self.standings = self.build_standings() - self._length = len(self.standings) - self._positions = [(team.get('name'), team.get('position')) for team in self.standings] - self._leagues = [(team.get('name'), team.get('league')) for team in self.standings] - self._games_played = [(team.get('name'), team.get('games').get('played')) for team in self.standings] - self._wins = [(team.get('name'), team['games']['win']['total']) for team in self.standings] - self._wins_percentage = [(team.get('name'), team['games']['win']['percentage']) for team in self.standings] - self._losses = [(team.get('name'), team['games']['lose']['total']) for team in self.standings] - self._loss_percentage = [(team.get('name'), team['games']['lose']['percentage']) for team in self.standings] - self.next_game = self.build_nextgame() - self._game_ids = [game.get('game_id') for game in self.next_game] - self._timestamps = [(game.get('game_id'), game.get('timestamp')) for game in self.next_game] - self._teams = [(game.get('game_id'), game.get('teams')) for game in self.next_game] - self._vs = [(game.get('game_id'), (game['teams']['home']['name'], game['teams']['away']['name'])) for game in self.next_game] - self._status = [(game.get('game_id'), game.get('status')) for game in self.next_game] - self._game_result = {game.get('game_id'): game.get('score') for game in self.next_game} - - def __repr__(self): - attrs = [ - f"length={self._length}", - f"positions={json.dumps(self._positions, indent=2)}", - f'leagues={json.dumps(self._leagues, indent=2)}', - f"games_played={json.dumps(self._games_played, indent=2)}", - f"wins={json.dumps(self._wins, indent=2)}", - f"wins_percentage={json.dumps(self._wins_percentage, indent=2)}", - f"losses={json.dumps(self._losses, indent=2)}", - f"loss_percentage={json.dumps(self._loss_percentage, indent=2)}", - f"game_ids={json.dumps(self._game_ids, indent=2)}", - f"timestamps={json.dumps(self._timestamps, indent=2)}", - f"teams={json.dumps(self._teams, indent=2)}", - f"vs={json.dumps(self._vs, indent=2)}", - f"status={json.dumps(self._status, indent=2)}", - f"game_result={json.dumps(self._game_result, indent=2)}" - ] - joined = "\t\n".join(attrs) - return f"Sport(\n{joined})" + self._next_game = self._schedule.positions[len(self._games_played)] if len(self._games_played) < len(self._schedule.positions) else [] + self._game_ids = None - def build_standings(self): - #counter = 0 - position = [] - regular_season_check = ( - "MLB - Regular Season", - "NBA - Regular Season", - "NHL - Regular Season", - "NFL - Regular Season" - ) - # Can Be Empty Must try and except for that - for pos in self.main_sport['standings'].get('response')[0]: - if not pos.get('stage') in regular_season_check: - continue - position.append({'name': pos.get('team').get('name'), - 'position': pos.get('position'), - 'league': pos.get('group').get('name'), - 'games': pos.get('games') - }) - return position - def build_nextgame(self): - main = [] - for game in self.main_sport['next_game'].get('response'): - main.append({ - 'game_id': game.get('id'), - 'timestamp': game.get('timestamp'), - 'status': game['status']['short'], - 'teams': game['teams'], - 'score': game['scores'] - }) - return main + @property + def get_api(self) -> Enum: + return self._api - def set_error(self): - if isinstance(self.main_sport['standings']['errors'], list): - return True, "" - else: - return False, self.main_sport['standings']['errors']['requests'] + @property + def get_sport(self) -> Enum: + return self._get_sport + + @property + def team_name(self): + return self._team.name @property - def get_sport(self): - return self._sport + def get_logo(self) -> Logo: + return logo_map[self._team.name] @property - def get_error(self): - return self._error - + def get_team(self): + return self._team + @property def get_length_position_teams(self): - return len(self.standings) + return len(self._standings) @property def get_standings(self): - return self.standings + return self._standings - @property - def get_position_teams(self): - return self._positions + @property + def get_schedule(self): + return self._schedule @property def get_leagues(self): - return self._leagues + return self._get_leagues @property def get_games_played(self): @@ -118,11 +77,11 @@ def get_games_played(self): @property def get_wins(self): - return self._wins + return self._get_wins @property def get_wins_percentage(self): - return self._wins_percentage + return self._win_percentage @property def get_losses(self): @@ -136,26 +95,9 @@ def get_loss_percentage(self): def get_game_ids(self): return self._game_ids - @property - def get_timestamps(self): - return self._timestamps - - @property - def get_teams(self): - return self._teams - - @property - def get_versus(self): - return self._vs - - @property - def get_status(self): - return self._status - - @property - def get_scores(self): - return self._game_result - def get_specific_score(self, game_id): return self._game_result.get(game_id) + @property + def get_next_game(self): + return self._next_game \ No newline at end of file diff --git a/lib/sports/logo.py b/lib/sports/logo.py new file mode 100644 index 0000000..c6f89c9 --- /dev/null +++ b/lib/sports/logo.py @@ -0,0 +1,171 @@ +from dataclasses import dataclass +from typing import Tuple + +@dataclass(repr=True) +class Logo: + name: str + sportsdb_leagueid: int + url: str + sport: str + shorthand: str + apisportsid: int + sportsdbid: int + sportsipyid: int + +baseball_teams = { + 'SFG': 'San Francisco Giants', + 'LAD': 'Los Angeles Dodgers', + 'TBR': 'Tampa Bay Rays', + 'HOU': 'Houston Astros', + 'MIL': 'Milwaukee Brewers', + 'CHW': 'Chicago White Sox', + 'BOS': 'Boston Red Sox', + 'NYY': 'New York Yankees', + 'TOR': 'Toronto Blue Jays', + 'STL': 'St. Louis Cardinals', + 'SEA': 'Seattle Mariners', + 'ATL': 'Atlanta Braves', + 'OAK': 'Oakland Athletics', + 'CIN': 'Cincinnati Reds', + 'PHI': 'Philadelphia Phillies', + 'CLE': 'Cleveland Indians', + 'SDP': 'San Diego Padres', + 'DET': 'Detroit Tigers', + 'NYM': 'New York Mets', + 'LAA': 'Los Angeles Angels', + 'COL': 'Colorado Rockies', + 'KCR': 'Kansas City Royals', + 'MIN': 'Minnesota Twins', + 'CHC': 'Chicago Cubs', + 'MIA': 'Miami Marlins', + 'WSN': 'Washington Nationals', + 'PIT': 'Pittsburgh Pirates', + 'TEX': 'Texas Rangers', + 'ARI': 'Arizona Diamondbacks', + 'BAL': 'Baltimore Orioles' +} +logo_map = {'Arizona Diamondbacks': Logo(name='Arizona Diamondbacks', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/sutyqp1431251804.png', sport='Baseball', shorthand='ARI', apisportsid=2, sportsdbid=135267, sportsipyid=None), + 'Atlanta Braves': Logo(name='Atlanta Braves', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/yjs76e1617811496.png', sport='Baseball', shorthand='ATL', apisportsid=3, sportsdbid=135268, sportsipyid=None), + 'Baltimore Orioles': Logo(name='Baltimore Orioles', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/ytywvu1431257088.png', sport='Baseball', shorthand='BAL', apisportsid=4, sportsdbid=135251, sportsipyid=None), + 'Boston Red Sox': Logo(name='Boston Red Sox', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/stpsus1425120215.png', sport='Baseball', shorthand='BOS', apisportsid=5, sportsdbid=135252, sportsipyid=None), + 'Chicago Cubs': Logo(name='Chicago Cubs', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/wxbe071521892391.png', sport='Baseball', shorthand='CHC', apisportsid=6, sportsdbid=135269, sportsipyid=None), + 'Chicago White Sox': Logo(name='Chicago White Sox', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/yyz5dh1554140884.png', sport='Baseball', shorthand='CWS', apisportsid=7, sportsdbid=135253, sportsipyid=None), + 'Cincinnati Reds': Logo(name='Cincinnati Reds', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/wspusr1431538832.png', sport='Baseball', shorthand='CIN', apisportsid=8, sportsdbid=135270, sportsipyid=None), + 'Cleveland Indians': Logo(name='Cleveland Indians', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/fp39hu1521904440.png', sport='Baseball', shorthand='CLE', apisportsid=9, sportsdbid=135254, sportsipyid=None), + 'Colorado Rockies': Logo(name='Colorado Rockies', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/wvbk1d1550584627.png', sport='Baseball', shorthand='COL', apisportsid=10, sportsdbid=135271, sportsipyid=None), + 'Detroit Tigers': Logo(name='Detroit Tigers', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/9dib6o1554032173.png', sport='Baseball', shorthand='DET', apisportsid=12, sportsdbid=135255, sportsipyid=None), + 'Houston Astros': Logo(name='Houston Astros', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/miwigx1521893583.png', sport='Baseball', shorthand='HOU', apisportsid=15, sportsdbid=135256, sportsipyid=None), + 'Kansas City Royals': Logo(name='Kansas City Royals', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/ii3rz81554031260.png', sport='Baseball', shorthand='KC', apisportsid=16, sportsdbid=135257, sportsipyid=None), + 'Los Angeles Angels': Logo(name='Los Angeles Angels', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/vswsvx1432577476.png', sport='Baseball', shorthand='LAA', apisportsid=17, sportsdbid=135258, sportsipyid=None), + 'Los Angeles Dodgers': Logo(name='Los Angeles Dodgers', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/rrdfmw1617528853.png', sport='Baseball', shorthand='LAD', apisportsid=18, sportsdbid=135272, sportsipyid=None), + 'Miami Marlins': Logo(name='Miami Marlins', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/0722fs1546001701.png', sport='Baseball', shorthand='MIA', apisportsid=19, sportsdbid=135273, sportsipyid=None), + 'Milwaukee Brewers': Logo(name='Milwaukee Brewers', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/08kh2a1595775193.png', sport='Baseball', shorthand='MIL', apisportsid=20, sportsdbid=135274, sportsipyid=None), + 'Minnesota Twins': Logo(name='Minnesota Twins', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/necd5v1521905719.png', sport='Baseball', shorthand='MIN', apisportsid=22, sportsdbid=135259, sportsipyid=None), + 'New York Mets': Logo(name='New York Mets', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/rxqspq1431540337.png', sport='Baseball', shorthand='NYM', apisportsid=24, sportsdbid=135275, sportsipyid=None), + 'New York Yankees': Logo(name='New York Yankees', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/wqwwxx1423478766.png', sport='Baseball', shorthand='NYY', apisportsid=25, sportsdbid=135260, sportsipyid=None), + 'Oakland Athletics': Logo(name='Oakland Athletics', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/wsxtyw1432577334.png', sport='Baseball', shorthand='OAK', apisportsid=26, sportsdbid=135261, sportsipyid=None), + 'Philadelphia Phillies': Logo(name='Philadelphia Phillies', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/3xrldf1617528682.png', sport='Baseball', shorthand='PHI', apisportsid=27, sportsdbid=135276, sportsipyid=None), + 'Pittsburgh Pirates': Logo(name='Pittsburgh Pirates', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/kw6uqr1617527138.png', sport='Baseball', shorthand='PIT', apisportsid=28, sportsdbid=135277, sportsipyid=None), + 'San Diego Padres': Logo(name='San Diego Padres', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/6wt1cn1617527530.png', sport='Baseball', shorthand='SD', apisportsid=30, sportsdbid=135278, sportsipyid=None), + 'San Francisco Giants': Logo(name='San Francisco Giants', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/mq81yb1521896622.png', sport='Baseball', shorthand='SF', apisportsid=31, sportsdbid=135279, sportsipyid=None), + 'Seattle Mariners': Logo(name='Seattle Mariners', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/39x9ph1521903933.png', sport='Baseball', shorthand='SEA', apisportsid=32, sportsdbid=135262, sportsipyid=None), + 'St. Louis Cardinals': Logo(name='St. Louis Cardinals', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/uvyvyr1424003273.png', sport='Baseball', shorthand='STL', apisportsid=33, sportsdbid=135280, sportsipyid=None), + 'St.Louis Cardinals': Logo(name='St. Louis Cardinals', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/uvyvyr1424003273.png', sport='Baseball', shorthand='STL', apisportsid=33, sportsdbid=135280, sportsipyid=None), + 'Tampa Bay Rays': Logo(name='Tampa Bay Rays', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/littyt1554031623.png', sport='Baseball', shorthand='TB', apisportsid=34, sportsdbid=135263, sportsipyid=None), + 'Texas Rangers': Logo(name='Texas Rangers', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/qt9qki1521893151.png', sport='Baseball', shorthand='TEX', apisportsid=35, sportsdbid=135264, sportsipyid=None), + 'Toronto Blue Jays': Logo(name='Toronto Blue Jays', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/f9zk3l1617527686.png', sport='Baseball', shorthand='TOR', apisportsid=36, sportsdbid=135265, sportsipyid=None), + 'Washington Nationals': Logo(name='Washington Nationals', sportsdb_leagueid=4424, url='https://www.thesportsdb.com/images/media/team/badge/wpqrut1423694764.png', sport='Baseball', shorthand='WAS', apisportsid=37, sportsdbid=135281, sportsipyid=None), + 'Atlanta Hawks': Logo(name='Atlanta Hawks', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/q3bx641635067495.png', sport='Basketball', shorthand='ATL', apisportsid=132, sportsdbid=134880, sportsipyid=None), + 'Boston Celtics': Logo(name='Boston Celtics', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/051sjd1537102179.png', sport='Basketball', shorthand='BOS', apisportsid=133, sportsdbid=134860, sportsipyid=None), + 'Brooklyn Nets': Logo(name='Brooklyn Nets', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/h0dwny1600552068.png', sport='Basketball', shorthand='BKN', apisportsid=134, sportsdbid=134861, sportsipyid=None), + 'Charlotte Hornets': Logo(name='Charlotte Hornets', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/xqtvvp1422380623.png', sport='Basketball', shorthand='CHA', apisportsid=135, sportsdbid=134881, sportsipyid=None), + 'Chicago Bulls': Logo(name='Chicago Bulls', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/yk7swg1547214677.png', sport='Basketball', shorthand='CHI', apisportsid=136, sportsdbid=134870, sportsipyid=None), + 'Cleveland Cavaliers': Logo(name='Cleveland Cavaliers', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/a2pp4c1503741152.png', sport='Basketball', shorthand='CLE', apisportsid=137, sportsdbid=134871, sportsipyid=None), + 'Dallas Mavericks': Logo(name='Dallas Mavericks', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/yqrxrs1420568796.png', sport='Basketball', shorthand='DAL', apisportsid=138, sportsdbid=134875, sportsipyid=None), + 'Denver Nuggets': Logo(name='Denver Nuggets', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/8o8j5k1546016274.png', sport='Basketball', shorthand='DEN', apisportsid=139, sportsdbid=134885, sportsipyid=None), + 'Detroit Pistons': Logo(name='Detroit Pistons', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/lg7qrc1621594751.png', sport='Basketball', shorthand='DET', apisportsid=140, sportsdbid=134872, sportsipyid=None), + 'Golden State Warriors': Logo(name='Golden State Warriors', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/irobi61565197527.png', sport='Basketball', shorthand='GSW', apisportsid=141, sportsdbid=134865, sportsipyid=None), + 'Houston Rockets': Logo(name='Houston Rockets', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/yezpho1597486052.png', sport='Basketball', shorthand='HOU', apisportsid=142, sportsdbid=134876, sportsipyid=None), + 'Indiana Pacers': Logo(name='Indiana Pacers', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/v6jzgm1503741821.png', sport='Basketball', shorthand='IND', apisportsid=143, sportsdbid=134873, sportsipyid=None), + 'Los Angeles Clippers': Logo(name='Los Angeles Clippers', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/jv7tf21545916958.png', sport='Basketball', shorthand='LAC', apisportsid=144, sportsdbid=134866, sportsipyid=None), + 'Los Angeles Lakers': Logo(name='Los Angeles Lakers', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/spa6c11621594682.png', sport='Basketball', shorthand='LAL', apisportsid=145, sportsdbid=134867, sportsipyid=None), + 'Memphis Grizzlies': Logo(name='Memphis Grizzlies', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/m64v461565196789.png', sport='Basketball', shorthand='MEM', apisportsid=146, sportsdbid=134877, sportsipyid=None), + 'Miami Heat': Logo(name='Miami Heat', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/5v67x51547214763.png', sport='Basketball', shorthand='MIA', apisportsid=147, sportsdbid=134882, sportsipyid=None), + 'Milwaukee Bucks': Logo(name='Milwaukee Bucks', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/olhug01621594702.png', sport='Basketball', shorthand='MIL', apisportsid=148, sportsdbid=134874, sportsipyid=None), + 'Minnesota Timberwolves': Logo(name='Minnesota Timberwolves', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/5xpgjg1621594771.png', sport='Basketball', shorthand='MIN', apisportsid=149, sportsdbid=134886, sportsipyid=None), + 'New Orleans Pelicans': Logo(name='New Orleans Pelicans', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/f341s31523700397.png', sport='Basketball', shorthand='NOP', apisportsid=150, sportsdbid=134878, sportsipyid=None), + 'New York Knicks': Logo(name='New York Knicks', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/wyhpuf1511810435.png', sport='Basketball', shorthand='NYK', apisportsid=151, sportsdbid=134862, sportsipyid=None), + 'Oklahoma City Thunder': Logo(name='Oklahoma City Thunder', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/xpswpq1422575434.png', sport='Basketball', shorthand='OKC', apisportsid=152, sportsdbid=134887, sportsipyid=None), + 'Orlando Magic': Logo(name='Orlando Magic', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/txuyrr1422492990.png', sport='Basketball', shorthand='ORL', apisportsid=153, sportsdbid=134883, sportsipyid=None), + 'Philadelphia 76ers': Logo(name='Philadelphia 76ers', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/71545f1518464849.png', sport='Basketball', shorthand='PHI', apisportsid=154, sportsdbid=134863, sportsipyid=None), + 'Phoenix Suns': Logo(name='Phoenix Suns', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/qrtuxq1422919040.png', sport='Basketball', shorthand='PHX', apisportsid=155, sportsdbid=134868, sportsipyid=None), + 'Portland Trail Blazers': Logo(name='Portland Trail Blazers', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/mbtzin1520794112.png', sport='Basketball', shorthand='POR', apisportsid=156, sportsdbid=134888, sportsipyid=None), + 'Sacramento Kings': Logo(name='Sacramento Kings', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/5d3dpz1611859587.png', sport='Basketball', shorthand='SAC', apisportsid=157, sportsdbid=134869, sportsipyid=None), + 'San Antonio Spurs': Logo(name='San Antonio Spurs', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/obucan1611859537.png', sport='Basketball', shorthand='SAS', apisportsid=158, sportsdbid=134879, sportsipyid=None), + 'Toronto Raptors': Logo(name='Toronto Raptors', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/ax36vz1635070057.png', sport='Basketball', shorthand='TOR', apisportsid=159, sportsdbid=134864, sportsipyid=None), + 'Utah Jazz': Logo(name='Utah Jazz', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/9p1e5j1572041084.png', sport='Basketball', shorthand='UTA', apisportsid=160, sportsdbid=134889, sportsipyid=None), + 'Washington Wizards': Logo(name='Washington Wizards', sportsdb_leagueid=4387, url='https://www.thesportsdb.com/images/media/team/badge/rhxi9w1621594729.png', sport='Basketball', shorthand='WAS', apisportsid=161, sportsdbid=134884, sportsipyid=None), + 'Arizona Cardinals': Logo(name='Arizona Cardinals', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/xvuwtw1420646838.png', sport='American Football', shorthand='ARI', apisportsid=0, sportsdbid=134946, sportsipyid=None), + 'Atlanta Falcons': Logo(name='Atlanta Falcons', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/rrpvpr1420658174.png', sport='American Football', shorthand='ATL', apisportsid=0, sportsdbid=134942, sportsipyid=None), + 'Baltimore Ravens': Logo(name='Baltimore Ravens', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/einz3p1546172463.png', sport='American Football', shorthand='BAL', apisportsid=0, sportsdbid=134922, sportsipyid=None), + 'Buffalo Bills': Logo(name='Buffalo Bills', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/6pb37b1515849026.png', sport='American Football', shorthand='BUF', apisportsid=0, sportsdbid=134918, sportsipyid=None), + 'Carolina Panthers': Logo(name='Carolina Panthers', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/xxyvvy1420940478.png', sport='American Football', shorthand='CAR', apisportsid=0, sportsdbid=134943, sportsipyid=None), + 'Chicago Bears': Logo(name='Chicago Bears', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/uwtwtv1420941123.png', sport='American Football', shorthand='CHI', apisportsid=0, sportsdbid=134938, sportsipyid=None), + 'Cincinnati Bengals': Logo(name='Cincinnati Bengals', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/qqtwwv1420941670.png', sport='American Football', shorthand='CIN', apisportsid=0, sportsdbid=134923, sportsipyid=None), + 'Cleveland Browns': Logo(name='Cleveland Browns', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/squvxy1420942389.png', sport='American Football', shorthand='CLE', apisportsid=0, sportsdbid=134924, sportsipyid=None), + 'Dallas Cowboys': Logo(name='Dallas Cowboys', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/wrxssu1450018209.png', sport='American Football', shorthand='DAL', apisportsid=0, sportsdbid=134934, sportsipyid=None), + 'Denver Broncos': Logo(name='Denver Broncos', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/upsspx1421635647.png', sport='American Football', shorthand='DEN', apisportsid=0, sportsdbid=134930, sportsipyid=None), + 'Detroit Lions': Logo(name='Detroit Lions', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/lgsgkr1546168257.png', sport='American Football', shorthand='DET', apisportsid=0, sportsdbid=134939, sportsipyid=None), + 'Green Bay Packers': Logo(name='Green Bay Packers', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/rqpwtr1421434717.png', sport='American Football', shorthand='GB', apisportsid=0, sportsdbid=134940, sportsipyid=None), + 'Houston Texans': Logo(name='Houston Texans', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/wqyryy1421436627.png', sport='American Football', shorthand='HOU', apisportsid=0, sportsdbid=134926, sportsipyid=None), + 'Indianapolis Colts': Logo(name='Indianapolis Colts', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/wqqvpx1421434058.png', sport='American Football', shorthand='IND', apisportsid=0, sportsdbid=134927, sportsipyid=None), + 'Jacksonville Jaguars': Logo(name='Jacksonville Jaguars', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/0mrsd41546427902.png', sport='American Football', shorthand='JAX', apisportsid=0, sportsdbid=134928, sportsipyid=None), + 'Kansas City Chiefs': Logo(name='Kansas City Chiefs', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/936t161515847222.png', sport='American Football', shorthand='KC', apisportsid=0, sportsdbid=134931, sportsipyid=None), + 'Las Vegas Raiders': Logo(name='Las Vegas Raiders', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/xqusqy1421724291.png', sport='American Football', shorthand='OAK', apisportsid=0, sportsdbid=134932, sportsipyid=None), + 'Los Angeles Chargers': Logo(name='Los Angeles Chargers', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/wbhu3a1548320628.png', sport='American Football', shorthand='LAC', apisportsid=None, sportsdbid=135908, sportsipyid=None), + 'Los Angeles Rams': Logo(name='Los Angeles Rams', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/8e8v4i1599764614.png', sport='American Football', shorthand='LA', apisportsid=None, sportsdbid=135907, sportsipyid=None), + 'Miami Dolphins': Logo(name='Miami Dolphins', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/trtusv1421435081.png', sport='American Football', shorthand='MIA', apisportsid=0, sportsdbid=134919, sportsipyid=None), + 'Minnesota Vikings': Logo(name='Minnesota Vikings', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/qstqqr1421609163.png', sport='American Football', shorthand='MIN', apisportsid=0, sportsdbid=134941, sportsipyid=None), + 'New England Patriots': Logo(name='New England Patriots', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/xtwxyt1421431860.png', sport='American Football', shorthand='NE', apisportsid=0, sportsdbid=134920, sportsipyid=None), + 'New Orleans Saints': Logo(name='New Orleans Saints', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/nd46c71537821337.png', sport='American Football', shorthand='NO', apisportsid=0, sportsdbid=134944, sportsipyid=None), + 'New York Giants': Logo(name='New York Giants', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/vxppup1423669459.png', sport='American Football', shorthand='NYG', apisportsid=0, sportsdbid=134935, sportsipyid=None), + 'New York Jets': Logo(name='New York Jets', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/hz92od1607953467.png', sport='American Football', shorthand='NYJ', apisportsid=0, sportsdbid=134921, sportsipyid=None), + 'Philadelphia Eagles': Logo(name='Philadelphia Eagles', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/pnpybf1515852421.png', sport='American Football', shorthand='PHI', apisportsid=0, sportsdbid=134936, sportsipyid=None), + 'Pittsburgh Steelers': Logo(name='Pittsburgh Steelers', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/2975411515853129.png', sport='American Football', shorthand='PIT', apisportsid=0, sportsdbid=134925, sportsipyid=None), + 'San Francisco 49ers': Logo(name='San Francisco 49ers', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/bqbtg61539537328.png', sport='American Football', shorthand='SF', apisportsid=0, sportsdbid=134948, sportsipyid=None), + 'Seattle Seahawks': Logo(name='Seattle Seahawks', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/wwuqyr1421434817.png', sport='American Football', shorthand='SEA', apisportsid=0, sportsdbid=134949, sportsipyid=None), + 'Tampa Bay Buccaneers': Logo(name='Tampa Bay Buccaneers', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/2dfpdl1537820969.png', sport='American Football', shorthand='TB', apisportsid=0, sportsdbid=134945, sportsipyid=None), + 'Tennessee Titans': Logo(name='Tennessee Titans', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/m48yia1515847376.png', sport='American Football', shorthand='TEN', apisportsid=0, sportsdbid=134929, sportsipyid=None), + 'Washington Football Team': Logo(name='Washington', sportsdb_leagueid=4391, url='https://www.thesportsdb.com/images/media/team/badge/1m3mzp1595609069.png', sport='American Football', shorthand='WAS', apisportsid=0, sportsdbid=134937, sportsipyid=None), + 'Anaheim Ducks': Logo(name='Anaheim Ducks', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/6g9t721547289240.png', sport='Ice Hockey', shorthand='ANA', apisportsid=670, sportsdbid=134846, sportsipyid=None), + 'Arizona Coyotes': Logo(name='Arizona Coyotes', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/3n1yqw1635072720.png', sport='Ice Hockey', shorthand='ARI', apisportsid=1460, sportsdbid=134847, sportsipyid=None), + 'Boston Bruins': Logo(name='Boston Bruins', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/vuspuq1421791546.png', sport='Ice Hockey', shorthand='BOS', apisportsid=673, sportsdbid=134830, sportsipyid=None), + 'Buffalo Sabres': Logo(name='Buffalo Sabres', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/3m3jhp1619536655.png', sport='Ice Hockey', shorthand='BUF', apisportsid=674, sportsdbid=134831, sportsipyid=None), + 'Calgary Flames': Logo(name='Calgary Flames', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/v8vkk11619536610.png', sport='Ice Hockey', shorthand='CGY', apisportsid=675, sportsdbid=134848, sportsipyid=None), + 'Carolina Hurricanes': Logo(name='Carolina Hurricanes', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/v07m3x1547232585.png', sport='Ice Hockey', shorthand='CAR', apisportsid=676, sportsdbid=134838, sportsipyid=None), + 'Chicago Blackhawks': Logo(name='Chicago Blackhawks', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/tuwyvr1422041801.png', sport='Ice Hockey', shorthand='CHI', apisportsid=678, sportsdbid=134854, sportsipyid=None), + 'Colorado Avalanche': Logo(name='Colorado Avalanche', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/wqutut1421173572.png', sport='Ice Hockey', shorthand='COL', apisportsid=679, sportsdbid=134855, sportsipyid=None), + 'Columbus Blue Jackets': Logo(name='Columbus Blue Jackets', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/ssytwt1421792535.png', sport='Ice Hockey', shorthand='CBJ', apisportsid=680, sportsdbid=134839, sportsipyid=None), + 'Dallas Stars': Logo(name='Dallas Stars', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/qrvywq1422042125.png', sport='Ice Hockey', shorthand='DAL', apisportsid=681, sportsdbid=134856, sportsipyid=None), + 'Detroit Red Wings': Logo(name='Detroit Red Wings', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/1c24ow1546544080.png', sport='Ice Hockey', shorthand='DET', apisportsid=682, sportsdbid=134832, sportsipyid=None), + 'Edmonton Oilers': Logo(name='Edmonton Oilers', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/uxxsyw1421618428.png', sport='Ice Hockey', shorthand='EDM', apisportsid=683, sportsdbid=134849, sportsipyid=None), + 'Florida Panthers': Logo(name='Florida Panthers', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/8qtaz11547158220.png', sport='Ice Hockey', shorthand='FLA', apisportsid=684, sportsdbid=134833, sportsipyid=None), + 'Los Angeles Kings': Logo(name='Los Angeles Kings', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/uvwtvx1421535024.png', sport='Ice Hockey', shorthand='LAK', apisportsid=685, sportsdbid=134852, sportsipyid=None), + 'Minnesota Wild': Logo(name='Minnesota Wild', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/swtsxs1422042685.png', sport='Ice Hockey', shorthand='MIN', apisportsid=687, sportsdbid=134857, sportsipyid=None), + 'Montreal Canadiens': Logo(name='Montreal Canadiens', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/stpryx1421791753.png', sport='Ice Hockey', shorthand='MTL', apisportsid=688, sportsdbid=134834, sportsipyid=None), + 'Nashville Predators': Logo(name='Nashville Predators', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/twqyvy1422052908.png', sport='Ice Hockey', shorthand='NSH', apisportsid=689, sportsdbid=134858, sportsipyid=None), + 'New Jersey Devils': Logo(name='New Jersey Devils', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/z4rsvp1619536740.png', sport='Ice Hockey', shorthand='NJD', apisportsid=690, sportsdbid=134840, sportsipyid=None), + 'New York Islanders': Logo(name='New York Islanders', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/hqn8511619536714.png', sport='Ice Hockey', shorthand='NYI', apisportsid=691, sportsdbid=134841, sportsipyid=None), + 'New York Rangers': Logo(name='New York Rangers', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/bez4251546192693.png', sport='Ice Hockey', shorthand='NYR', apisportsid=692, sportsdbid=134842, sportsipyid=None), + 'Ottawa Senators': Logo(name='Ottawa Senators', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/2tc1qy1619536592.png', sport='Ice Hockey', shorthand='OTT', apisportsid=693, sportsdbid=134835, sportsipyid=None), + 'Philadelphia Flyers': Logo(name='Philadelphia Flyers', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/qxxppp1421794965.png', sport='Ice Hockey', shorthand='PHI', apisportsid=695, sportsdbid=134843, sportsipyid=None), + 'Pittsburgh Penguins': Logo(name='Pittsburgh Penguins', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/dsj3on1546192477.png', sport='Ice Hockey', shorthand='PIT', apisportsid=696, sportsdbid=134844, sportsipyid=None), + 'San Jose Sharks': Logo(name='San Jose Sharks', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/yui7871546193006.png', sport='Ice Hockey', shorthand='SJS', apisportsid=697, sportsdbid=134853, sportsipyid=None), + 'Seattle Kraken': Logo(name='Seattle Kraken', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/zsx49m1595775836.png', sport='Ice Hockey', shorthand=None, apisportsid=1436, sportsdbid=140082, sportsipyid=None), + 'St. Louis Blues': Logo(name='St. Louis Blues', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/rsqtwx1422053715.png', sport='Ice Hockey', shorthand='STL', apisportsid=698, sportsdbid=134859, sportsipyid=None), + 'Tampa Bay Lightning': Logo(name='Tampa Bay Lightning', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/swysut1421791822.png', sport='Ice Hockey', shorthand='TBL', apisportsid=699, sportsdbid=134836, sportsipyid=None), + 'Toronto Maple Leafs': Logo(name='Toronto Maple Leafs', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/mxig4p1570129307.png', sport='Ice Hockey', shorthand='TOR', apisportsid=700, sportsdbid=134837, sportsipyid=None), + 'Vancouver Canucks': Logo(name='Vancouver Canucks', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/xqxxpw1421875519.png', sport='Ice Hockey', shorthand='VAN', apisportsid=701, sportsdbid=134850, sportsipyid=None), + 'Vegas Golden Knights': Logo(name='Vegas Golden Knights', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/7fd4521619536689.png', sport='Ice Hockey', shorthand='VGK', apisportsid=702, sportsdbid=135913, sportsipyid=None), + 'Washington Capitals': Logo(name='Washington Capitals', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/u17iel1547157581.png', sport='Ice Hockey', shorthand='WSH', apisportsid=703, sportsdbid=134845, sportsipyid=None), + 'Winnipeg Jets': Logo(name='Winnipeg Jets', sportsdb_leagueid=4380, url='https://www.thesportsdb.com/images/media/team/badge/bwn9hr1547233611.png', sport='Ice Hockey', shorthand='WPG', apisportsid=704, sportsdbid=134851, sportsipyid=None)} \ No newline at end of file diff --git a/lib/sports/sportbase.py b/lib/sports/sportbase.py index d476b20..42093d3 100644 --- a/lib/sports/sportbase.py +++ b/lib/sports/sportbase.py @@ -1,6 +1,257 @@ +from dataclasses import dataclass from lib.run import Runner, Caller +from typing import List, Tuple, Optional, Dict +from enum import Enum +from lib.sports.logo import Logo +from datetime import datetime + +class ModuleException(Exception): + pass + +class RequestException(ModuleException): + pass + +class API(Enum): + APISPORTS = 0 + SPORTSIPY = 1 + SPORTSDB = 2 + +class SportStructure(Enum): + Hockey = 0 + Baseball = 1 + Football = 2 + Basketball = 3 + +class GameStatus(Enum): + NotStarted = 0 + InGame = 1 + Finished = 2 + Overtime = 3 + +class GameResult(Enum): + WIN = 0 + LOSS = 1 + TIE = 2 + NOT_PLAYED = 3 class SportBase(Runner): pass + class SportResultBase(Caller): - pass \ No newline at end of file + pass + +@dataclass(repr=True) +class Team(): + name: str + logo: Logo + position: int = None + league: str = None + +@dataclass(repr=True) +class SportStandings(): + positions: List[Team] + +@dataclass(repr=True) +class Score: + team: int + opposing_team: int +@dataclass(repr=True) +class Game: + team: Team + timestamp: datetime + status: GameStatus + opposing_team: Team + result: GameResult + homeoraway: str + score: Score = None + +@dataclass(repr=True) +class BaseModuleResult(): + name: str + team: Dict[str, str] + schedule: Dict[str, str] + standings: Dict[str, str] + sport: SportStructure + +@dataclass(repr=True) +class ModuleResult(BaseModuleResult): + name: str + team: Team + schedule: List[Game] + standings: List[Team] + sport: SportStructure + games_played: int + wins: int + losses: int + +@dataclass(repr=True) +class HockeyResult(BaseModuleResult): + name: str + team: Team + schedule: List[Game] + standings: List[Team] + sport: SportStructure + games_played: int + wins: int + losses: int + +@dataclass(repr=True) +class FootballResult(BaseModuleResult): + name: str + team: Team + schedule: List[Game] + standings: List[Team] + sport: SportStructure + games_played: int + wins: int + losses: int + +@dataclass(repr=True) +class BaseballResult(BaseModuleResult): + name: str + team: Team + schedule: List[Game] + standings: List[Team] + sport: SportStructure + games_played: int + wins: int + losses: int + +def determine_game_status(team, game) -> Tuple[GameStatus, GameResult]: + if hasattr(game, "game"): + game_num = game.game + elif hasattr(game, "week"): + game_num = game.week + if hasattr(team, 'games_played'): + len_games = team.games_played + if hasattr(team, "games_finished"): + len_games = team.games_finished + if game_num == len_games: + return GameStatus.NotStarted, GameResult.NOT_PLAYED + elif game_num > len_games: + return GameStatus.NotStarted, GameResult.NOT_PLAYED + else: + if game.result == "Loss" or game.result == "OTL": + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN +def determine_team(game, team): + if game['teams']['home'] == team.name: + return True + return False + +def determine_apisports_game_status(game_dict: Dict, team, sport): + if sport == 'hockey': + in_game = ['P', 'OT', 'BT', "Q", "HT", "IN"] + if game_dict['status']['short'] in in_game: + return GameStatus.InGame, GameResult.NOT_PLAYED + elif game_dict['status']['short'] in ('FT', 'AP') and datetime.now().date() == datetime.fromtimestamp(game_dict['timestamp']).date(): + is_team_home_team = determine_team(game_dict, team) + if is_team_home_team: + if game_dict['scores']['home'] == game_dict['scores']['away']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home'] < game_dict['scores']['away']: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN + else: + if game_dict['scores']['home'] == game_dict['scores']['away']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home'] > game_dict['scores']['away']: + return GameStatus.Finished, GameResult.WIN + else: + return GameStatus.Finished, GameResult.LOSS + elif game_dict['status']['short'] in ('FT', 'AP') or datetime.fromtimestamp(game_dict['timestamp']) < datetime.now(): + is_team_home_team = determine_team(game_dict, team) + if is_team_home_team: + if game_dict['scores']['home'] == game_dict['scores']['away']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home'] < game_dict['scores']['away']: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN + else: + if game_dict['scores']['home'] == game_dict['scores']['away']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home'] > game_dict['scores']['away']: + return GameStatus.Finished, GameResult.WIN + else: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.NotStarted, GameResult.NOT_PLAYED + elif sport == 'basketball': + in_game = ['P', 'OT', 'BT', "Q", "HT", "IN"] + if game_dict['status']['short'] in in_game: + return GameStatus.InGame, GameResult.NOT_PLAYED + elif game_dict['status']['short'] in ('FT', 'AP') and datetime.now().date() == datetime.fromtimestamp(game_dict['timestamp']).date(): + is_team_home_team = determine_team(game_dict, team) + if is_team_home_team: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] < game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN + else: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] > game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.WIN + else: + return GameStatus.Finished, GameResult.LOSS + elif game_dict['status']['short'] in ('FT', 'AP') or datetime.fromtimestamp(game_dict['timestamp']) < datetime.now(): + is_team_home_team = determine_team(game_dict, team) + if is_team_home_team: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] < game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN + else: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] > game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.WIN + else: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.NotStarted, GameResult.NOT_PLAYED + elif sport == "baseball": + in_game = ['P', 'OT', 'BT', "Q", "HT", "IN"] + if game_dict['status']['short'] in in_game: + return GameStatus.InGame, GameResult.NOT_PLAYED + elif game_dict['status']['short'] in ('FT', 'AP') and datetime.now().date() == datetime.fromtimestamp(game_dict['timestamp']).date(): + is_team_home_team = determine_team(game_dict, team) + if is_team_home_team: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] < game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN + else: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] > game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.WIN + else: + return GameStatus.Finished, GameResult.LOSS + elif game_dict['status']['short'] in ('FT', 'AP') or datetime.fromtimestamp(game_dict['timestamp']) < datetime.now(): + is_team_home_team = determine_team(game_dict, team) + if is_team_home_team: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] < game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.Finished, GameResult.WIN + else: + if game_dict['scores']['home']['total'] == game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.TIE + elif game_dict['scores']['home']['total'] > game_dict['scores']['away']['total']: + return GameStatus.Finished, GameResult.WIN + else: + return GameStatus.Finished, GameResult.LOSS + else: + return GameStatus.NotStarted, GameResult.NOT_PLAYED \ No newline at end of file diff --git a/lib/sports/sports.py b/lib/sports/sports.py index 12a1322..43fd4a6 100755 --- a/lib/sports/sports.py +++ b/lib/sports/sports.py @@ -1,9 +1,16 @@ -from typing import List, Tuple, get_args +from statistics import mean from lib.sports.apisports.apisports import ApiSports +from lib.sports.sportsipy.sportsipy import SportsipyAPI +from sportsipy.nhl.teams import Team as nhl_team from lib.run import Runner, Caller -import os +from dataclasses import dataclass +from enum import Enum +from typing import Tuple, List, Dict, Set, Optional +from datetime import datetime +from lib.sports.sportbase import API +from lib.sports.logo import Logo, logo_map +import lib.sports.sportbase as base import json -import sys # TODO @thefinaljoke More abstract to use different apis class SportApi(Runner): @@ -23,82 +30,144 @@ async def run(self): api_sports = ApiSports(self.config) api_result = await api_sports.run_api_sports() return api_result + elif self.config['sport']['api'] or self.config['sport']['api'] == 'sportsipy': + sports = SportsipyAPI(self.config) + api_result = await sports.run_api_sportsipy() + return api_result return -class SportFinal(Caller): +@dataclass(repr=True) +class Sport: + team_name: str + # Enum Mapping to sport + sport: base.SportStructure + # Method of API + api: base.API + # Logo for the Team + logo: Logo + # List of standing in order + standings: base.SportStandings + # List of games + schedule: List[base.Game] + # A Single game representing the next game + next_game: base.Game + # A List of Wins, with a float of win percentage + wins: Tuple[List[base.Game], float] + # A List of Losses, with a flost of loss percentage + losses: Tuple[List[base.Game], float] + # Set of Leagues if Applicable + leagues: Set[str] = None + +class SportTransform(Caller): """ Final Normalized Object that goes to - The Sport Matrix. + The Sport Matrix. This is the final object + * NO MATTER What the return types should not change + """ def __init__(self, api_result) -> None: # Any Object from any api object self.api_result = api_result + + @property + def team_name(self): + return self.api_result.team_name @property - def get_sport(self): + def get_api(self) -> Enum: + return self.api_result.get_api + + def _normalize_sport(self) -> Enum: + """ + All apis Should return a string in + thier result object + """ return self.api_result.get_sport + + @property + def get_sport(self) -> Enum: + return self._normalize_sport() + + def _normalize_logo(self) -> Logo: + return self.api_result.get_logo + @property - def get_error(self): - return self.api_result.get_error + def get_logo(self) -> Logo: + return self._normalize_logo() + + def _normalize_length_position_teams(self) -> int: + return self.api_result.get_length_position_teams + @property def get_length_position_teams(self): - return len(self.api_result.standings) + return self._normalize_length_position_teams() + + def _normalize_standings(self) -> base.SportStandings: + return self.api_result.get_standings @property def get_standings(self): - return self.api_result.standings - - @property - def position_teams(self): - return self.api_result.position_teams + return self._normalize_standings() - @property - def get_leagues(self): + def _normalize_leagues(self) -> Set[str]: return self.api_result.get_leagues @property - def get_games_played(self): - return self.api_result.games_played - + def get_leagues(self) -> Set[str]: + return self._normalize_leagues() + + def _normalize_schedule(self): + return self.api_result.get_schedule + @property - def get_wins(self): - return self.api_result.get_wins + def get_schedule(self): + return self._normalize_schedule() + + def _normalize_games_played(self) -> List[str]: + return self.api_result.get_games_played @property - def get_wins_percentage(self): - return self.api_result.win_percentage + def get_games_played(self) -> List[str]: + return self._normalize_games_played() - @property - def get_losses(self): - return self.api_result.losses + def _normalize_next_game(self) -> base.Game: + return self.api_result.get_next_game @property - def get_loss_percentage(self): - return self.api_result.loss_percentage + def get_next_game(self) -> base.Game: + return self._normalize_next_game() - @property - def get_game_ids(self): - return self.api_result.game_ids - + def _normalize_wins(self) -> List[str]: + return self.api_result.get_wins + @property - def get_timestamps(self): - return self.api_result.timestamps + def get_wins(self) -> List[str]: + return self._normalize_wins() + def _normalize_win_percentage(self) -> float: + return self.api_result.get_wins_percentage + @property - def get_teams(self): - return self.api_result.teams + def get_wins_percentage(self) -> float: + return self._normalize_win_percentage() - @property - def get_versus(self): - return self.api_result.vs + def _normalize_losses(self) -> List[str]: + return self.api_result.get_losses @property - def get_status(self): - return self.api_result.status + def get_losses(self) -> List[str]: + return self._normalize_losses() + def _normalize_loss_percentage(self) -> float: + return self.api_result.get_loss_percentage + @property - def get_scores(self): - return self.api_result.game_result - - def get_specific_score(self, game_id): - return self.api_result.game_result.get(game_id) \ No newline at end of file + def get_loss_percentage(self) -> float: + return self._normalize_loss_percentage() + + def _normalize_game_ids(self) -> Optional[List[int]]: + return self.api_result.get_game_ids + + @property + def get_game_ids(self) -> Optional[List[int]]: + return self._normalize_game_ids() \ No newline at end of file diff --git a/lib/sports/sportsipy/__init__.py b/lib/sports/sportsipy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sports/sportsipy/baseball/__init__.py b/lib/sports/sportsipy/baseball/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sports/sportsipy/baseball/baseball.py b/lib/sports/sportsipy/baseball/baseball.py new file mode 100644 index 0000000..6fa159a --- /dev/null +++ b/lib/sports/sportsipy/baseball/baseball.py @@ -0,0 +1,93 @@ +import asyncio +from lib.sports.sportsipy.result import SportsipyApiResult +from sportsipy.mlb.schedule import Schedule, Game +from sportsipy.mlb.teams import ( + Team, + Teams, +) +from typing import List +from lib.sports.logo import logo_map, baseball_teams +from sportsipy.mlb.boxscore import Boxscore, Boxscores +from lib.asynclib import make_async +from datetime import datetime +from lib.run import Runner +import lib.sports.sportbase as base + + +class BaseballSportsipy(Runner): + def __init__(self, config): + super().__init__(config) + + + @make_async + def run_team(self, team: str) -> Team: + self.logger.debug("Running Team") + return Team(team) + + @make_async + def run_schedule(self, team: str) -> Schedule: + self.logger.debug("Running Schedule") + return Schedule(team) + + @make_async + def run_standings(self): + self.logger.debug("Running Standings") + return Teams() + + async def run(self) -> SportsipyApiResult: + try: + self.logger.info('Running Sportsipy') + sport = {} + team = logo_map[self.config['sport']['team_id']] + self.logger.info("Running Baseball Sportsipy Api") + sport['team'] = asyncio.create_task(self.run_team(team.shorthand), name="team_task") + sport['schedule'] = asyncio.create_task(self.run_schedule(team.shorthand), name="schedule_task") + sport['standings'] = asyncio.create_task(self.run_standings(), name="standing_task") + await asyncio.gather(*sport.values()) + sport['sport'] = base.SportStructure.Baseball + abbr = sport['team'].result().abbreviation + baseball_result = base.ModuleResult( + name=baseball_teams[abbr], + team=base.Team( + name=baseball_teams[abbr], + logo=logo_map[baseball_teams[abbr]], + position=sport['team'].result().rank + ), + schedule=[ + base.Game( + team=base.Team( + name=baseball_teams[abbr], + logo=logo_map[baseball_teams[abbr]], + position=sport['team'].result().rank + ), + timestamp=game.datetime, + status=base.determine_game_status(sport['team'].result(), game)[0], + opposing_team=base.Team( + name=baseball_teams[game.opponent_abbr], + logo=logo_map[baseball_teams[game.opponent_abbr]], + ), + result=base.determine_game_status(sport['team'].result(), game)[1], + homeoraway=game.location, + score=base.Score( + team=game.runs_scored, + opposing_team=game.runs_allowed + ) + ) for game in sport['schedule'].result() + ], + standings=[ + base.Team( + name=baseball_teams[team.abbreviation], + logo=logo_map[baseball_teams[team.abbreviation]], + position=team.rank + ) for team in sport['standings'].result() + ], + sport=base.SportStructure.Baseball, + games_played=sport['team'].result().games_finished, + wins=sport['team'].result().wins, + losses=sport['team'].result().losses + ) + return SportsipyApiResult(api_result=baseball_result) + except Exception as error: + self.logger.error(f"Error Occured inside of baseball module: {error}") + return None + diff --git a/lib/sports/sportsipy/basketball/__init__.py b/lib/sports/sportsipy/basketball/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sports/sportsipy/basketball/basketball.py b/lib/sports/sportsipy/basketball/basketball.py new file mode 100644 index 0000000..4510354 --- /dev/null +++ b/lib/sports/sportsipy/basketball/basketball.py @@ -0,0 +1,96 @@ +import asyncio +from lib.sports.sportsipy.result import SportsipyApiResult +from sportsipy.nba.schedule import Schedule, Game +from sportsipy.nba.teams import ( + Team, + Teams, +) +from typing import List +from sportsipy.nba.boxscore import Boxscore, Boxscores +from lib.asynclib import make_async +from datetime import datetime +from lib.run import Runner +from lib.sports.logo import logo_map +import lib.sports.sportbase as base + +class BasketballSportsipy(Runner): + """ + Right now of 11-14-21 + There is a failure inside of the basketball + SportsipyApi Teams() Module + """ + + def __init__(self, config): + super().__init__(config) + + @make_async + def run_team(self, team: str) -> Team: + self.logger.debug("Running Team") + return Team(team) + + @make_async + def run_schedule(self, team: str) -> Schedule: + self.logger.debug("Running Schedule") + return Schedule(team) + + @make_async + def run_standings(self): + self.logger.debug("Running Standings") + return Teams() + + async def run(self) -> SportsipyApiResult: + try: + self.logger.info('Running Sportsipy') + sport = {} + team = self.config['sport']['team_id'] + self.logger.info("Running Basketball Sportsipy Api") + breakpoint() + sport['team'] = asyncio.create_task(self.run_team(team), name="team_task") + sport['schedule'] = asyncio.create_task(self.run_schedule(team), name="schedule_task") + sport['standings'] = asyncio.create_task(self.run_standings(), name="standing_task") + await asyncio.gather(*sport.values()) + sport['sport'] = base.SportStructure.Basketball + basketball_result = base.ModuleResult( + name=sport['team'].result().name, + team=base.Team( + name=sport['team'].result().name, + logo=logo_map[sport['team'].result().name], + position=sport['team'].result().rank + ), + schedule=[ + base.Game( + team = base.Team( + name=sport['team'].result().name, + logo=logo_map[sport['team'].result().name], + position=sport['team'].result().rank + ), + timestamp=game.datetime, + status=base.determine_game_status(sport['team'].result(), game)[0], + opposing_team= base.Team( + name=game.opponent_name, + logo=logo_map[game.opponent_name] + ), + result=base.determine_game_status(sport['team'].result(), game)[1], + homeoraway=game.location, + score=base.Score( + team=game.goals_scored, + opposing_team=game.goals_allowed + ) if base.GameStatus.Finished else None + ) for game in sport['schedule'].result() + ], + standings=[ + base.Team( + name=team.name, + logo=logo_map[team.name], + position=team.rank + ) for team in sport['standings'].result() + ], + sport=sport['sport'], + games_played=sport['team'].result().games_played, + wins=sport['team'].result().wins, + losses=sport['team'].result().losses + ) + return SportsipyApiResult(api_result=basketball_result) + except Exception as error: + self.logger.error(f"An Error Occured in basketball Module: {error}") + return None diff --git a/lib/sports/sportsipy/football/__init__.py b/lib/sports/sportsipy/football/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sports/sportsipy/football/football.py b/lib/sports/sportsipy/football/football.py new file mode 100644 index 0000000..bb75f8e --- /dev/null +++ b/lib/sports/sportsipy/football/football.py @@ -0,0 +1,88 @@ +import asyncio +import lib.sports.sportbase as base +from lib.sports.sportsipy.result import SportsipyApiResult +from sportsipy.nfl.schedule import Schedule, Game +from sportsipy.nfl.teams import ( + Team, + Teams, +) +from typing import List +from lib.sports.logo import logo_map +from lib.asynclib import make_async +from datetime import datetime +from lib.run import Runner + +class FootballSportsipy(Runner): + def __init__(self, config): + super().__init__(config) + + @make_async + def run_team(self, team: str) -> Team: + self.logger.debug("Running Team") + return Team(team) + + @make_async + def run_schedule(self, team: str) -> Schedule: + self.logger.debug("Running Schedule") + return Schedule(team) + + @make_async + def run_standings(self): + self.logger.debug("Running Standings") + return Teams() + + async def run(self) -> SportsipyApiResult: + try: + self.logger.info("Inside of the Football Sportsipy") + sport = {} + team = logo_map[self.config['sport']['team_id']] + self.logger.info("Running Football Sportsipy Api") + sport['team'] = asyncio.create_task(self.run_team(team.shorthand), name="team_task") + sport['schedule'] = asyncio.create_task(self.run_schedule(team.shorthand), name="schedule_task") + sport['standings'] = asyncio.create_task(self.run_standings(), name="standing_task") + await asyncio.gather(*sport.values()) + sport['sport'] = base.SportStructure.Football + football_result = base.ModuleResult( + name=sport['team'].result().name, + team=base.Team( + name=sport['team'].result().name, + logo=logo_map[sport['team'].result().name], + position=sport['team'].result().rank + ), + schedule=[ + base.Game( + team = base.Team( + name=sport['team'].result().name, + logo=logo_map[sport['team'].result().name], + position=sport['team'].result().rank + ), + timestamp=game.datetime, + status=base.determine_game_status(sport['team'].result(), game)[0], + opposing_team= base.Team( + name=game.opponent_name, + logo=logo_map[game.opponent_name] + ), + result=base.determine_game_status(sport['team'].result(), game)[1], + homeoraway=game.location, + score=base.Score( + team=game.points_scored, + opposing_team=game.points_allowed + ) if base.GameStatus.Finished else None + ) for game in sport['schedule'].result() + ], + standings=[ + base.Team( + name=team.name, + logo=logo_map[team.name], + position=team.rank + ) for team in sport['standings'].result() + ], + sport=sport['sport'], + games_played=sport['team'].result().games_played, + wins=sport['team'].result().wins, + losses=sport['team'].result().losses + ) + return SportsipyApiResult(api_result=football_result) + except Exception as error: + self.logger.error(f"Error Occured inside of football module: {error}") + return None diff --git a/lib/sports/sportsipy/hockey/__init__.py b/lib/sports/sportsipy/hockey/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/sports/sportsipy/hockey/hockey.py b/lib/sports/sportsipy/hockey/hockey.py new file mode 100644 index 0000000..35e750b --- /dev/null +++ b/lib/sports/sportsipy/hockey/hockey.py @@ -0,0 +1,91 @@ +import asyncio +from asyncio.tasks import Task +from lib.sports.logo import Logo, logo_map +from lib.sports.sportsipy.result import SportsipyApiResult +from sportsipy.nhl.schedule import Schedule, Game +from sportsipy.nhl.teams import ( + Team, + Teams, +) +from typing import List, Tuple +from sportsipy.nhl.boxscore import Boxscore, Boxscores +from lib.asynclib import make_async +from datetime import datetime +from lib.run import Runner +import lib.sports.sportbase as base + +class HockeySportsipy(Runner): + def __init__(self, config): + super().__init__(config) + + @make_async + def run_team(self, team: str) -> Team: + self.logger.debug("Running Team") + return Team(team) + + @make_async + def run_schedule(self, team: str) -> Schedule: + self.logger.debug("Running Schedule") + return Schedule(team) + + @make_async + def run_standings(self): + self.logger.debug("Running Standings") + return Teams() + + async def run(self) -> SportsipyApiResult: + try: + self.logger.info('Running Sportsipy') + sport = {} + team = logo_map[self.config['sport']['team_id']] + self.logger.info("Running Hockey Sportsipy Api") + sport['team'] = asyncio.create_task(self.run_team(team.shorthand), name="team_task") + sport['schedule'] = asyncio.create_task(self.run_schedule(team.shorthand), name="schedule_task") + sport['standings'] = asyncio.create_task(self.run_standings(), name="standing_task") + await asyncio.gather(*sport.values()) + sport['sport'] = base.SportStructure.Hockey + hockey_result = base.ModuleResult( + name=sport['team'].result().name, + team=base.Team( + name=sport['team'].result().name, + logo=logo_map[sport['team'].result().name], + position=sport['team'].result().rank + ), + schedule=[ + base.Game( + team = base.Team( + name=sport['team'].result().name, + logo=logo_map[sport['team'].result().name], + position=sport['team'].result().rank + ), + timestamp=game.datetime, + status=base.determine_game_status(sport['team'].result(), game)[0], + opposing_team= base.Team( + name=game.opponent_name, + logo=logo_map[game.opponent_name] + ), + result=base.determine_game_status(sport['team'].result(), game)[1], + homeoraway=game.location, + score=base.Score( + team=game.goals_scored, + opposing_team=game.goals_allowed + ) if base.GameStatus.Finished else None + ) for game in sport['schedule'].result() + ], + standings=[ + base.Team( + name=team.name, + logo=logo_map[team.name], + position=team.rank + ) for team in sport['standings'].result() + ], + sport=sport['sport'], + games_played=sport['team'].result().games_played, + wins=sport['team'].result().wins, + losses=sport['team'].result().losses + ) + return SportsipyApiResult(api_result=hockey_result) + except Exception as error: + self.logger.error(f"An Error Occured in hockey Module: {error}") + return None + diff --git a/lib/sports/sportsipy/result.py b/lib/sports/sportsipy/result.py new file mode 100644 index 0000000..9455bb5 --- /dev/null +++ b/lib/sports/sportsipy/result.py @@ -0,0 +1,99 @@ +import json +from asyncio import Task +from typing import Dict, List, Tuple +from datetime import datetime +from enum import Enum +from sportsipy.nhl.schedule import Game +from sportsipy.nhl.teams import Team as nhl_team +from lib.sports.sportbase import SportResultBase, API +from lib.sports.logo import Logo, logo_map +import lib.sports.sportbase as base + +class SportsipyApiResult(SportResultBase): + + def __init__(self, api_result: Dict[str, Task]) -> None: + self.api_result = api_result + self._get_sport: Enum = api_result.sport + self._team: base.Team = api_result.team + self._schedule: base.SportStandings = base.SportStandings( + positions=api_result.schedule + ) + + self._api: Enum = API.SPORTSIPY + self._standings: List[base.Team] = api_result.standings + self._position = self._team.position + self._get_leagues = None + self._games_played: List[Game] = self._schedule.positions[:api_result.games_played] + self._get_wins: List[Game] = [game for game in self._games_played if base.GameResult.WIN == game.result] + self._win_percentage: float = api_result.wins/len(self._games_played) + self._losses: List[Game] = [game for game in self._games_played if base.GameResult.LOSS == game.result] + self._loss_percentage: float = api_result.losses/len(self._games_played) + self._next_game = self._schedule.positions[len(self._games_played)] + self._game_ids = None + + @property + def get_api(self) -> Enum: + return self._api + + @property + def get_sport(self) -> Enum: + return self._get_sport + + @property + def team_name(self): + return self._team.name + + @property + def get_logo(self) -> Logo: + return logo_map[self._team.name] + + @property + def get_team(self): + return self._team + + @property + def get_length_position_teams(self): + return len(self._standings) + + @property + def get_standings(self): + return self._standings + + @property + def get_schedule(self): + return self._schedule + + @property + def get_leagues(self): + return self._get_leagues + + @property + def get_games_played(self): + return self._games_played + + @property + def get_wins(self): + return self._get_wins + + @property + def get_wins_percentage(self): + return self._win_percentage + + @property + def get_losses(self): + return self._losses + + @property + def get_loss_percentage(self): + return self._loss_percentage + + @property + def get_game_ids(self): + return self._game_ids + + def get_specific_score(self, game_id): + return self._game_result.get(game_id) + + @property + def get_next_game(self): + return self._next_game \ No newline at end of file diff --git a/lib/sports/sportsipy/sportsipy.py b/lib/sports/sportsipy/sportsipy.py new file mode 100644 index 0000000..524be49 --- /dev/null +++ b/lib/sports/sportsipy/sportsipy.py @@ -0,0 +1,28 @@ +from lib.sports.sportbase import SportBase, SportResultBase +from lib.sports.sportsipy.hockey.hockey import HockeySportsipy +from lib.sports.sportsipy.baseball.baseball import BaseballSportsipy +from lib.sports.sportsipy.basketball.basketball import BasketballSportsipy +from lib.sports.sportsipy.football.football import FootballSportsipy + +class SportsipyAPI(SportBase): + def __init__(self, config): + self.config = config + self.sport = self.config['sport'] + + async def run_api_sportsipy(self): + if 'hockey' == self.sport.get('sport').lower(): + self.logger.debug("Running hockey sportsipy") + hockey = HockeySportsipy(self.config) + return await hockey.run() + elif 'baseball' == self.sport.get('sport').lower(): + self.logger.debug("Running Baseball sportsipy") + baseball = BaseballSportsipy(self.config) + return await baseball.run() + elif 'basketball' == self.sport.get('sport').lower(): + self.logger.debug("Running basketball sportsipy") + basketball = BasketballSportsipy(self.config) + return await basketball.run() + elif 'football' == self.sport.get('sport').lower(): + self.logger.debug("Running football sportsipy") + football = FootballSportsipy(self.config) + return await football.run() \ No newline at end of file diff --git a/lib/weather/ecIcons_utf8.csv b/lib/weather/ecIcons_utf8.csv deleted file mode 100755 index a4f1c65..0000000 --- a/lib/weather/ecIcons_utf8.csv +++ /dev/null @@ -1,50 +0,0 @@ -encode,Code,ForecastCode,OWMCode,Description,Represents,fontcode,font -UTF8,0,0,800,Sunny,Day Conditions Only,f00d, -UTF8,1,2,800,Mainly Sunny,Day Conditions Only,f00d, -UTF8,2,1,801,Partly Cloudy,Day Conditions Only,f002, -UTF8,3,4,801,Mostly Cloudy,Day Conditions Only,f013, -UTF8,6,6,500,Light Rain Shower,Day Conditions Only,f008, -UTF8,7,7,300,Light Rain Shower and Flurries,Day Conditions Only,f006, -UTF8,8,8,600,Light Flurries,Day Conditions Only,f00a, -UTF8,10,10,801,Cloudy,Day and Night Conditions,f013, -UTF8,11,13,500,Precipitation,Day and Night Conditions,f01c, -UTF8,12,13,500,Rain,Day and Night Conditions,f019, -UTF8,13,12,500,Rain Shower,Day and Night Conditions,f01a, -UTF8,14,14,500,Freezing Rain,Day and Night Conditions,f0b5, -UTF8,15,15,600,Rain and Snow,Day and Night Conditions,f017, -UTF8,16,16,600,Snow,Day and Night Conditions,f01b, -UTF8,16,17,600,Snow,Day and Night Conditions,f01b, -UTF8,17,17,600,Snow,Day and Night Conditions,f01b, -UTF8,18,17,600,Heavy Snow,Day and Night Conditions,f01b, -UTF8,19,9,200,Thunderstorm,Day Conditions Only,f01e, -UTF8,23,23,721,Haze,Day and Night Conditions,f0b6, -UTF8,24,24,741,Fog,Day and Night Conditions,f014, -UTF8,25,17,600,Drifting Snow,Day and Night Conditions,f064, -UTF8,26,27,600,Ice Crystals,Day and Night Conditions,f076, -UTF8,27,19,600,Hail,Day and Night Conditions,f015, -UTF8,28,28,300,Drizzle,Day and Night Conditions,f01c, -UTF8,30,30,800,Clear,Night Conditions Only,f02e, -UTF8,31,31,800,Mainly Clear,Night Conditions Only,f02e, -UTF8,32,32,801,Partly Cloudy,Night Conditions Only,f031, -UTF8,33,33,801,Mostly Cloudy,Night Conditions Only,f041, -UTF8,36,36,500,Light Rain Shower,Night Conditions Only,f02b, -UTF8,37,37,300,Light Rain Shower and Flurries,Night Conditions Only,f026, -UTF8,38,38,600,Light Flurries,Night Conditions Only,f02a, -UTF8,39,39,200,Thunderstorm,Night Conditions Only,f02d, -UTF8,40,40,600,Blowing Snow,Day and Night Conditions,f064, -UTF8,41,43,781,Funnel Cloud,Day and Night Conditions,f056, -UTF8,42,43,781,Tornado,Day and Night Conditions,f056, -UTF8,43,43,430,Windy,Day and Night Conditions,f021, -UTF8,44,44,711,Smoke,Day and Night Conditions,f062, -UTF8,45,44,761,Blowing Dust,Day and Night Conditions,f063, -UTF8,46,9,200,Thunderstorm with Hail,Day and Night Conditions,f01e, -UTF8,47,9,200,Thunderstorm with Dust Storm,Day and Night Conditions,f01e, -UTF8,48,43,781,Waterspout,Day and Night Conditions,f056, -UTF8,90,29,900,N/A,Day and Night Conditions,f07b, -UTF8,91,91,901,Humidity,Day and Night Conditions,f07a, -UTF8,92,92,902,Rising,Day and Night Conditions,f057, -UTF8,93,93,903,Falling,Day and Night Conditions,f088, -UTF8,94,94,904,Steady,Day and Night Conditions,f04d, -UTF8,1,5,800,Clear,Day Conditions Only,f00d, -UTF8,3,3,801,Cloudy Periods,Day Conditions Only,f013, -UTF8,31,31,806,Mainly Clear,Night Conditions Only,f02e, diff --git a/lib/weather/normal.py b/lib/weather/normal.py new file mode 100644 index 0000000..2da8ca8 --- /dev/null +++ b/lib/weather/normal.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +from typing import Dict, Tuple +from datetime import datetime, timedelta +from lib.weather.openweather.weather import OpenWeatherApi +from lib.weather.weathergov.nws import NWSApi +from lib.run import Caller + +class NormalizedWeather(): + + def __init__(self, api_result) -> None: + self.api_result = api_result + + @property + def get_api(self): + return self.api_result.get_api + + @property + def get_icon(self): + return self.api_result.get_icon + + @property + def get_lat_long(self) -> Tuple[float, float]: + return self.api_result.get_lat_long + + @property + def get_wind_speed(self) -> int: + return self.api_result.get_wind_speed + + @property + def get_daily(self) -> Dict[str, str]: + return self.api_result.get_daily + + @property + def get_wind_deg(self) -> int: + return self.api_result.get_wind_deg + + @property + def get_precipitation(self) -> int: + return self.api_result.get_precipitation * 100 + + @property + def get_uv(self) -> int: + return self.api_result.get_uv + + @property + def get_place(self) -> str: + return self.api_result.get_place + + @property + def get_weather(self) -> Dict[str, str]: + return self.api_result.get_weather + + @property + def get_conditions(self) -> str: + return self.api_result.get_conditions + + @property + def get_weather_icon(self) -> str: + return self.api_result.get_weather_icon + + @property + def get_temp(self) -> int: + return self.api_result.get_temp + + @property + def get_feels_like(self) -> int: + return self.api_result.get_feels_like + + @property + def get_min_temp(self) -> int: + return self.api_result.get_min_temp + + @property + def get_max_temp(self) -> int: + return self.api_result.get_max_temp + + @property + def get_humidity(self) -> None: + return self.api_result.get_humidity + + @property + def get_wind(self) -> Dict: + return self.api_result.get_wind + + @property + def get_time(self) -> datetime: + return self.api_result.get_time + + @property + def get_sunrise(self) -> datetime: + return self.api_result.get_sunrise + + @property + def get_sunset(self) -> datetime: + return self.api_result.get_sunset + + def calculate_duration_of_daylight(self) -> timedelta: + return self.api_result.get_sunset - self.api_result.get_time + +class WeatherApi(Caller): + + def __init__(self, config) -> None: + super().__init__() + self.config = config + + async def run_weather(self): + if self.config['weather']['api'] == "openweather": + open_weather = OpenWeatherApi(self.config) + result = await open_weather.run() + else: + nws = NWSApi(self.config) + result = await nws.run() + return NormalizedWeather(result) \ No newline at end of file diff --git a/lib/weather/openweather/__init__.py b/lib/weather/openweather/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/weather/weather.py b/lib/weather/openweather/weather.py similarity index 84% rename from lib/weather/weather.py rename to lib/weather/openweather/weather.py index dec13df..7b17ab6 100755 --- a/lib/weather/weather.py +++ b/lib/weather/openweather/weather.py @@ -10,42 +10,11 @@ from typing import Dict, Tuple, List from datetime import datetime from datetime import timedelta +import lib.weather.weatherbase as base +from lib.weather.weather_icon import weather_icon_mapping import csv -def get_weather_csv() -> List[Dict[str, str]]: - csv_path = '/etc/ohmyoled/ecIcons_utf8.csv' - return list(csv.DictReader(open(csv_path))) - -def build_weather_icons() -> str: - csv = get_weather_csv() - icon = {} - for icn in csv: - icon.update({icn["OWMCode"]: WeatherIcon(icn['OWMCode'], icn['Description'], icn['fontcode'], icn['font'])}) - return icon - -class WeatherIcon(): - def __init__(self, owm_id: str, description: str, fontcode: str, font: str) -> None: - self.owm_id = owm_id - self.description = description - self.fontcode = fontcode - self.font = font - - @property - def get_owm_id(self) -> str: - return self.owm_id - @property - def get_description(self) -> str: - return self.description - @property - def get_fontcode(self) -> str: - return self.fontcode - @property - def get_font(self) -> str: - return self.font - - - -class WeatherApi(Runner): +class OpenWeatherApi(Runner): """ Weatherapi object To parse the config file and @@ -127,9 +96,9 @@ async def run(self) -> Dict: api_data = await self.get_data(args) current_data = await self.get_current_location() api_data['name'] = current_data['city'] - return api_data + return OpenWeather(api_data) -class Weather(Caller): +class OpenWeather(Caller): """ Weather object to describe current Polled data """ @@ -137,6 +106,8 @@ def __init__(self, api: Dict) -> None: super().__init__() self.api = api self.api_json = api + self._api_caller = base.APIWeather.OPENWEATHER + self._lat_long = (self.api['lat'], self.api['lon']) self._place = self.api_json.get('name') self._current = self.api_json.get('current') self._weather = self._current.get('weather') @@ -180,6 +151,49 @@ def __repr__(self) -> str: joined_attrs = ',\n'.join(attrs) return f"Weather(\n{joined_attrs})" + @property + def get_icon(self): + owm_wxcode: int = int(self._weather[0]['id']) + if owm_wxcode in range(200,299): + # Thunderstorm Class + owm_icon = weather_icon_mapping[17] + elif owm_wxcode in range(300,399): + # Drizzle Class + owm_icon = weather_icon_mapping[23] + elif owm_wxcode in range(500,599): + # Rain Class + owm_icon = weather_icon_mapping[9] + elif owm_wxcode in range(600,699): + # Snow Class + owm_icon = weather_icon_mapping[13] + elif owm_wxcode in range(700, 780): + owm_icon = weather_icon_mapping[36] + elif owm_wxcode == 800: + # Sunny + if self._sunset > datetime.now(): + owm_icon = weather_icon_mapping[0] + else: + owm_icon = weather_icon_mapping[48] + + elif owm_wxcode in range(801,805): + # Rain Class + if self._sunset > datetime.now(): + owm_icon = weather_icon_mapping[3] + else: + owm_icon = weather_icon_mapping[48] + else: + owm_icon = weather_icon_mapping[0] + + return owm_icon + + @property + def get_api(self): + return self._api_caller + + @property + def get_lat_long(self) -> Tuple[float, float]: + return self._lat_long + @property def get_wind_speed(self) -> int: return self._wind_speed diff --git a/lib/weather/weather_icon.py b/lib/weather/weather_icon.py new file mode 100644 index 0000000..dfb7f64 --- /dev/null +++ b/lib/weather/weather_icon.py @@ -0,0 +1,52 @@ +import lib.weather.weatherbase as base + +weather_icon_mapping = { + 0: base.WeatherIcon(condition='Sunny', icon='f00d', font='\uf00d', time_of_day='Day Conditions Only', url=None, owmcode=800), + 1: base.WeatherIcon(condition='Mainly Sunny', icon='f00d', font='\uf00d', time_of_day='Day Conditions Only', url=None, owmcode=800), + 2: base.WeatherIcon(condition='Partly Cloudy', icon='f002', font='\uf002', time_of_day='Day Conditions Only', url=None, owmcode=801), + 3: base.WeatherIcon(condition='Mostly Cloudy', icon='f013', font='\uf013', time_of_day='Day Conditions Only', url=None, owmcode=801), + 4: base.WeatherIcon(condition='Light Rain Shower', icon='f008', font='\uf008', time_of_day='Day Conditions Only', url=None, owmcode=500), + 5: base.WeatherIcon(condition='Light Rain Shower and Flurries', icon='f006', font='\uf006', time_of_day='Day Conditions Only', url=None, owmcode=300), + 6: base.WeatherIcon(condition='Light Flurries', icon='f00a', font='\uf00a', time_of_day='Day Conditions Only', url=None, owmcode=600), + 7: base.WeatherIcon(condition='Cloudy', icon='f013', font='\uf013', time_of_day='Day and Night Conditions', url=None, owmcode=801), + 8: base.WeatherIcon(condition='Precipitation', icon='f01c', font='\uf01c', time_of_day='Day and Night Conditions', url=None, owmcode=500), + 9: base.WeatherIcon(condition='Rain', icon='f019', font='\uf019', time_of_day='Day and Night Conditions', url=None, owmcode=500), + 10: base.WeatherIcon(condition='Rain Shower', icon='f01a', font='\uf01a', time_of_day='Day and Night Conditions', url=None, owmcode=500), + 11: base.WeatherIcon(condition='Freezing Rain', icon='f0b5', font='\uf0b5', time_of_day='Day and Night Conditions', url=None, owmcode=500), + 12: base.WeatherIcon(condition='Rain and Snow', icon='f017', font='\uf017', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 13: base.WeatherIcon(condition='Snow', icon='f01b', font='\uf01b', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 14: base.WeatherIcon(condition='Snow', icon='f01b', font='\uf01b', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 15: base.WeatherIcon(condition='Snow', icon='f01b', font='\uf01b', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 16: base.WeatherIcon(condition='Heavy Snow', icon='f01b', font='\uf01b', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 17: base.WeatherIcon(condition='Thunderstorm', icon='f01e', font='\uf01e', time_of_day='Day Conditions Only', url=None, owmcode=200), + 18: base.WeatherIcon(condition='Haze', icon='f0b6', font='\uf0b6', time_of_day='Day and Night Conditions', url=None, owmcode=721), + 19: base.WeatherIcon(condition='Fog', icon='f014', font='\uf014', time_of_day='Day and Night Conditions', url=None, owmcode=741), + 20: base.WeatherIcon(condition='Drifting Snow', icon='f064', font='\uf064', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 21: base.WeatherIcon(condition='Ice Crystals', icon='f076', font='\uf076', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 22: base.WeatherIcon(condition='Hail', icon='f015', font='\uf015', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 23: base.WeatherIcon(condition='Drizzle', icon='f01c', font='\uf01c', time_of_day='Day and Night Conditions', url=None, owmcode=300), + 24: base.WeatherIcon(condition='Clear', icon='f02e', font='\uf02e', time_of_day='Night Conditions Only', url=None, owmcode=800), + 25: base.WeatherIcon(condition='Mainly Clear', icon='f02e', font='\uf02e', time_of_day='Night Conditions Only', url=None, owmcode=800), + 26: base.WeatherIcon(condition='Partly Cloudy', icon='f031', font='\uf031', time_of_day='Night Conditions Only', url=None, owmcode=801), + 27: base.WeatherIcon(condition='Mostly Cloudy', icon='f041', font='\uf041', time_of_day='Night Conditions Only', url=None, owmcode=801), + 28: base.WeatherIcon(condition='Light Rain Shower', icon='f02b', font='\uf02b', time_of_day='Night Conditions Only', url=None, owmcode=500), + 29: base.WeatherIcon(condition='Light Rain Shower and Flurries', icon='f026', font='\uf026', time_of_day='Night Conditions Only', url=None, owmcode=300), + 30: base.WeatherIcon(condition='Light Flurries', icon='f02a', font='\uf02a', time_of_day='Night Conditions Only', url=None, owmcode=600), + 31: base.WeatherIcon(condition='Thunderstorm', icon='f02d', font='\uf02d', time_of_day='Night Conditions Only', url=None, owmcode=200), + 32: base.WeatherIcon(condition='Blowing Snow', icon='f064', font='\uf064', time_of_day='Day and Night Conditions', url=None, owmcode=600), + 33: base.WeatherIcon(condition='Funnel Cloud', icon='f056', font='\uf056', time_of_day='Day and Night Conditions', url=None, owmcode=781), + 34: base.WeatherIcon(condition='Tornado', icon='f056', font='\uf056', time_of_day='Day and Night Conditions', url=None, owmcode=781), + 35: base.WeatherIcon(condition='Windy', icon='f021', font='\uf021', time_of_day='Day and Night Conditions', url=None, owmcode=430), + 36: base.WeatherIcon(condition='Smoke', icon='f062', font='\uf062', time_of_day='Day and Night Conditions', url=None, owmcode=711), + 37: base.WeatherIcon(condition='Blowing Dust', icon='f063', font='\uf063', time_of_day='Day and Night Conditions', url=None, owmcode=761), + 38: base.WeatherIcon(condition='Thunderstorm with Hail', icon='f01e', font='\uf01e', time_of_day='Day and Night Conditions', url=None, owmcode=200), + 39: base.WeatherIcon(condition='Thunderstorm with Dust Storm', icon='f01e', font='\uf01e', time_of_day='Day and Night Conditions', url=None, owmcode=200), + 40: base.WeatherIcon(condition='Waterspout', icon='f056', font='\uf056', time_of_day='Day and Night Conditions', url=None, owmcode=781), + 41: base.WeatherIcon(condition='N/A', icon='f07b', font='\uf07b', time_of_day='Day and Night Conditions', url=None, owmcode=900), + 42: base.WeatherIcon(condition='Humidity', icon='f07a', font='\uf07a', time_of_day='Day and Night Conditions', url=None, owmcode=901), + 43: base.WeatherIcon(condition='Rising', icon='f057', font='\uf057', time_of_day='Day and Night Conditions', url=None, owmcode=902), + 44: base.WeatherIcon(condition='Falling', icon='f088', font='\uf088', time_of_day='Day and Night Conditions', url=None, owmcode=903), + 45: base.WeatherIcon(condition='Steady', icon='f04d', font='\uf04d', time_of_day='Day and Night Conditions', url=None, owmcode=904), + 46: base.WeatherIcon(condition='Clear', icon='f00d', font='\uf00d', time_of_day='Day Conditions Only', url=None, owmcode=800), + 47: base.WeatherIcon(condition='Cloudy Periods', icon='f013', font='\uf013', time_of_day='Day Conditions Only', url=None, owmcode=801), + 48: base.WeatherIcon(condition='Mainly Clear', icon='f02e', font='\uf02e', time_of_day='Night Conditions Only', url=None, owmcode=806)} \ No newline at end of file diff --git a/lib/weather/weatherbase.py b/lib/weather/weatherbase.py new file mode 100644 index 0000000..15655cb --- /dev/null +++ b/lib/weather/weatherbase.py @@ -0,0 +1,77 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Tuple, Union +from enum import Enum + +class APIWeather(Enum): + OPENWEATHER = 1 + # National Weather Service + NWS = 2 + +class WindDirection(Enum): + S = 180 + N = 0 + W = 270 + E = 90 + NE = 45 + NW = 315 + NNE = 30 + ENE = 75 + ESE = 105 + SE = 135 + SSE = 165 + SSW = 210 + SW = 225 + WSW = 255 + WNW = 285 + NNW = 345 + +@dataclass(repr=True) +class WeatherIcon: + condition: str + icon: str + font: str + time_of_day: str + url: str = None + owmcode: int = None + + def request_url_icon(self): + pass + +@dataclass(repr=True) +class CurrentWeather(): + conditions: str + temp: Union[int, float] + feels_like: Union[int, float] + wind_speed: int + humidity: int + perciptation_chance: int + uv: int + wind_direction: int = None + weather_icon: WeatherIcon = None + +@dataclass(repr=True) +class DayForcast: + todayhigh: int + todaylow: int + sunrise: datetime + sunset: datetime + +@dataclass(repr=True) +class WeatherBase(): + api: APIWeather + # (lat, lng) + location: Tuple[float, float] + location_name: str + current: CurrentWeather + dayforcast: DayForcast + +@dataclass(repr=True) +class Weather(): + api: APIWeather + # (lat, lng) + location: Tuple[float, float] + location_name: str + current: CurrentWeather + dayforcast: DayForcast + diff --git a/lib/weather/weathergov/__init__.py b/lib/weather/weathergov/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/weather/weathergov/nws.py b/lib/weather/weathergov/nws.py new file mode 100644 index 0000000..6a0b4f4 --- /dev/null +++ b/lib/weather/weathergov/nws.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +import asyncio +import sys +import json +import datetime as dt +import lib.weather.weatherbase as base +from lib.weather.weather_icon import weather_icon_mapping +from lib.asynclib import make_async +from lib.run import Runner, Caller +from datetime import datetime, timedelta +from typing import Tuple, Dict, List +from suntime import Sun + +class NWSApi(Runner): + + def __init__(self, config) -> None: + super().__init__(config) + self.weather = self.config['weather'] + + async def parse_args(self) -> str: + """ + Check if zipcode or city in config file + """ + + return await self.url_builder() + + + async def get_long_and_lat(self, location: str=None, zipcode: int=None, url=None) -> Tuple: + """ + Searches for Longitude and latitude for Given City + """ + self.logger.debug("Getting Lat and Long") + try: + if location: + self.logger.debug("Computing Longitude and Latitude") + response = await self.get_data(url) + lon = response.get('coord').get('lon') + lat = response.get('coord').get('lat') + return lon, lat + else: + raise Exception("Zip Code not Supported") + except Exception as e: + self.logger.critical(e) + sys.exit("No City Found") + + @make_async + def get_current_location(self) -> Dict[str, str]: + url = 'http://ipinfo.io/json' + response = self.run_non_async_request(url) + return response.json() + + async def url_builder(self): + """ + Builds Url to poll the Api + """ + self.logger.debug("Building Weather url...") + ip_json: Dict[str, str] = await self.get_current_location() + lon, lat = ip_json['loc'].split(',')[1], ip_json['loc'].split(',')[0] + url = f"https://api.weather.gov/points/{lat},{lon}" + return url + + async def run(self) -> Dict: + self.logger.info("Running Api for Weather") + args = await self.parse_args() + main_json = await self.get_data(args) + observation_url = f'https://api.weather.gov/stations/{main_json["properties"]["radarStation"]}/observations/latest' + tasks = { + 'forcast': asyncio.create_task(self.get_data(main_json['properties']['forecast'])), + 'hourly': asyncio.create_task(self.get_data(main_json['properties']['forecastHourly'])), + 'observations': asyncio.create_task(self.get_data(observation_url)) + } + await asyncio.gather(*tasks.values()) + count = 0 + while count <= 4: + if not all([('status' in tasks['hourly'].result()), ('status' in tasks['forcast'].result())]): + break + count += 1 + tasks['hourly'] = asyncio.create_task(self.get_data(main_json['properties']['forecastHourly'])) + tasks['forcast'] = asyncio.create_task(self.get_data(main_json['properties']['forecast'])) + await asyncio.gather(tasks['hourly'], tasks['forcast']) + api_data = {'main_json': main_json, 'forcast': tasks['forcast'].result(), 'hourly': tasks['hourly'].result(), 'observe': tasks['observations'].result()} + return NWSTransform(api_data) + +class NWSTransform(Caller): + + def __init__(self, api: Dict) -> None: + super().__init__() + self.api = api + self.api_json = api + self._api_caller = base.APIWeather.NWS + self._observation = self.api_json['observe'] + self._late_observation = self._observation + self._lat_long = (self.api['main_json']['geometry']['coordinates'][1], self.api['main_json']['geometry']['coordinates'][0]) + self._place = self.api_json['main_json']['properties']['relativeLocation']['properties']['city'] + self._current = self.api_json['hourly']['properties']['periods'][0] + self._weather = self.api_json['hourly']['properties']['periods'] + self._conditions = self._current['shortForecast'] + self._temp = int((self._late_observation['properties']['temperature']['value'] * 1.8) + 32) + # There is no Feels like + self._feels_like = self._temp + self._daily = self.api_json['forcast']['properties']['periods'][0] + # Have to figure out how to get the temp mina nd max with + self._min_temp = min(self.determine_max_and_min_temps()) + self._max_temp = max(self.determine_max_and_min_temps()) + self._humidity = int(self._late_observation['properties']['relativeHumidity']['value']) + self._wind_speed = int(self._late_observation['properties']['windSpeed']['value'] / 1.609344) + self._wind_deg = self._late_observation['properties']['windDirection']['value'] + self._time = datetime.now() + self._sunrise = self.gen_rise_and_set()[0] + self._sunset = self.gen_rise_and_set()[1] + self._pop = 0 + self._uv = None + + def __repr__(self) -> str: + attrs = [ + f"name={self._place}", + f"current={json.dumps(self._current, indent=2)}", + f"weather={json.dumps(self._weather, indent=2)}", + f"conditions={self._conditions}", + f"weather_icon={self._weather_icon}", + f"temp={self._temp}", + f"feels_like={self._feels_like}", + f"daily={json.dumps(self._daily, indent=2)}", + f"min_temp={self._min_temp}", + f"max_temp={self._max_temp}", + f"humidity={self._humidity}", + f"wind_speed={self._wind_speed}", + f"wind_deg={self._wind_deg}", + f"time={self._time}", + f"sunrise={self._sunrise}", + f"sunset={self._sunset}", + f"precipitation={self._pop}", + f"uv={self._uv}" + ] + joined_attrs = ',\n'.join(attrs) + return f"Weather(\n{joined_attrs})" + + @property + def get_icon(self): + # Have to get the icon + condition: str = self._weather[0]['shortForecast'].lower() + if any(s in condition.lower() for s in ("sunny", "clear", 'sun')): + # Sunny + if self._sunset.replace(tzinfo=None) > datetime.now(): + owm_icon = weather_icon_mapping[0] + else: + owm_icon = weather_icon_mapping[48] + elif any(s in condition.lower() for s in ('rain', 'storm', 'thunderstorm ')): + owm_icon = weather_icon_mapping[9] + elif 'snow' in condition: + owm_icon = weather_icon_mapping[13] + elif any(s in condition.lower() for s in ('cloudy', 'cloud')): + owm_icon = weather_icon_mapping[7] + else: + owm_icon = weather_icon_mapping[0] + return owm_icon + + def determine_max_and_min_temps(self) -> List[int]: + return [entry['temperature'] for entry in self._weather if datetime.now().date() == datetime.fromisoformat(entry['startTime']).date()] + + def gen_rise_and_set(self): + lat, lng = self._lat_long + tz = datetime.now().date() + sun = Sun(lat, lng) + sun_rise = sun.get_local_sunrise_time(tz) + sun_set = sun.get_local_sunset_time(tz) + return sun_rise, sun_set + @property + def get_api(self): + return self._api_caller + + @property + def get_lat_long(self) -> Tuple[float, float]: + return self._lat_long + + @property + def get_wind_speed(self) -> int: + return self._wind_speed + + @property + def get_daily(self) -> Dict[str, str]: + return self._daily + + @property + def get_wind_deg(self) -> int: + return self._wind_deg + + @property + def get_precipitation(self) -> int: + return self._pop + + @property + def get_uv(self) -> int: + return self._uv + + @property + def get_place(self) -> str: + return self._place + + @property + def get_weather(self) -> Dict[str, str]: + return self._weather + + @property + def get_conditions(self) -> str: + return self._conditions + + @property + def get_weather_icon(self) -> str: + return self._weather_icon + + @property + def get_temp(self) -> int: + return self._temp + + @property + def get_feels_like(self) -> int: + return self._feels_like + + @property + def get_min_temp(self) -> int: + return self._min_temp + + @property + def get_max_temp(self) -> int: + return self._max_temp + + @property + def get_humidity(self) -> None: + return self._humidity + + @property + def get_wind(self) -> Dict: + return self._wind + + @property + def get_time(self) -> datetime: + return self._time + + @property + def get_sunrise(self) -> datetime: + return self._sunrise + + @property + def get_sunset(self) -> datetime: + return self._sunset + + def calculate_duration_of_daylight(self) -> timedelta: + return self._sunset - self._time diff --git a/main.py b/main.py index 87a3790..0903fb6 100755 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ RGBMatrix ) from lib.upgrade.upgrade import Upgrader -from lib.weather.weather import WeatherApi +from lib.weather.normal import WeatherApi from matrix.stock.stockmatrix import StockMatrix from lib.stock.stocks import StockApi from lib.sports.sports import SportApi diff --git a/matrix/sport/sportmatrix.py b/matrix/sport/sportmatrix.py index aceb5df..a9fd461 100755 --- a/matrix/sport/sportmatrix.py +++ b/matrix/sport/sportmatrix.py @@ -4,13 +4,14 @@ import os import time import urllib.request +import lib.sports.sportbase as sport_types from datetime import datetime -from collections import defaultdict -from typing import List, Dict, Tuple -from collections import deque +from typing import Dict, Tuple + +from sportsipy.nhl.schedule import Schedule from matrix.error import ErrorMatrix from PIL import ImageFont, Image, ImageDraw -from lib.sports.sports import SportFinal +from lib.sports.sports import SportTransform, Sport from matrix.matrix import Matrix class SportMatrix(Matrix): @@ -18,52 +19,52 @@ def __init__(self, matrix, api, logger: Logger) -> None: self.matrix = matrix self.api = api self.logger = logger + def __str__(self) -> str: return "SportMatrix" - async def poll_api(self) -> SportFinal: - return SportFinal(await self.api.run()) - def divisions(self, standings: List[Dict]) -> Tuple[List[str], List[str]]: - leagues = {league['league']: [] for league in standings} - for team in standings: - leagues[team['league']].append(f"{team['position']}: {team['name']}") - return leagues - def determine_nextgame(self, nextgame_api): - status: Tuple = ("FT", "ABD") - for game in nextgame_api: - if "IN" in game['status']: - self.logger.debug(f"In Game") - # During the game - return game - if game['status'] == "FT" and datetime.fromtimestamp(game['timestamp']).date() == datetime.today().date(): - # Same Day will display for the rest of the day - self.logger.debug("Game is finished but still same day") - return game - if game['status'] not in status: - return game + async def poll_api(self) -> Sport: + sport = SportTransform(await self.api.run()) + if not sport.api_result: + return None + return Sport( + team_name=sport.team_name, + sport=sport.get_sport, + logo=sport.get_logo, + api=sport.get_api, + standings=sport.get_standings, + schedule=sport.get_schedule, + next_game=sport.get_next_game, + wins=sport.get_wins, + losses=sport.get_losses, + ) def away_team(self, nextgame_data): - away = nextgame_data['teams']['away'] - away.update(nextgame_data['score']['away']) - return away + if nextgame_data.homeoraway.lower() == "home": + return nextgame_data.team + else: + return nextgame_data.opposing_team + def home_team(self, nextgame_data): - home = nextgame_data['teams']['home'] - home.update(nextgame_data['score']['home']) - return home + if nextgame_data.homeoraway.lower() == "away": + return nextgame_data.team + else: + return nextgame_data.opposing_team def get_logo(self, logo_url: str, name: str) -> str: + name = name.replace(" ", "_") file_name: str = f"/tmp/{name}.png" if not os.path.isfile(file_name): urllib.request.urlretrieve(logo_url, file_name) return file_name - def build_in_game_image(self, nextgame: Dict): + def build_in_game_image(self, nextgame): middle_image = self.make_new_image((34,16)) middle_draw = ImageDraw.Draw(middle_image) font = ImageFont.truetype("/usr/share/fonts/fonts/04B_03B_.TTF", 8) - status = nextgame['status'] + status = nextgame.status self.logger.debug(f"status: {status}") - score = (nextgame['teams']['home']['total'], nextgame['teams']['away']['total']) + score = (nextgame.score.team, nextgame.score.opposing_team) self.logger.debug(f"Score: {score}") middle_draw.multiline_text((12,0), f"{status}\n{score[0]}-{score[1]}", font=font) return middle_image, (15, 0) @@ -72,57 +73,49 @@ def build_finished_game_image(self, nextgame): middle_image = self.make_new_image((34,16)) middle_draw = ImageDraw.Draw(middle_image) font = ImageFont.truetype("/usr/share/fonts/fonts/04B_03B_.TTF", 8) - status = nextgame['status'] - score = (nextgame['teams']['home']['total'], nextgame['teams']['away']['total']) + status = nextgame.status + score = (nextgame.score.team, nextgame.score.opposing_team) middle_draw.multiline_text((12,0), f"{status}\n{score[0]}-{score[1]}", font=font) return middle_image, (15, 0) def check_offseason(self, api) -> bool: try: - start_time, end_time = api.get_timestamps[0][1], api.get_timestamps[-1][1] - if datetime.fromtimestamp(start_time) <= datetime.now() <= datetime.fromtimestamp(end_time): + start_time, end_time = api.schedule.positions[0].timestamp, api.schedule.positions[-1].timestamp + if start_time <= datetime.now() <= end_time: return True except Exception: return False - def build_next_game_image(self, nextgame: Dict): + def build_next_game_image(self, nextgame): middle_image = self.make_new_image((34,16)) middle_draw = ImageDraw.Draw(middle_image) font = ImageFont.truetype("/usr/share/fonts/fonts/04B_03B_.TTF", 8) - time = datetime.fromtimestamp(nextgame['timestamp']).strftime("%I:%M%p %a") + time = nextgame.timestamp.strftime("%m-%d %a") formatted_time = time.split() - formatted_time[0] = f" {formatted_time[0]}" + formatted_time[0] = f" {formatted_time[0]}" time = "\n ".join(formatted_time) middle_draw.multiline_text((0,0), f'{time}', font=font) return middle_image, (15, 0) def build_home_away_image(self, nextgame): - self.logger.debug(f"Building Home away image") home_data = self.home_team(nextgame) - self.logger.debug(f"Home Data {home_data}") - home_logo = Image.open(self.get_logo(home_data['logo'], home_data['id'])) - self.logger.debug(f"Got Logo {home_logo}") + home_logo = Image.open(self.get_logo(home_data.logo.url, home_data.name)) home_logo.thumbnail((16,16)) away_data = self.away_team(nextgame) - self.logger.debug(f"Away Data: {away_data}") - away_logo = Image.open(self.get_logo(away_data['logo'], away_data['id'])) - self.logger.debug(f"Away Logo {away_logo}") + away_logo = Image.open(self.get_logo(away_data.logo.url, away_data.name)) away_logo.thumbnail((16,16)) return (home_logo, (-2,0)), (away_logo, (50, 0)) def build_middle_nextgame(self, api) -> Image: - nextgame = self.determine_nextgame(api.next_game) - in_game_status = ("IN", "Q", "OT", "BT", "HT", "P") - if nextgame['status'] in in_game_status: - return self.build_in_game_image(nextgame) - elif "NS" == nextgame['status']: - return self.build_next_game_image(nextgame) - elif "FT" == nextgame['status']: - return self.build_finished_game_image(nextgame) + if api.next_game.status == sport_types.GameStatus.InGame: + return self.build_in_game_image(api.next_game) + elif sport_types.GameStatus.NotStarted == api.next_game.status: + return self.build_next_game_image(api.next_game) + elif sport_types.GameStatus.Finished == api.next_game.status: + return self.build_finished_game_image(api.next_game) def build_middle_image(self, api) -> Image: - nextgame = self.determine_nextgame(api.next_game) - home_image, away_image = self.build_home_away_image(nextgame) + home_image, away_image = self.build_home_away_image(api.next_game) middle_image = self.build_middle_nextgame(api) master_middle_image = self.make_new_image((64, 16)) master_middle_image.paste(home_image[0], home_image[1]) @@ -136,16 +129,15 @@ def build_top_home_away_images(self, nextgame: Dict, xpos: int) -> Tuple: top_home_draw = ImageDraw.Draw(top_home_image) top_away_image = self.make_new_image((22,8)) top_away_draw = ImageDraw.Draw(top_away_image) - hometeam = nextgame['teams']['home']['name'] - awayteam = nextgame['teams']['away']['name'] + hometeam = nextgame.team.name + awayteam = nextgame.opposing_team.name top_home_draw.text((-xpos,0), hometeam, font=font) top_away_draw.text((-xpos,0), awayteam, font=font) return top_home_image, top_away_image def build_top_image(self, api: Dict, xpos: int) -> Tuple: - nextgame = self.determine_nextgame(api.next_game) master_top_image = self.make_new_image((64, 8)) - home_image, away_image = self.build_top_home_away_images(nextgame, xpos) + home_image, away_image = self.build_top_home_away_images(api.next_game, xpos) top_middle_image = self.make_new_image((22,8)) top_middle_draw = ImageDraw.Draw(top_middle_image) top_middle_draw.text((5,0), "VS", font=ImageFont.truetype("/usr/share/fonts/fonts/04B_03B_.TTF", 8)) @@ -162,12 +154,8 @@ def build_standings_image(self, api, xpos) -> Tuple[int, int]: standings_draw = ImageDraw.Draw(standings_image) scrolling_font = ImageFont.truetype("/usr/share/fonts/fonts/04B_03B_.TTF", 8) color = (156,163,173) - # Can't Have multiple images and or buffers - divs = self.divisions(api.get_standings) - master = [] - for league, names in divs.items(): - master.append(f"{league} " + " ".join(names)) - text = " ".join(master) + + text = " ".join(api) standings_draw.text( (-xpos, 0), text, @@ -178,89 +166,36 @@ def build_standings_image(self, api, xpos) -> Tuple[int, int]: return standings_image, (0, 25) async def render(self, api, loop): - try: + if not api: + raise Exception("Error Occurrred inside of the sport matrix") self.clear() self.reload_image() - if not api.get_error[0]: - raise Exception(api.get_error) - if 'baseball' in api.get_sport: - self.logger.info("Found Baseball, Displaying Baseball Matrix") - if self.check_offseason(api): - xpos = 0 - xpos_for_top = 0 - while xpos < 2700: - self.reload_image() - images = ( - self.build_standings_image(api, xpos), - self.build_middle_image(api), - self.build_top_image(api, xpos_for_top), - ) - for image, position in images: - self.paste_image(image, position) - await self.render_image() - xpos +=1 - xpos_for_top += 1 - if xpos_for_top == 100: - xpos_for_top = 0 - time.sleep(3) if xpos == 1 else time.sleep(.001) - else: - font = ImageFont.truetype("/usr/share/fonts/fonts/04b24.otf", 14) - self.draw_multiline_text((0, 0), "Baseball\nOffseason", font=font) - await self.render_image() - time.sleep(30) - - if 'basketball' in api.get_sport: - self.logger.info("Found Basketball, Displaying Basketball Matrix") - if self.check_offseason(api): - xpos = 0 - xpos_for_top = 0 - while xpos < 2700: - self.reload_image() - images = ( - self.build_standings_image(api, xpos), - self.build_middle_image(api), - self.build_top_image(api, xpos_for_top), - ) - for image, position in images: - self.paste_image(image, position) - self.render_image() - xpos +=1 - xpos_for_top += 1 - if xpos_for_top == 100: - xpos_for_top = 0 - time.sleep(3) if xpos == 1 else time.sleep(.01) - else: - font = ImageFont.truetype("/usr/share/fonts/fonts/04b24.otf", 14) - self.draw_multiline_text((0, 0), "Basketball\nOffseason", font=font) + if self.check_offseason(api): + xpos = 0 + xpos_for_top = 0 + positions = [f"{team.position}. {team.name}" for team in api.standings] + while xpos < 2700: + self.reload_image() + images = ( + self.build_standings_image(positions, xpos), + self.build_middle_image(api), + self.build_top_image(api, xpos_for_top), + ) + for image, position in images: + self.paste_image(image, position) await self.render_image() - time.sleep(30) + xpos +=1 + xpos_for_top += 1 + if xpos_for_top == 100: + xpos_for_top = 0 + time.sleep(3) if xpos == 1 else time.sleep(.001) + else: + font = ImageFont.truetype("/usr/share/fonts/fonts/04b24.otf", 14) + self.draw_multiline_text((0, 0), "Baseball\nOffseason", font=font) + await self.render_image() + time.sleep(30) - if 'hockey' in api.get_sport: - self.logger.info("Found Hockey, Displaying Hockey Matrix") - if self.check_offseason(api): - xpos = 0 - xpos_for_top = 0 - while xpos < 2700: - self.reload_image() - images = ( - self.build_standings_image(api, xpos), - self.build_middle_image(api), - self.build_top_image(api, xpos_for_top), - ) - for image, position in images: - self.paste_image(image, position) - self.render_image() - xpos +=1 - xpos_for_top += 1 - if xpos_for_top == 100: - xpos_for_top = 0 - time.sleep(3) if xpos == 1 else time.sleep(.01) - else: - font = ImageFont.truetype("/usr/share/fonts/fonts/04b24.otf", 14) - self.draw_multiline_text((0, 0), "Hockey\nOffseason", font=font) - await self.render_image() - time.sleep(30) except Exception as e: self.logger.error(e) error_matrix = ErrorMatrix(self.matrix, self.logger, "Sports Matrix") diff --git a/matrix/weathermatrix.py b/matrix/weathermatrix.py index 466a6cf..2bde2a0 100755 --- a/matrix/weathermatrix.py +++ b/matrix/weathermatrix.py @@ -5,22 +5,43 @@ from datetime import datetime from PIL import ImageFont from matrix.matrix import Matrix -from lib.weather.weather import ( - Weather, - build_weather_icons -) +import lib.weather.weatherbase as base +from lib.weather.normal import NormalizedWeather class WeatherMatrix(Matrix): def __init__(self, matrix, api: Dict, logger) -> None: self.matrix = matrix self.api = api self.logger = logger - self.icons = build_weather_icons() def __str__(self) -> str: return "WeatherMatrix" - async def poll_api(self) -> Weather: - return Weather(await self.api.run()) + + async def poll_api(self): + result: NormalizedWeather = await self.api.run_weather() + return base.Weather( + api=result.get_api, + location=result.get_lat_long, + location_name=result.get_place, + current=base.CurrentWeather( + conditions=result.get_conditions, + temp=result.get_temp, + feels_like=result.get_feels_like, + wind_speed=result.get_wind_speed, + humidity=result.get_humidity, + perciptation_chance=result.get_precipitation, + uv=result.get_uv, + wind_direction=result.get_wind_deg, + weather_icon=result.get_icon, + ), + dayforcast=base.DayForcast( + todayhigh=result.get_max_temp, + todaylow=result.get_min_temp, + sunrise=result.get_sunrise, + sunset=result.get_sunset + ) + ) + def get_temp_color(self, temp: int) -> Tuple[int, int, int]: if temp >= 100: @@ -37,84 +58,81 @@ def get_temp_color(self, temp: int) -> Tuple[int, int, int]: def render_temp(self, api) -> None: font: ImageFont = ImageFont.truetype("/usr/share/fonts/retro_computer.ttf", 7) self.draw_text((0, 8), "T:", font=font) - self.draw_text((10, 8), f"{str(int(api.get_temp))}F", font=font, fill=self.get_temp_color(int(api.get_temp))) + self.draw_text((10, 8), f"{str(int(api.current.temp))}F", font=font, fill=self.get_temp_color(int(api.current.temp))) self.draw_text((30, 8), "R:", font=font) - self.draw_text((40, 8), f"{str(int(api.get_feels_like))}F", font=font, fill=self.get_temp_color(int(api.get_temp))) + self.draw_text((40, 8), f"{str(int(api.current.feels_like))}F", font=font, fill=self.get_temp_color(int(api.current.feels_like))) self.draw_text((1, 18), f"H:", font=font) - self.draw_text((10, 18), f"{str(int(api.get_max_temp))}F", font=font, fill=self.get_temp_color(int(api.get_temp))) + self.draw_text((10, 18), f"{str(int(api.dayforcast.todayhigh))}F", font=font, fill=self.get_temp_color(int(api.dayforcast.todayhigh))) self.draw_text((30, 18), f"L:", font=font) - self.draw_text((40, 18), f"{str(int(api.get_min_temp))}F", font=font, fill=self.get_temp_color(int(api.get_temp))) + self.draw_text((40, 18), f"{str(int(api.dayforcast.todaylow))}F", font=font, fill=self.get_temp_color(int(api.dayforcast.todaylow))) - def render_icon(self, api: Weather) -> None: + def render_icon(self, api) -> None: font: ImageFont = ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 9) - owm_wxcode: int = int(api.get_weather[0]['id']) + owm_wxcode: int = api.current.weather_icon.owmcode if owm_wxcode in range(200,299): # Thunderstorm Class - owm_icon = 200 color: Tuple[int] = (254, 204, 1) elif owm_wxcode in range(300,399): # Drizzle Class - owm_icon = 300 color: Tuple[int] = (220,220,220) elif owm_wxcode in range(500,599): # Rain Class - owm_icon = 500 color: Tuple[int] = (108, 204, 228) elif owm_wxcode in range(600,699): # Snow Class - owm_icon = 600 color: Tuple[int] = (255,255,255) elif owm_wxcode in range(700, 780): - owm_icon = 711 color: Tuple[int] = (192, 192, 192) elif owm_wxcode == 800: # Sunny - if api.get_sunset > datetime.now(): - owm_icon = 800 + if api.dayforcast.sunset.replace(tzinfo=None) > datetime.now(): color: Tuple[int] = (220, 149, 3) else: - owm_icon = 806 color: Tuple[int] = (255,255,255) elif owm_wxcode in range(801,805): - # Rain Class - owm_icon = 801 color: Tuple[int] = (220,220,220) else: owm_icon = owm_wxcode - weather_icon = self.icons[str(owm_icon)] - self.draw_text((50, 0), weather_icon.get_font, font, fill=color) + self.draw_text((50, 0), api.current.weather_icon.font, font, fill=color) - def render_location(self, api: Weather) -> None: + def render_location(self, api: base.Weather, xpos) -> None: font = ImageFont.truetype("/usr/share/fonts/04B_03B_.TTF",8) - self.draw_text((2, 1), api.get_place, font, (0, 254, 0)) + self.draw_text((-xpos, 1), api.location_name, font, (0, 254, 0)) - def render_humidity (self, api: Weather) -> None: + def render_humidity (self, api: base.Weather) -> None: font = ImageFont.truetype("/usr/share/fonts/04B_03B_.TTF", 8) self.draw_text((2, 8), "H:", font) - self.draw_text((10, 8), f"{api.get_humidity}%", font, fill=(7, 250, 246)) + self.draw_text((10, 8), f"{api.current.humidity}%", font, fill=(7, 250, 246)) self.draw_text((27, 8), f"P:", font) - self.draw_text((34, 8), f"{int(api.get_precipitation)}%", font, fill=(7, 250, 246)) + self.draw_text((34, 8), f"{int(api.current.perciptation_chance)}%", font, fill=(7, 250, 246)) - def render_wind(self, api: Weather) -> None: + def render_wind(self, api: base.Weather) -> None: font = ImageFont.truetype("/usr/share/fonts/04B_03B_.TTF", 8) - speed = api.get_wind_speed - deg = api.get_wind_deg + speed = api.current.wind_speed + deg = api.current.wind_direction self.draw_text((1, 12), "\uf050", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 9)) self.draw_text((15, 15), f"{str(int(deg))}", font, fill=(201, 1, 253)) - self.draw_text((30, 13), "\uf042", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 9), fill=(201, 1, 253)) + if len(str(int(deg))) == 3: + self.draw_text((30, 13), "\uf042", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 9), fill=(201, 1, 253)) + elif len(str(int(deg))) == 2: + self.draw_text((29, 13), "\uf042", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 9), fill=(201, 1, 253)) + else: + self.draw_text((28, 13), "\uf042", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 9), fill=(201, 1, 253)) self.draw_text((36, 15), f"{str(int(speed))}mph", font, fill=(201, 1, 253)) - def render_time(self, api: Weather) -> None: + def render_time(self, api) -> None: font: ImageFont = ImageFont.truetype("/usr/share/fonts/04B_03B_.TTF", 8) - sunrise: datetime = api.get_sunrise.strftime("%H:%M") - sunset: datetime = api.get_sunset.strftime("%H:%M") + sunrise: datetime = api.dayforcast.sunrise.strftime("%H:%M") + sunset: datetime = api.dayforcast.sunset.strftime("%H:%M") self.draw_text((1, 18), "\uf058", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 11), fill=(255, 255, 0)) self.draw_text((7, 23), sunrise, font=font) self.draw_text((35, 18), "\uf044", font=ImageFont.truetype("/usr/share/fonts/weathericons.ttf", 11), fill=(255, 145, 0)) self.draw_text((40, 23), sunset, font=font) - def render_conditions(self, api: Weather, xpos: int) -> None: - self.draw_text((-xpos, 26), f"Conditions: {api.get_conditions}", font=ImageFont.truetype("/usr/share/fonts/04B_03B_.TTF", 8), fill=(255,255,255)) - async def render(self, api: Weather, loop) -> None: + + def render_conditions(self, api, xpos: int) -> None: + self.draw_text((-xpos, 26), f"Conditions: {api.current.conditions}", font=ImageFont.truetype("/usr/share/fonts/04B_03B_.TTF", 8), fill=(255,255,255)) + + async def render(self, api , loop) -> None: self.logger.info("Rendering Weather Matrix") self.logger.debug("Clearing Image") self.clear() @@ -125,7 +143,7 @@ async def render(self, api: Weather, loop) -> None: self.reload_image() self.render_temp(api) self.render_icon(api) - self.render_location(api) + self.render_location(api, xpos) self.render_conditions(api, xpos) xpos += 1 await self.render_image() @@ -133,19 +151,31 @@ async def render(self, api: Weather, loop) -> None: self.reload_image() self.render_temp(api) self.render_icon(api) - self.render_location(api) + self.render_location(api, 0) self.render_conditions(api, 0) await self.render_image() time.sleep(25) self.clear() self.logger.debug("Reloading Image in matrix") self.reload_image() - self.render_location(api) + xpos = 0 + self.logger.info("Loading Screen 2 of Matrix") + while xpos < 100: + self.reload_image() + self.render_location(api, xpos) + self.render_icon(api) + self.render_humidity(api) + self.render_wind(api) + self.render_time(api) + await self.render_image() + xpos += 1 + time.sleep(3) if xpos == 1 else time.sleep(.05) + self.reload_image() + self.render_location(api, 0) self.render_icon(api) self.render_humidity(api) self.render_wind(api) self.render_time(api) - self.logger.info("Loading Screen 2 of Matrix") await self.render_image() time.sleep(30) diff --git a/setup.py b/setup.py index 67c2970..c5057f6 100755 --- a/setup.py +++ b/setup.py @@ -32,7 +32,8 @@ "aiohttp", "iso6709", "sportsipy", - "wget" + "wget", + "suntime" ], setup_requires=["pytest-runner"], tests_require=["pytest==4.4.1"],