|
// Variables used by Scriptable. |
|
// These must be at the very top of the file. Do not edit. |
|
// icon-color: yellow; icon-glyph: bicycle; |
|
const textSize = 11 |
|
const fontName = "Menlo-Regular" |
|
const preferredTextWidth = 45 |
|
const ridersToShow = 7 |
|
|
|
const widget = new ListWidget() |
|
widget.spacing = 4 |
|
|
|
const heading = widget.addText("Tour de France Standings") |
|
heading.textSize = textSize + 1 |
|
heading.fontName = "Menlo-Bold" |
|
heading.centerAlignText() |
|
|
|
await addTourStandings() |
|
|
|
widget.presentLarge() |
|
|
|
Script.setWidget(widget) |
|
Script.complete() |
|
|
|
async function addTourStandings() { |
|
const tourStartDate = new Date("2020-08-29") |
|
const now = new Date() |
|
var dayOfTourYesterday = daysBetween(tourStartDate, now) |
|
|
|
console.log(dayOfTourYesterday + " days between " + tourStartDate + " and " + now) |
|
|
|
// This allows you to add standings for more than the latest stage, |
|
// but it may exceed the allowed memory consumption of widgets and thus not display at all |
|
// await addRankingsForDay(dayOfTour - 1) |
|
// widget.addSpacer(10) |
|
await addRankingsForDay(dayOfTourYesterday + 1) |
|
} |
|
|
|
async function addRankingsForDay(today) { |
|
var selectedDay = today |
|
var selectedPreviousDay = null |
|
var latestStandings = null |
|
var previousStandings = null |
|
var dayText = "" |
|
|
|
// If today is not rest day, try to fetch |
|
if (stageForDay(today) != null) { |
|
const stage = stageForDay(today) |
|
latestStandings = await topStandingsForStage(stage) |
|
} |
|
|
|
// Check if today was rest day or data not ready yet |
|
if (latestStandings == null) { |
|
// Check if yesterday was not rest day |
|
if (stageForDay(today - 1) != null) { |
|
selectedDay = today - 1 |
|
dayText = " (Yesterday)" |
|
} else { |
|
// Yesterday was rest day, so 2 days ago must be fine |
|
selectedDay = today - 2 |
|
dayText = " (2 days ago)" |
|
} |
|
|
|
const stage = stageForDay(selectedDay) |
|
latestStandings = await topStandingsForStage(stage) |
|
} else { |
|
dayText = " (Today)" |
|
} |
|
|
|
// If day before selected day is rest day, then go back another day |
|
if (stageForDay(selectedDay - 1) != null) { |
|
selectedPreviousDay = selectedDay - 1 |
|
} else { |
|
selectedPreviousDay = selectedDay - 2 |
|
} |
|
|
|
const prevStage = stageForDay(selectedPreviousDay) |
|
previousStandings = await topStandingsForStage(prevStage) |
|
|
|
// Only show top 5 |
|
latestStandings = latestStandings.slice(0, ridersToShow) |
|
|
|
const heading = widget.addText("Stage " + stageForDay(selectedDay) + dayText) |
|
heading.font = new Font(fontName, textSize) |
|
|
|
for (standing of latestStandings) { |
|
const position = standing.position |
|
|
|
const rider = standing.rider |
|
const name = rider.shortname |
|
const country = rider.nationality |
|
|
|
var rankChangeIcon = "🆕" |
|
var rankChangeText = "(--)" |
|
for (oldStanding of previousStandings) { |
|
if (name == oldStanding.rider.shortname) { |
|
|
|
oldPosition = oldStanding.position |
|
if (oldPosition < position) { |
|
rankChangeIcon = "⬇️" |
|
} else if (oldPosition > position) { |
|
rankChangeIcon = "⬆️" |
|
} else { |
|
rankChangeIcon = "🔁" |
|
} |
|
|
|
const rankChange = oldPosition - position |
|
if (rankChange > 0) { |
|
rankChangeText = "(+" + rankChange + ")" |
|
} else if (rankChange < 0) { |
|
rankChangeText = "(" + rankChange + |
|
")" |
|
} |
|
|
|
break |
|
} |
|
} |
|
|
|
const time = standing.time |
|
const gap = standing.gap |
|
|
|
var timeText |
|
if (position == 1) { |
|
timeText = formatTime(time) |
|
} else { |
|
timeText = "+" + formatTime(gap) |
|
} |
|
|
|
const textBeforeTime = position + ". " + rankChangeText + " " + rankChangeIcon + " " + flagForCountry(country) + |
|
" " + name |
|
|
|
var missingSpaces = preferredTextWidth - textBeforeTime.length - timeText.length |
|
|
|
var spacesString = "" |
|
while (missingSpaces > spacesString.length) { |
|
spacesString = spacesString + " " |
|
} |
|
|
|
const finalText = textBeforeTime + spacesString + timeText |
|
|
|
// console.log(name) |
|
// console.log(textBeforeTime.length) |
|
// console.log(spacesString.length) |
|
// console.log(timeText.length) |
|
// console.log("total " + finalText.length) |
|
|
|
const widgetText = widget.addText(finalText) |
|
widgetText.font = new Font(fontName, textSize) |
|
} |
|
} |
|
|
|
async function topStandingsForStage(stage) { |
|
var stageForUrl = new String(stage) |
|
if (stage < 10) { |
|
stageForUrl = "0" + stageForUrl |
|
} |
|
|
|
const url = "https://prod-tdf-api.netcosports.com/rankings/" + stageForUrl + "00/ITG" |
|
console.log(url) |
|
|
|
const request = new Request(url) |
|
const string = await request.loadString() |
|
|
|
const json = JSON.parse(string) |
|
|
|
// If response is array, then we have results from this stage in the tour |
|
if (Array.isArray(json)) { |
|
const allRanks = json[0].ranks |
|
return allRanks |
|
} else { |
|
return null |
|
} |
|
} |
|
|
|
function flagForCountry(country) { |
|
switch (country) { |
|
case "FRA": |
|
return "🇫🇷" |
|
case "GBR": |
|
return "🇬🇧" |
|
case "SLO": |
|
return "🇸🇮" |
|
case "SUI": |
|
return "🇨🇭" |
|
case "ITA": |
|
return "🇮🇹" |
|
case "COL": |
|
return "🇨🇴" |
|
case "NED": |
|
return "🇳🇱" |
|
case "DAN": |
|
return "🇩🇰" |
|
case "AUS": |
|
return "🇦🇺" |
|
case "ESP": |
|
return "🇪🇸" |
|
default: |
|
console.log("missing country: " + country) |
|
return "🇺🇳" |
|
} |
|
} |
|
|
|
function stageForDay(day) { |
|
// Return null for rest days or if the tour is done |
|
if (day == 10 || day == 17 || day > 23) { |
|
return null |
|
} |
|
|
|
const dayToStageMap = { |
|
1:1, |
|
2:2, |
|
3:3, |
|
4:4, |
|
5:5, |
|
6:6, |
|
7:7, |
|
8:8, |
|
9:9, |
|
// Day 10 is rest day |
|
11:10, |
|
12:11, |
|
13:12, |
|
14:13, |
|
15:14, |
|
16:15, |
|
// Day 17 is rest day |
|
18:16, |
|
19:17, |
|
20:18, |
|
21:19, |
|
22:20, |
|
23:21 |
|
} |
|
|
|
return dayToStageMap[day] |
|
} |
|
|
|
function daysBetween(date1, date2) { |
|
const firstDateUTC = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate()) |
|
const secondDateUTC = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate()) |
|
|
|
// console.log(new Date(firstDateUTC)) |
|
// console.log(new Date(secondDateUTC)) |
|
|
|
return Math.floor((secondDateUTC - firstDateUTC) / (1000 * 60 * 60 * 24)) |
|
} |
|
|
|
function formatTime(ms) { |
|
const seconds = ms / 1000 |
|
const minutes = Math.floor(seconds / 60) |
|
const hours = Math.floor(minutes / 60) |
|
|
|
const modSecs = seconds % 60 |
|
const formattedSecs = (modSecs < 10 ? "0" : "") + modSecs + "\"" |
|
|
|
const modMins = minutes % 60 |
|
const formattedMins = (modMins < 10 ? "0" : "") + modMins + "' " |
|
|
|
var formattedHours = "" |
|
if (hours >= 1) { |
|
formattedHours = hours + "h " |
|
} |
|
|
|
return formattedHours + formattedMins + formattedSecs |
|
} |