Skip to content

Commit

Permalink
Merge pull request #29 from AlecM33/pitcher-command-changes
Browse files Browse the repository at this point in the history
recent games split for pitchers, better advanced stats
  • Loading branch information
AlecM33 authored Jul 31, 2024
2 parents c7d45d8 + e2191de commit b08a3ab
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 90 deletions.
6 changes: 6 additions & 0 deletions modules/MLB-API-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const endpoints = {
hitter: (personId) => {
return 'https://statsapi.mlb.com/api/v1/people/' + personId + '/stats?stats=season,statSplits,lastXGames&group=hitting&gameType=R&sitCodes=vl,vr,risp&limit=7';
},
pitcher: (personId, lastXGamesLimit) => {
return `https://statsapi.mlb.com/api/v1/people?personIds=${personId}&hydrate=stats(type=[season,lastXGames,sabermetrics,seasonAdvanced,expectedStatistics],groups=pitching,limit=${lastXGamesLimit})`;
},
liveFeed: (gamePk, fields = []) => {
LOGGER.debug('https://statsapi.mlb.com/api/v1.1/game/' + gamePk + '/feed/live' + (fields.length > 0 ? '?fields=' + fields.join() : ''));
return 'https://statsapi.mlb.com/api/v1.1/game/' + gamePk + '/feed/live' + (fields.length > 0 ? '?fields=' + fields.join() : '');
Expand Down Expand Up @@ -224,5 +227,8 @@ module.exports = {
},
team: async (teamId) => {
return (await fetch(endpoints.team(teamId))).json();
},
pitcher: async (personId, lastXGamesLimit) => {
return (await fetch(endpoints.pitcher(personId, lastXGamesLimit))).json();
}
};
97 changes: 95 additions & 2 deletions modules/command-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ module.exports = {
mlbAPIUtil.savantPitchData(probable),
new Promise((resolve, reject) => {
if (probable) {
resolve(mlbAPIUtil.people([probable]));
resolve(mlbAPIUtil.pitcher(probable, 3));
} else {
resolve(undefined);
}
Expand Down Expand Up @@ -288,6 +288,94 @@ module.exports = {
home: (game.teams?.home?.team?.abbreviation || game.teams?.home?.abbreviation || game.gameData?.teams?.home?.abbreviation),
away: (game.teams?.away?.team?.abbreviation || game.teams?.away?.abbreviation || game.gameData?.teams?.away?.abbreviation)
};
},

buildPitchingStatsMarkdown: (pitchingStats, pitchMix, lastThree, seasonAdvanced, sabermetrics, includeExtra = false) => {
let reply = '';

if (lastThree) {
reply += '\n**Recent Games:** \n';
reply += `G: ${lastThree.gamesPlayed}, `;
reply += `ERA: ${lastThree.era}, `;
reply += `Hits: ${lastThree.hits}, `;
reply += `K: ${lastThree.strikeOuts}, `;
reply += `BB: ${lastThree.baseOnBalls}, `;
reply += `HR: ${lastThree.homeRuns}\n`;
}
reply += '**Season:** \n';
if (!pitchingStats) {
reply += 'G: 0, ';
reply += 'W-L: -, ';
reply += 'ERA: -.--, ';
reply += 'WHIP: -.--';
} else {
reply += `G: ${pitchingStats.gamesPlayed}, `;
reply += `W-L: ${pitchingStats.wins}-${pitchingStats.losses}, `;
reply += `ERA: ${pitchingStats.era}, `;
reply += `WHIP: ${pitchingStats.whip} `;
if (includeExtra && seasonAdvanced && sabermetrics) {
reply += '\n...\n';
reply += `IP: ${pitchingStats.inningsPitched}\n`;
reply += `K/BB: ${seasonAdvanced.strikesoutsToWalks}\n`;
reply += `BABIP: ${seasonAdvanced.babip}\n`;
reply += `SLG: ${seasonAdvanced.slg}\n`;
reply += `WAR: ${sabermetrics.war.toFixed(2)}\n`;
reply += `Saves/Opps: ${pitchingStats.saves}/${pitchingStats.saveOpportunities}`;
}
}
reply += '\n**Arsenal:**' + '\n';
if (pitchMix instanceof Error) {
reply += pitchMix.message;
return reply;
}
if (pitchMix && pitchMix.length > 0 && pitchMix[0].length > 0) {
reply += (() => {
let arsenal = '';
for (let i = 0; i < pitchMix[0].length; i ++) {
arsenal += pitchMix[0][i] + ' (' + pitchMix[1][i] + '%)' +
': ' + pitchMix[2][i] + ' mph, ' + pitchMix[3][i] + ' BAA' + '\n';
}
return arsenal;
})();
} else {
reply += 'No data!';
}

return reply;
},

getWeatherEmoji: (condition) => {
switch (condition) {
case 'Clear':
case 'Sunny':
return '\u2600';
case 'Cloudy':
return '\u2601';
case 'Partly Cloudy':
return '\uD83C\uDF24';
case 'Dome':
case 'Roof Closed':
return '';
case 'Drizzle':
case 'Rain':
return '\uD83C\uDF27';
case 'Snow':
return '\u2744';
case 'Overcast':
return '\uD83C\uDF2B';
default:
return '';
}
},

getScoreString: (liveFeed, currentPlayJSON) => {
const homeScore = currentPlayJSON.result.homeScore;
const awayScore = currentPlayJSON.result.awayScore;
return (currentPlayJSON.about.halfInning === 'top'
? '**' + liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + '**, ' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore
: liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + ', **' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore + '**');
}
};

Expand Down Expand Up @@ -335,7 +423,12 @@ async function resolveDoubleHeaderSelection (interaction) {
}

function parsePitchingStats (people) {
return people?.people[0]?.stats?.find(stat => stat?.group?.displayName === 'pitching')?.splits[0]?.stat;
return {
season: people?.people[0]?.stats?.find(stat => stat?.type?.displayName === 'season')?.splits[0]?.stat,
lastXGames: people?.people[0]?.stats?.find(stat => stat?.type?.displayName === 'lastXGames')?.splits[0]?.stat,
seasonAdvanced: people?.people[0]?.stats?.find(stat => stat?.type?.displayName === 'seasonAdvanced')?.splits[0]?.stat,
sabermetrics: people?.people[0]?.stats?.find(stat => stat?.type?.displayName === 'sabermetrics')?.splits[0]?.stat
};
}

/* This is not the best solution, admittedly. We are building an HTML version of the table in a headless browser, styling
Expand Down
112 changes: 24 additions & 88 deletions modules/interaction-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ module.exports = {
const probables = matchup.probables;
const hydratedHomeProbable = await commandUtil.hydrateProbable(probables.homeProbable);
const hydratedAwayProbable = await commandUtil.hydrateProbable(probables.awayProbable);

joinImages([hydratedHomeProbable.spot, hydratedAwayProbable.spot],
{ direction: 'horizontal', offset: 10, margin: 0, color: 'transparent' })
.then(async (img) => {
Expand All @@ -41,14 +40,26 @@ module.exports = {
name: (hydratedHomeProbable.handedness
? hydratedHomeProbable.handedness + 'HP **'
: '**') + (probables.homeProbableLastName || 'TBD') + '** (' + probables.homeAbbreviation + ')',
value: buildPitchingStatsMarkdown(hydratedHomeProbable.pitchingStats, hydratedHomeProbable.pitchMix),
value: commandUtil.buildPitchingStatsMarkdown(
hydratedHomeProbable.pitchingStats.season,
hydratedHomeProbable.pitchMix,
hydratedHomeProbable.pitchingStats.lastXGames,
hydratedHomeProbable.pitchingStats.seasonAdvanced,
hydratedHomeProbable.pitchingStats.sabermetrics
),
inline: true
})
.addFields({
name: (hydratedAwayProbable.handedness
? hydratedAwayProbable.handedness + 'HP **'
: '**') + (probables.awayProbableLastName || 'TBD') + '** (' + probables.awayAbbreviation + ')',
value: buildPitchingStatsMarkdown(hydratedAwayProbable.pitchingStats, hydratedAwayProbable.pitchMix),
value: commandUtil.buildPitchingStatsMarkdown(
hydratedAwayProbable.pitchingStats.season,
hydratedAwayProbable.pitchMix,
hydratedAwayProbable.pitchingStats.lastXGames,
hydratedAwayProbable.pitchingStats.seasonAdvanced,
hydratedAwayProbable.pitchingStats.sabermetrics
),
inline: true
});
await interaction.followUp({
Expand Down Expand Up @@ -390,7 +401,14 @@ module.exports = {
'## ' + (pitcherInfo.handedness
? pitcherInfo.handedness + 'HP **'
: '**') + (pitcher.fullName || 'TBD') + '** (' + abbreviation + ')' +
buildPitchingStatsMarkdown(pitcherInfo.pitchingStats, pitcherInfo.pitchMix, true))
commandUtil.buildPitchingStatsMarkdown(
pitcherInfo.pitchingStats.season,
pitcherInfo.pitchMix,
pitcherInfo.pitchingStats.lastXGames,
pitcherInfo.pitchingStats.seasonAdvanced,
pitcherInfo.pitchingStats.sabermetrics,
true
))
.setColor((halfInning === 'top'
? globalCache.values.game.homeTeamColor
: globalCache.values.game.awayTeamColor)
Expand Down Expand Up @@ -467,7 +485,7 @@ module.exports = {
liveFeed.gameData.teams.home.teamName.toLowerCase().replaceAll(' ', '-') + '/' +
liveFeed.gameData.datetime.officialDate.replaceAll('-', '/') +
'/' + game.gamePk + '/play/' + scoringPlayIndex;
links.push(getScoreString(liveFeed, play) + ' [' + play.result.description.trim() + '](<' + link + '>)\n');
links.push(commandUtil.getScoreString(liveFeed, play) + ' [' + play.result.description.trim() + '](<' + link + '>)\n');
});
// discord limits messages to 2,000 characters. We very well might need a couple messages to link everything.
const messagesNeeded = Math.ceil(liveFeed.liveData.plays.scoringPlays.length / globals.SCORING_PLAYS_PER_MESSAGE);
Expand Down Expand Up @@ -552,93 +570,11 @@ module.exports = {
components: [],
content: weather && Object.keys(weather).length > 0
? 'Weather at ' + currentLiveFeed.gameData.venue.name + ':\n' +
getWeatherEmoji(weather.condition) + ' ' + weather.condition + '\n' +
commandUtil.getWeatherEmoji(weather.condition) + ' ' + weather.condition + '\n' +
'\uD83C\uDF21 ' + weather.temp + '°\n' +
'\uD83C\uDF43 ' + weather.wind
: 'Not available yet - check back an hour or two before game time.'
});
}
}
};

function getWeatherEmoji (condition) {
switch (condition) {
case 'Clear':
case 'Sunny':
return '\u2600';
case 'Cloudy':
return '\u2601';
case 'Partly Cloudy':
return '\uD83C\uDF24';
case 'Dome':
case 'Roof Closed':
return '';
case 'Drizzle':
case 'Rain':
return '\uD83C\uDF27';
case 'Snow':
return '\u2744';
case 'Overcast':
return '\uD83C\uDF2B';
default:
return '';
}
}

function getScoreString (liveFeed, currentPlayJSON) {
const homeScore = currentPlayJSON.result.homeScore;
const awayScore = currentPlayJSON.result.awayScore;
return (currentPlayJSON.about.halfInning === 'top'
? '**' + liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + '**, ' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore
: liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + ', **' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore + '**');
}

function buildPitchingStatsMarkdown (pitchingStats, pitchMix, includeExtra = false) {
let reply = '\n';
if (!pitchingStats) {
reply += 'G: -\n' +
'W-L: -\n' +
'ERA: -.--\n' +
'WHIP: -.--' +
(includeExtra
? '\nK/9: -.--\n' +
'BB/9: -.--\n' +
'H/9: -.--\n' +
'HR/9: -.--\n' +
'Saves/Opps: -/-'
: '');
} else {
reply += 'G: ' + pitchingStats.gamesPlayed + '\n' +
'W-L: ' + pitchingStats.wins + '-' + pitchingStats.losses + '\n' +
'ERA: ' + pitchingStats.era + '\n' +
'WHIP: ' + pitchingStats.whip +
(includeExtra
? '\nK/9: ' + pitchingStats.strikeoutsPer9Inn + '\n' +
'BB/9: ' + pitchingStats.walksPer9Inn + '\n' +
'H/9: ' + pitchingStats.hitsPer9Inn + '\n' +
'HR/9: ' + pitchingStats.homeRunsPer9 + '\n' +
'Saves/Opps: ' + pitchingStats.saves + '/' + pitchingStats.saveOpportunities
: '');
}
reply += '\n**Arsenal:**' + '\n';
if (pitchMix instanceof Error) {
reply += pitchMix.message;
return reply;
}
if (pitchMix && pitchMix.length > 0 && pitchMix[0].length > 0) {
reply += (() => {
let arsenal = '';
for (let i = 0; i < pitchMix[0].length; i ++) {
arsenal += pitchMix[0][i] + ' (' + pitchMix[1][i] + '%)' +
': ' + pitchMix[2][i] + ' mph, ' + pitchMix[3][i] + ' BAA' + '\n';
}
return arsenal;
})();
} else {
reply += 'No data!';
}

return reply;
}
Empty file added {
Empty file.

0 comments on commit b08a3ab

Please sign in to comment.