Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save RayHollister/f0bb913174afefc2f672d3da01d244e7 to your computer and use it in GitHub Desktop.
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.
<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>
@RayHollister
Copy link
Author

RayHollister commented Jul 11, 2023

Tired of having to update your radio schedules in Composer AND in Grove? This solution solves that! It also allows you to override it with the Grove UI if you want to add something special to the schedule line.

  1. Go to your Grove dashboard.
  2. Create a new Module.
  3. Give it a title and set the type as "HTML Embed."
  4. Paste the code above into your new Module.
  5. Replace {ucs} in the code with your Composer UCS number.
    a. Log into Composer.
    b. The UCS number can be found in the URL: "https://composer.nprstations.org/{ucs}/".
  6. Publish the Module.
  7. Open the Sites and Settings menu in Grove
  8. Click the "Page Defaults" tab.
  9. Scroll down to "Type Specific Landing Page Content"
  10. Click to open "Type Specific Landing Page Elements: Radio Show"
  11. Click "Add," then select "Shared."
  12. Find the module you created. It will be listed as "HTML Embed: Whatever Title You Gave It."
  13. Click Save
  14. Open a Radio Shows that you want to change.
  15. Delete any existing content in the "Show Schedule" field and leave it blank.
  16. Check the preview, and you should see the schedule from Composer in the schedule field.
    a. If you don't see the schedule, you might have entered your UCS incorrectly or the radio show's title in Grove doesn't match the title in Composer. Correct the UCS and/or the title of the show in Grove or Composer, and it should start working.

I know this because it just happened to me while I was writing up these notes. "Wait Wait" was "Wait Wait... Don't Tell Me" in Grove and "Wait Wait... Don't Tell Me!" in Composer.

  1. Repeat steps14 through 16 for each radio show for which you want to automate the schedule.
  2. If you want to set the schedule manually on the radio show page, you can just enter what you want to appear in the "Show Schedule" field. If there is anything in the "Show Schedule" field, the script ignores it and doesn't do anything.

*Updated instructions to implement change across entire Grove site for all Radio Show pages.

@RayHollister
Copy link
Author

rev 4. Fixed sequential days to show 'through' instead of 'and' between the days.

@RayHollister
Copy link
Author

rev 5. Changed 'Monday through Friday' to 'Weekdays'

@RayHollister
Copy link
Author

RayHollister commented Jul 21, 2023

Rev 6. Changed 12 p.m. and 12 a.m. to noon and midnight respectively. Changed the day to the previous day if midnight. (Stupid AP Style!!)

@RayHollister
Copy link
Author

Rev 7 added a cache buster to the JSON

@RayHollister
Copy link
Author

Fixed weekdays

@RayHollister
Copy link
Author

Fixed capitalization on weekdays!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment