Fetch and parse the Zwift Climbing Portal Schedule XML from the Zwift CDN.

Example usage:

console.log(await zwiftPortalSchedule());

Expected output will look something like this:

[
  {
    "date": "Sun, Oct 27",
    "road": "La Turbie + Col d'Eze",
    "distance": "15.9km",
    "elevation": "616m",
    "gradient": "3.9%",
    "isPortalOfMonth": false,
    "world": "Watopia"
  },
  {
    "date": "Mon, Oct 28",
    "road": "Col du Galibier (Lautaret)",
    "distance": "10.4km",
    "elevation": "539m",
    "gradient": "5.2%",
    "isPortalOfMonth": false,
    "world": "Watopia"
  },
  {
    "date": "Thu, Oct 31",
    "road": "Mt. Hamilton",
    "distance": "31.3km",
    "elevation": "1259m",
    "gradient": "4.0%",
    "isPortalOfMonth": true,
    "world": "France"
  }
]

Create a cron to send an email periodically:

import { zwiftPortalSchedule } from "https://esm.town/v/dazzag24/zwiftPortalSchedule";
import { email } from "https://esm.town/v/std/email";

export default async function sendZwiftPortalSchedule(interval: Interval) {
  console.log("Parsing Zwift portal road schedule...");

  const schedule = await zwiftPortalSchedule();
  const content = schedule.map(event =>
    `**${event.date}**\n${event.road}\nšŸ“ ${event.distance} | ā›°ļø ${event.elevation} | šŸ“ˆ ${event.gradient}${
      event.isPortalOfMonth ? " | šŸ† Portal of the Month" : ""
    }\nšŸŒ ${event.world}\n`
  ).join("\n");

  console.log(content);

  return email({
    subject: "Zwift Climbing Portal Schedule",
    text: "**Upcoming Zwift Portal Roads**\n\n" + content,
  });
}