Skip to content

Instantly share code, notes, and snippets.

@yongkangc
Last active January 27, 2025 00:56
Show Gist options
  • Save yongkangc/e81420e1089c8a66c78de8c41a3f23e2 to your computer and use it in GitHub Desktop.
Save yongkangc/e81420e1089c8a66c78de8c41a3f23e2 to your computer and use it in GitHub Desktop.
Calendar Scraper Scraper

Export Academic Schedule to Calendar (ICS)

This script extracts your class schedule from a university portal (e.g., PeopleSoft/MyPortal) and generates an .ics calendar file for importing into apps like Google Calendar, Outlook, or Apple Calendar.


Prerequisites

  • A browser with developer tools (Chrome, Firefox, Edge).
  • Access to your My Weekly Schedule on the portal.
  • Basic understanding of JavaScript (for troubleshooting).

Steps to Use

1. Prepare the Portal Page

  1. Navigate to:
    MyPortal > My Record > My Weekly Schedule.
  2. Configure display settings:
    • Show all 7 days (Monday–Sunday).
    • Enable: "Show Class Title".
    • Disable: "Show AM/PM" and "Show Instructors".
    • Click "Refresh Calendar" after changing settings.
  3. Set the calendar to display the week starting on 27th January 2025 (or your desired start date).

2. Run the Script

  1. Open the browser’s developer tools:
    • Press F12 or Ctrl+Shift+I (Windows/Linux) / Cmd+Option+I (Mac).
    • Go to the Console tab.
  2. Copy the script below and paste it into the console:
// vvv-- CODE --vvv
(async function (doc) {
  let classes = [];
  for (let i = 0; i < 14; i++) {
    const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
 
    const day_from_x = {};
    const days = Array.from(doc.querySelectorAll('#WEEKLY_SCHED_HTMLAREA th'))
      .slice(1)
      .map((e, i) => {
        let [day, month] = e.textContent.split('\n')[1].split(' ');
        const x = Math.round(e.getBoundingClientRect().x);
        day_from_x[x] = i;
        return {
          day: parseInt(day),
          month: MONTHS.indexOf(month) + 1,
          year: 2025,
        };
      });
 
    const removeExtraSpaces = s => s.split(' ').filter(s => s.length > 0).join(' ');
 
    const c = Array.from(doc.querySelectorAll('#WEEKLY_SCHED_HTMLAREA td > span'))
      .map(e => e.parentNode)
      .filter(e => day_from_x[Math.round(e.getBoundingClientRect().x)] !== undefined)
      .map(e => {
        const span = e.childNodes[0].childNodes;
        const day = days[day_from_x[Math.round(e.getBoundingClientRect().x)]];
 
        const formatCode = s => {
          const words = removeExtraSpaces(s).split(' ');
          return words[0] + words.slice(1).join(' ');
        }
 
        return {
          name: removeExtraSpaces(`${formatCode(span[0].textContent)} ${span[2].textContent} ${span[4].textContent}`),
          day,
          time: span[6].textContent.split(' - ').map(s => s.split(':').join('')),
          place: removeExtraSpaces(span[8].textContent),
        }
      });
 
    console.log(`Found ${c.length} classes for Week ${i + 1}`);
    classes.push(...c);
 
    doc.getElementById('DERIVED_CLASS_S_SSR_NEXT_WEEK').click();
    await new Promise(res => setTimeout(res, 2000));
  }
  return classes;
})(document.getElementById('ptifrmtgtframe').contentDocument).then(classes => {
  const now = new Date();
 
  const p = (n, l) => ("000" + n).slice(-l);
  const f = (c, i) => `BEGIN:VEVENT
SUMMARY:${c.name}
DTSTAMP;TZID=Asia/Singapore:${p(now.getUTCFullYear(), 4)}${p(now.getMonth() + 1, 2)}${p(now.getUTCDate(), 2)}T000000
DTSTART;TZID=Asia/Singapore:${p(c.day.year, 4)}${p(c.day.month, 2)}${p(c.day.day, 2)}T${p(c.time[0], 4)}00
DTEND;TZID=Asia/Singapore:${p(c.day.year, 4)}${p(c.day.month, 2)}${p(c.day.day, 2)}T${p(c.time[1], 4)}00
LOCATION:${c.place}
END:VEVENT`;
 
  const icsContent = `BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
${classes.map(f).join('\n')}
END:VCALENDAR
`;
 
  let file = new Blob([icsContent], { type: 'text/calendar' });
  const a = document.createElement("a");
  const url = URL.createObjectURL(file);
  a.href = url;
  a.download = 'schedule.ics';
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 0);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment