diff --git a/espn_api/base_league.py b/espn_api/base_league.py index 4c69e5996..c82570999 100644 --- a/espn_api/base_league.py +++ b/espn_api/base_league.py @@ -120,6 +120,22 @@ def _get_all_pro_schedule(self): pro_team_schedule[team['id']] = pro_game return pro_team_schedule + def _get_offers(self, week: int = None): + '''Returns a list of free agent auction bids''' + if week is None: + bids = [] + for week in range(0, self.finalScoringPeriod+1): + data = self.espn_request.get_league_offers(week=week) + transactions = data.get('transactions', []) + if transactions: # Only append non-empty transaction lists + for t in transactions: + bids.append(t) + else: + data = self.espn_request.get_league_offers(week=week) + bids = data.get('transactions', []) + + return bids + def standings(self) -> List: standings = sorted(self.teams, key=lambda x: x.final_standing if x.final_standing != 0 else x.standing, reverse=False) return standings diff --git a/espn_api/base_offer.py b/espn_api/base_offer.py new file mode 100644 index 000000000..acf396a0e --- /dev/null +++ b/espn_api/base_offer.py @@ -0,0 +1,64 @@ +from datetime import datetime + + +class Offer(object): + def __init__(self, data, player_map, get_team_data): + status = data['status'] + self.id = data['id'] + self.dateTime = None + if status == 'CANCELED': + self.result = 'Canceled' + else: + if status == 'EXECUTED': + self.result = 'Processed' + elif status == 'FAILED_INVALIDPLAYERSOURCE': + self.result = 'Outbid' + elif status == 'FAILED_AUCTIONBUDGETEXCEEDED': + self.result = 'Budget Exceeded' + elif status == 'FAILED_POSITIONLIMIT': + self.result = 'Position Limit Exceeded' + elif status == 'FAILED_ROSTERLOCK': + self.result = 'Failed Due to Roster Lock' + elif status == 'FAILED_PLAYERALREADYDROPPED' or status == 'FAILED_ROSTERLIMIT' or status == 'PENDING': + self.result = 'Player already dropped' + else: + self.result = status + # fixes bug with unprocessed waivers stuck on "PENDING" status + if 'processDate' in data: + self.dateTime = datetime.fromtimestamp(int(data['processDate'] / 1000)) # convert from milliseconds to seconds + self.amount = data['bidAmount'] + self.teamId = data['teamId'] + self.droppedPlayer = None + for item in data['items']: + if item['type'] == 'ADD': + self.player = item['playerId'] + elif item['type'] == 'DROP' and self.result == 'Processed': + self.droppedPlayer = item['playerId'] + + def __lt__(self, other): + # sort by status, then bid amount + result_ranking = {'Processed': 7, + 'Outbid': 6, + 'Player already dropped': 5, + 'Budget Exceeded': 4, + 'Position Limit Exceeded': 3, + 'Failed Due to Roster Lock': 2, + 'CANCELLED': 1, + 'PENDING': 0} + if result_ranking[self.result] != result_ranking[other.result]: + return result_ranking[self.result] < result_ranking[other.result] + else: + # sort by bid amount + return self.amount < other.amount + + def __repr__(self): + if self.result == 'Canceled': + return 'Canceled bid' + else: + ret_string = 'Offer(Date:{0}, Player:{1}, Team:{2}, Result:{3}, Bid:{4}'.format(self.dateTime, self.player, self.teamId, + self.result, self.amount) + if self.droppedPlayer: + ret_string += ', Dropped:{0})'.format(self.droppedPlayer) + else: + ret_string += ')' + return ret_string \ No newline at end of file diff --git a/espn_api/football/league.py b/espn_api/football/league.py index 03ea5e679..9e3184b4f 100644 --- a/espn_api/football/league.py +++ b/espn_api/football/league.py @@ -9,6 +9,7 @@ from .box_player import BoxPlayer from .player import Player from .activity import Activity +from ..base_offer import Offer from .settings import Settings from .utils import power_points, two_step_dominance from .constant import POSITION_MAP, ACTIVITY_MAP, TRANSACTION_TYPES @@ -422,3 +423,56 @@ def transactions(self, scoring_period: int = None, types: Set[str] = {"FREEAGENT transactions = data['transactions'] return [Transaction(transaction, self.player_map, self.get_team_data) for transaction in transactions] + + def offers_report(self, week: int = None) -> List[Offer]: + '''Returns a list of all waiver/free agent auction offers sorted by timestamp and bid amount''' + + data = self._get_offers(week) + bids = [Offer(bid, self.player_map, self.get_team_data) for bid in data] + + if len(bids) == 0: + return [] + + # Process bids to fix missing timestamps + # For any bid without a timestamp, search for another bid for the same player + # from the same team that has a timestamp and use that timestamp + for bid in bids: + if bid.result != 'Canceled' and bid.dateTime is None: + for other_bid in bids: + if bid.id != other_bid.id and other_bid.dateTime is not None: + if other_bid.player == bid.player and other_bid.teamId == bid.teamId: + bid.dateTime = other_bid.dateTime + break + + # Group bids by report_time + reports = {} + for bid in bids: + if bid.dateTime not in reports: + reports[bid.dateTime] = [] + reports[bid.dateTime].append(bid) + + # Sort report times + sorted_report_times = sorted([t for t in reports.keys() if t is not None]) + + # Create a sorted list of offers, with the highest bid for each player first + sorted_offers = [] + for report_time in sorted_report_times: + report = reports[report_time] + # Sort by bid amount (highest first) + report.sort(reverse=True) + + # Process the bids in order + processed_players = set() + for bid in report: + # If this player hasn't been processed yet, add all related bids + if bid.player not in processed_players: + sorted_offers.append(bid) + processed_players.add(bid.player) + + # Find and add all other bids for the same player + for other_bid in report: + if other_bid.id != bid.id and other_bid.player == bid.player: + sorted_offers.append(other_bid) + + return sorted_offers + diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index 9e1079090..71f36ac38 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -151,6 +151,19 @@ def get_league_message_board(self, msg_types = None): data = self.get(params=params, extend=extend, headers=headers) return data + def get_league_offers(self, week: int): + '''Gets the league offers reports''' + params = { + 'scoringPeriodId': week, + 'view': 'mTransactions2' + } + + filters = {"transactions": {"filterType": {"value": ["WAIVER", "WAIVER_ERROR"]}}} + headers = {'x-fantasy-filter': json.dumps(filters)} + + data = self.league_get(params=params, headers=headers) + return data + def get_player_card(self, playerIds: List[int], max_scoring_period: int, additional_filters: List = None): '''Gets the player card''' params = { 'view': 'kona_playercard' }