Last active
April 13, 2023 02:45
-
-
Save evandcoleman/a2ab0e15a691ba8e8894a38836e054fd to your computer and use it in GitHub Desktop.
Scriptable widget for MLB scores
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: deep-gray; icon-glyph: magic; | |
const TEAM = "NYY"; | |
///////////////////////////////////////// | |
// | |
// Cache | |
// | |
///////////////////////////////////////// | |
class Cache { | |
constructor(name) { | |
this.fm = FileManager.iCloud(); | |
this.cachePath = this.fm.joinPath(this.fm.documentsDirectory(), name); | |
if (!this.fm.fileExists(this.cachePath)) { | |
this.fm.createDirectory(this.cachePath) | |
} | |
} | |
async read(key, expirationMinutes) { | |
try { | |
const path = this.fm.joinPath(this.cachePath, key); | |
await this.fm.downloadFileFromiCloud(path); | |
const createdAt = this.fm.creationDate(path); | |
if (expirationMinutes) { | |
if ((new Date()) - createdAt > (expirationMinutes * 60000)) { | |
this.fm.remove(path); | |
return null; | |
} | |
} | |
const value = this.fm.readString(path); | |
try { | |
return JSON.parse(value); | |
} catch(error) { | |
return value; | |
} | |
} catch(error) { | |
return null; | |
} | |
}; | |
write(key, value) { | |
const path = this.fm.joinPath(this.cachePath, key.replace('/', '-')); | |
console.log(`Caching to ${path}...`); | |
if (typeof value === 'string' || value instanceof String) { | |
this.fm.writeString(path, value); | |
} else { | |
this.fm.writeString(path, JSON.stringify(value)); | |
} | |
} | |
} | |
/////////////////////////////////////////// | |
const cache = new Cache("mlbWidgetCache"); | |
const widget = await createWidget(); | |
Script.setWidget(widget); | |
Script.complete(); | |
async function createWidget() { | |
const w = new ListWidget() | |
w.backgroundColor = new Color("#0F1011"); | |
w.setPadding(15, 10, 15, 15) | |
const mainStack = w.addStack(); | |
mainStack.layoutVertically(); | |
const { game, team } = await fetchTeam(TEAM); | |
const awayLogo = await fetchTeamLogo(game.teams.away.team.abbreviation); | |
const homeLogo = await fetchTeamLogo(game.teams.home.team.abbreviation); | |
const scoreStack = mainStack.addStack(); | |
scoreStack.layoutVertically(); | |
const awayStack = scoreStack.addStack(); | |
awayStack.centerAlignContent(); | |
const awayLogoImage = awayStack.addImage(awayLogo); | |
awayLogoImage.imageSize = new Size(42, 42); | |
awayStack.addSpacer(); | |
const awayName = awayStack.addText(game.teams.away.team.abbreviation); | |
awayName.font = Font.title2(); | |
awayStack.addSpacer(); | |
const awayRuns = awayStack.addText(`${game.linescore.teams.away.runs || ''}`); | |
awayRuns.font = Font.title2(); | |
const spacer = scoreStack.addSpacer(); | |
spacer.length = 6; | |
const homeStack = scoreStack.addStack(); | |
homeStack.centerAlignContent(); | |
const homeLogoImage = homeStack.addImage(homeLogo); | |
homeLogoImage.imageSize = new Size(42, 42); | |
homeStack.addSpacer(); | |
const homeName = homeStack.addText(game.teams.home.team.abbreviation); | |
homeName.font = Font.title2(); | |
homeStack.addSpacer(); | |
const homeRuns = homeStack.addText(`${game.linescore.teams.home.runs || ''}`); | |
homeRuns.font = Font.title2(); | |
mainStack.addSpacer(); | |
const statusStack = mainStack.addStack(); | |
statusStack.layoutHorizontally(); | |
statusStack.addSpacer(); | |
const statusText = statusStack.addText(getFormattedStatus(game)); | |
statusText.font = Font.callout(); | |
statusText.textColor = Color.lightGray(); | |
return w | |
} | |
function getFormattedStatus(game) { | |
const status = game.status.abstractGameState; | |
const innings = game.linescore.innings.length; | |
switch (status) { | |
case "Final": | |
case "Completed Early": | |
case "Game Over": | |
if (innings !== 9) { | |
return `${status}/${innings}`; | |
} else { | |
return status; | |
} | |
case "Delayed": | |
return `${status}/${innings}`; | |
case "Suspended": | |
return `${status}/${innings}`; | |
case "In Progress": | |
return `${game.linescore.inningState} ${game.linescore.currentInning}`; | |
case "Preview": | |
case "Pre-Game": | |
const df = new DateFormatter(); | |
df.useNoDateStyle(); | |
df.useShortTimeStyle(); | |
return df.string(new Date(game.gameDate)); | |
default: | |
return status; | |
} | |
} | |
async function fetchTeam(team) { | |
const scoreboard = await fetchScoreboard(); | |
const games = scoreboard.filter(game => { | |
const away = game.teams.away.team.abbreviation; | |
const home = game.teams.home.team.abbreviation; | |
return team === away || team === home; | |
}); | |
const game = games[0]; | |
const isHome = game.teams.home.team.abbreviation === team; | |
return { | |
game, | |
team: isHome ? game.teams.home.team : game.teams.away.team, | |
}; | |
} | |
async function fetchScoreboard() { | |
const df = new DateFormatter(); | |
df.dateFormat = "yyyy-MM-dd"; | |
const now = new Date(); | |
const date = now.getHours() < 5 ? new Date(now.getTime() - 43200000) : now; | |
const dateString = df.string(date); | |
const url = `https://statsapi.mlb.com/api/v1/schedule?date=${dateString}&language=en&hydrate=team(league),venue(location,timezone),linescore(matchup,runners,positions),decisions,homeRuns,probablePitcher,flags,review,seriesStatus,person,stats,broadcasts(all)&sportId=1`; | |
const data = await fetchJson(`mlb_scores_${dateString}`, url); | |
return data.dates[0].games; | |
} | |
async function fetchTeamLogo(team) { | |
const req = new Request(`https://a.espncdn.com/i/teamlogos/mlb/500/${team.toLowerCase()}.png`); | |
return req.loadImage(); | |
} | |
async function fetchJson(key, url, headers) { | |
const cached = await cache.read(key, 5); | |
if (cached) { | |
return cached; | |
} | |
try { | |
console.log(`Fetching url: ${url}`); | |
const req = new Request(url); | |
req.headers = headers; | |
const resp = await req.loadJSON(); | |
cache.write(key, resp); | |
return resp; | |
} catch (error) { | |
try { | |
return cache.read(key, 5); | |
} catch (error) { | |
console.log(`Couldn't fetch ${url}`); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment