Skip to content

Commit

Permalink
Merge pull request #196 from cwendt94/InitialBaseball
Browse files Browse the repository at this point in the history
Initial Baseball Implementation
  • Loading branch information
cwendt94 authored Apr 5, 2021
2 parents fe03912 + c3e9e39 commit def1e28
Show file tree
Hide file tree
Showing 13 changed files with 443 additions and 3 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
![](https://github.com/cwendt94/espn-api/workflows/Espn%20API%20Integration%20Test/badge.svg) [![codecov](https://codecov.io/gh/cwendt94/espn-api/branch/master/graphs/badge.svg)](https://codecov.io/gh/cwendt94/espn-api) [![Join the chat at https://gitter.im/ff-espn-api/community](https://badges.gitter.im/ff-espn-api/community.svg)](https://gitter.im/ff-espn-api/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![PyPI version](https://badge.fury.io/py/espn-api.svg)](https://badge.fury.io/py/espn-api)

## ESPN API
## [NOTICE] Currently username and password are not working, ESPN recently changed their authentication. You can still access your private league using SWID and ESPN_S2.
This package uses ESPN's Fantasy API to extract data from any public or private league for **Fantasy Football and Basketball**.
## [NOTICE] Username and password will be removed soon. You can access your private league using SWID and ESPN_S2.
This package uses ESPN's Fantasy API to extract data from any public or private league for **Fantasy Football and Basketball (Hockey and Baseball in development)**.
Please feel free to make suggestions, bug reports, and pull request for features or fixes!

This package was inspired and based off of [rbarton65/espnff](https://github.com/rbarton65/espnff).
Expand All @@ -27,6 +27,10 @@ pip install espn_api
from espn_api.football import League
# Basketball API
from espn_api.basketball import League
# Hockey API
from espn_api.hockey import League
# Baseball API
from espn_api.baseball import League
# Init
league = League(league_id=222, year=2019)
```
Expand Down
10 changes: 10 additions & 0 deletions espn_api/baseball/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
__all__ = ['League',
'Team',
'Player',
'Matchup',
]

from .league import League
from .team import Team
from .player import Player
from .matchup import Matchup
30 changes: 30 additions & 0 deletions espn_api/baseball/activity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from .constant import ACTIVITY_MAP

class Activity(object):
def __init__(self, data, player_map, get_team_data):
self.actions = [] # List of tuples (Team, action, player)
self.date = data['date']
for msg in data['messages']:
team = ''
action = 'UNKNOWN'
player = ''
msg_id = msg['messageTypeId']
if msg_id == 244:
team = get_team_data(msg['from'])
elif msg_id == 239:
team = get_team_data(msg['for'])
else:
team = get_team_data(msg['to'])
if msg_id in ACTIVITY_MAP:
action = ACTIVITY_MAP[msg_id]
if msg['targetId'] in player_map:
player = player_map[msg['targetId']]
self.actions.append((team, action, player))

def __repr__(self):
return 'Activity(' + ' '.join("(%s,%s,%s)" % tup for tup in self.actions) + ')'





98 changes: 98 additions & 0 deletions espn_api/baseball/constant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
POSITION_MAP = {
0: 'C',
1: '1B',
2: '2B',
3: '3B',
4: 'SS',
5: 'OF',
6: '2B/SS',
7: '1B/3B',
8: 'DH',
9: '9', # For unknown use stat number
10: '10',
11: 'DH',
12: 'UTIL',
13: 'P',
14: 'SP',
15: 'RP',
16: 'BE',
17: 'IL',
18: '18',
19: '19'
# reverse TODO
}

PRO_TEAM_MAP = {
0: 'FA',
1: 'Bal',
2: 'Bos',
3: 'LAA',
4: 'ChW',
5: 'Cle',
6: 'Det',
7: 'KC',
8: 'Mil',
9: 'Min',
10: 'NYY',
11: 'Oak',
12: 'Sea',
13: 'Tex',
14: 'Tor',
15: 'Atl',
16: 'ChC',
17: 'Cin',
18: 'Hou',
19: 'LAD',
20: 'Wsh',
21: 'NYM',
22: 'Phi',
23: 'Pit',
24: 'StL',
25: 'SD',
26: 'SF',
27: 'Col',
28: 'Mia',
29: 'Ari',
30: 'TB',
}

STATS_MAP = {
0: 'AB',
1: 'H',
2: 'AVG',
5: 'HR',
10: 'BB',
16: 'PA',
17: 'OBP',
20: 'R',
21: 'RBI',
23: 'SB',
34: 'OUTS',
35: 'BATTERS',
36: 'PITCHES',
37: 'P_H',
39: 'P_BB',
41: 'WHIP',
42: 'HBP',
44: 'P_R',
45: 'ER',
46: 'P_HR',
47: 'ERA',
48: 'K',
53: 'W',
54: 'L',
57: 'SV',
99: 'STARTER',
}

ACTIVITY_MAP = {
178: 'FA ADDED',
180: 'WAIVER ADDED',
179: 'DROPPED',
181: 'DROPPED',
239: 'DROPPED',
244: 'TRADED',
'FA': 178,
'WAIVER': 180,
'TRADED': 244
}
120 changes: 120 additions & 0 deletions espn_api/baseball/league.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import datetime
import time
import json
import math
from typing import List, Tuple
import pdb

from ..base_league import BaseLeague
from .team import Team
from .player import Player
from .matchup import Matchup
from .constant import PRO_TEAM_MAP
from.activity import Activity
from .constant import POSITION_MAP, ACTIVITY_MAP

class League(BaseLeague):
'''Creates a League instance for Public/Private ESPN league'''
def __init__(self, league_id: int, year: int, espn_s2=None, swid=None, username=None, password=None, debug=False):
super().__init__(league_id=league_id, year=year, sport='mlb', espn_s2=espn_s2, swid=swid, username=username, password=password, debug=debug)

data = self._fetch_league()
self._fetch_teams(data)

def _fetch_league(self):
data = super()._fetch_league()
self._fetch_players()
return(data)

def _fetch_teams(self, data):
'''Fetch teams in league'''
super()._fetch_teams(data, TeamClass=Team)

# replace opponentIds in schedule with team instances
for team in self.teams:
team.division_name = self.settings.division_map.get(team.division_id, '')
for week, matchup in enumerate(team.schedule):
for opponent in self.teams:
if matchup.away_team == opponent.team_id:
matchup.away_team = opponent
if matchup.home_team == opponent.team_id:
matchup.home_team = opponent

def standings(self) -> List[Team]:
standings = sorted(self.teams, key=lambda x: x.final_standing if x.final_standing != 0 else x.standing, reverse=False)
return standings

def scoreboard(self, matchupPeriod: int = None) -> List[Matchup]:
'''Returns list of matchups for a given matchup period'''
if not matchupPeriod:
matchupPeriod=self.currentMatchupPeriod

params = {
'view': 'mMatchup',
}
data = self.espn_request.league_get(params=params)
schedule = data['schedule']
matchups = [Matchup(matchup) for matchup in schedule if matchup['matchupPeriodId'] == matchupPeriod]

for team in self.teams:
for matchup in matchups:
if matchup.home_team == team.team_id:
matchup.home_team = team
elif matchup.away_team == team.team_id:
matchup.away_team = team

return matchups

def get_team_data(self, team_id: int) -> Team:
for team in self.teams:
if team_id == team.team_id:
return team
return None

def recent_activity(self, size: int = 25, msg_type: str = None) -> List[Activity]:
'''Returns a list of recent league activities (Add, Drop, Trade)'''
if self.year < 2019:
raise Exception('Cant use recent activity before 2019')

msg_types = [178,180,179,239,181,244]
if msg_type in ACTIVITY_MAP:
msg_types = [ACTIVITY_MAP[msg_type]]
params = {
'view': 'kona_league_communication'
}

filters = {"topics":{"filterType":{"value":["ACTIVITY_TRANSACTIONS"]},"limit":size,"limitPerMessageSet":{"value":25},"offset":0,"sortMessageDate":{"sortPriority":1,"sortAsc":False},"sortFor":{"sortPriority":2,"sortAsc":False},"filterIncludeMessageTypeIds":{"value":msg_types}}}
headers = {'x-fantasy-filter': json.dumps(filters)}
data = self.espn_request.league_get(extend='/communication/', params=params, headers=headers)
data = data['topics']
activity = [Activity(topic, self.player_map, self.get_team_data) for topic in data]

return activity

def free_agents(self, week: int=None, size: int=50, position: str=None, position_id: int=None) -> List[Player]:
'''Returns a List of Free Agents for a Given Week\n
Should only be used with most recent season'''

if self.year < 2019:
raise Exception('Cant use free agents before 2019')
if not week:
week = self.current_week

slot_filter = []
if position and position in POSITION_MAP:
slot_filter = [POSITION_MAP[position]]
if position_id:
slot_filter.append(position_id)


params = {
'view': 'kona_player_info',
'scoringPeriodId': week,
}
filters = {"players":{"filterStatus":{"value":["FREEAGENT","WAIVERS"]},"filterSlotIds":{"value":slot_filter},"limit":size,"sortPercOwned":{"sortPriority":1,"sortAsc":False},"sortDraftRanks":{"sortPriority":100,"sortAsc":True,"value":"STANDARD"}}}
headers = {'x-fantasy-filter': json.dumps(filters)}

data = self.espn_request.league_get(params=params, headers=headers)
players = data['players']

return [Player(player) for player in players]
47 changes: 47 additions & 0 deletions espn_api/baseball/matchup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pdb

from .constant import STATS_MAP

class Matchup(object):
'''Creates Matchup instance'''
def __init__(self, data):
self.home_team_live_score = None
self.away_team_live_score = None
self._fetch_matchup_info(data)

def __repr__(self):
# TODO: use final score when that's available?
# writing this too early to see if data['home']['totalPoints'] is final score
# it might also be used for points leagues instead of category leagues
if not self.away_team_live_score:
return 'Matchup(%s, %s)' % (self.home_team, self.away_team, )
else:
return 'Matchup(%s %s - %s %s)' % (self.home_team,
str(round(self.home_team_live_score, 1)),
str(round(self.away_team_live_score, 1)),
self.away_team)

def _fetch_matchup_info(self, data):
'''Fetch info for matchup'''
self.home_team = data['home']['teamId']
self.home_final_score = data['home']['totalPoints']
self.away_team = data['away']['teamId']
self.away_final_score = data['away']['totalPoints']
self.winner = data['winner']
self.home_team_cats = None
self.away_team_cats = None

# if stats are available
if 'cumulativeScore' in data['home'].keys() and data['home']['cumulativeScore']['scoreByStat']:

self.home_team_live_score = (data['home']['cumulativeScore']['wins'] +
data['home']['cumulativeScore']['ties']/2)
self.away_team_live_score = (data['away']['cumulativeScore']['wins'] +
data['away']['cumulativeScore']['ties']/2)

self.home_team_cats = { STATS_MAP[i]: {'score': data['home']['cumulativeScore']['scoreByStat'][i]['score'],
'result': data['home']['cumulativeScore']['scoreByStat'][i]['result']} for i in data['home']['cumulativeScore']['scoreByStat'].keys()}

self.away_team_cats = { STATS_MAP[i]: {'score': data['away']['cumulativeScore']['scoreByStat'][i]['score'],
'result': data['away']['cumulativeScore']['scoreByStat'][i]['result']} for i in data['away']['cumulativeScore']['scoreByStat'].keys()}

24 changes: 24 additions & 0 deletions espn_api/baseball/player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from .constant import POSITION_MAP, PRO_TEAM_MAP, STATS_MAP
from .utils import json_parsing
import pdb

class Player(object):
'''Player are part of team'''
def __init__(self, data):
self.name = json_parsing(data, 'fullName')
self.playerId = json_parsing(data, 'id')
self.position = POSITION_MAP[json_parsing(data, 'defaultPositionId') - 1]
self.lineupSlot = POSITION_MAP.get(data.get('lineupSlotId'), '')
self.eligibleSlots = [POSITION_MAP[pos] for pos in json_parsing(data, 'eligibleSlots')]
self.acquisitionType = json_parsing(data, 'acquisitionType')
self.proTeam = PRO_TEAM_MAP[json_parsing(data, 'proTeamId')]
self.injuryStatus = json_parsing(data, 'injuryStatus')
self.stats = {}

# add available stats
player = data['playerPoolEntry']['player'] if 'playerPoolEntry' in data else data['player']
self.injuryStatus = player.get('injuryStatus', self.injuryStatus)
self.injured = player.get('injured', False)

def __repr__(self):
return 'Player(%s)' % (self.name, )
Loading

0 comments on commit def1e28

Please sign in to comment.