Last active
August 14, 2023 20:16
-
-
Save RayHollister/f0bb913174afefc2f672d3da01d244e7 to your computer and use it in GitHub Desktop.
This JavaScript fetches and formats the weekly broadcast schedule of a specific NPR radio show from their API. It groups same times on sequential days and displays the schedule on the radio show's webpage. The script runs automatically on page load, if no schedule is already present.
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
<script> | |
const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | |
async function getShowSchedule() { | |
let scheduleDiv = document.querySelector(".RadioShowPage-mediaSchedule"); | |
if (scheduleDiv) return; | |
const programName = document.querySelector(".RadioShowPage-headline").textContent.trim(); | |
try { | |
const today = new Date(); | |
const nextWeek = new Date(); | |
nextWeek.setDate(today.getDate() + 6); | |
const formatDate = (date) => { | |
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; | |
}; | |
const todayStr = formatDate(today); | |
const nextWeekStr = formatDate(nextWeek); | |
// caching | |
// const response = await fetch(`https://api.composer.nprstations.org/v1/widget/{ucs}/week?date=${todayStr},${nextWeekStr}&format=json`); | |
// cache buster | |
const response = await fetch(`https://api.composer.nprstations.org/v1/widget/{ucs}/week?date=${todayStr},${nextWeekStr}&format=json&nocache=${new Date().getTime()}`); | |
const data = await response.json(); | |
const showSchedules = data.onThisWeek.filter(show => show.program.name === programName); | |
if (!showSchedules.length) { | |
console.error('Invalid API response', data); | |
return; | |
} | |
const timeDayMap = {}; | |
showSchedules.forEach(show => { | |
const showDate = new Date(show.start_utc); | |
let time = formatStartTime(show.start_time); | |
let day; | |
if (time === "12 a.m.") { | |
time = "at midnight"; | |
day = shiftDay(showDate); | |
} else if (time === "12 p.m.") { | |
time = "at noon"; | |
day = showDate.toLocaleDateString('en-US', { weekday: 'long' }); | |
} else { | |
day = showDate.toLocaleDateString('en-US', { weekday: 'long' }); | |
} | |
if (!timeDayMap[time]) { | |
timeDayMap[time] = [day]; | |
} else { | |
timeDayMap[time].push(day); | |
} | |
}); | |
const formattedScheduleTimesArr = combineTimesForDays(timeDayMap); | |
let formattedScheduleTimes = ""; | |
if (formattedScheduleTimesArr.length > 1) { | |
formattedScheduleTimes = formattedScheduleTimesArr.slice(0, -1).join(", ") + " and " + formattedScheduleTimesArr.slice(-1); | |
} else { | |
formattedScheduleTimes = formattedScheduleTimesArr[0]; | |
} | |
scheduleDiv = document.createElement('div'); | |
scheduleDiv.className = "RadioShowPage-mediaSchedule"; | |
const radioTopDiv = document.querySelector('.RadioShowPage-top'); | |
radioTopDiv.insertAdjacentElement('afterend', scheduleDiv); | |
// Capitalize first character | |
formattedScheduleTimes = formattedScheduleTimes.charAt(0).toUpperCase() + formattedScheduleTimes.slice(1); | |
scheduleDiv.textContent = formattedScheduleTimes; | |
} catch (error) { | |
console.error("Error: ", error); | |
} | |
} | |
function combineSequentialDays(days) { | |
let combinedDays = ""; | |
let sequenceStart = days[0]; | |
let previousDay = days[0]; | |
for (let i = 1; i < days.length; i++) { | |
const day = days[i]; | |
const dayIndex = daysOfWeek.indexOf(day); | |
const previousDayIndex = daysOfWeek.indexOf(previousDay); | |
if ((dayIndex !== (previousDayIndex + 1) % 7) && (dayIndex !== previousDayIndex)) { | |
if (sequenceStart === previousDay) { | |
combinedDays += `${sequenceStart}, `; | |
} else if ((daysOfWeek.indexOf(sequenceStart) + 4) % 7 === daysOfWeek.indexOf(previousDay)) { | |
combinedDays += "weekdays, "; | |
} else { | |
combinedDays += `${sequenceStart} through ${previousDay}, `; | |
} | |
sequenceStart = day; | |
} | |
previousDay = day; | |
} | |
if (sequenceStart === previousDay) { | |
combinedDays += `${sequenceStart}`; | |
} else if ((daysOfWeek.indexOf(sequenceStart) + 4) % 7 === daysOfWeek.indexOf(previousDay)) { | |
combinedDays += "weekdays"; | |
} else { | |
combinedDays += `${sequenceStart} through ${previousDay}`; | |
} | |
// Replace the last comma with 'and' if there are only two items | |
if (combinedDays.split(', ').length == 2) { | |
combinedDays = combinedDays.replace(', ', ' and '); | |
} | |
return combinedDays; | |
} | |
function formatStartTime(startTime) { | |
if (!startTime) { | |
console.error('Invalid start time:', startTime); | |
return ''; | |
} | |
const timeParts = startTime.split(":"); | |
let hours = parseInt(timeParts[0]); | |
const minutes = timeParts[1]; | |
let period = "a.m."; | |
if (hours >= 12) { | |
period = "p.m."; | |
if (hours > 12) { | |
hours -= 12; | |
} | |
} | |
if (hours === 0) { | |
hours = 12; | |
} | |
let formattedTime = `${hours}`; | |
if (minutes !== '00') { | |
formattedTime += `:${minutes}`; | |
} | |
formattedTime += ` ${period}`; | |
return formattedTime; | |
} | |
function shiftDay(date) { | |
date.setDate(date.getDate() - 1); | |
return date.toLocaleDateString('en-US', { weekday: 'long' }); | |
} | |
function combineTimesForDays(timeDayMap) { | |
const dayTimeMap = {}; | |
for (let time in timeDayMap) { | |
const days = timeDayMap[time].sort((a, b) => daysOfWeek.indexOf(a) - daysOfWeek.indexOf(b)); | |
const dayString = combineSequentialDays(days); | |
if (!dayTimeMap[dayString]) { | |
dayTimeMap[dayString] = [time]; | |
} else { | |
dayTimeMap[dayString].push(time); | |
} | |
} | |
let formattedScheduleTimesArr = []; | |
for (let days in dayTimeMap) { | |
const times = dayTimeMap[days]; | |
const timeString = times.join(" and "); | |
formattedScheduleTimesArr.push({days: days, timeString: timeString}); | |
} | |
// Sort the array | |
formattedScheduleTimesArr.sort((a, b) => { | |
const timeA = parseInt(a.timeString.split(":")[0]); | |
const timeB = parseInt(b.timeString.split(":")[0]); | |
return timeA - timeB; | |
}); | |
// Convert back to string format | |
return formattedScheduleTimesArr.map(item => `${item.days} ${item.timeString}`); | |
} | |
getShowSchedule(); | |
</script> |
Fixed weekdays
Fixed capitalization on weekdays!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Rev 7 added a cache buster to the JSON