Skip to content
This repository has been archived by the owner on Feb 2, 2021. It is now read-only.

Google Script to Sync with Calendar #98

Open
fstab opened this issue Dec 18, 2018 · 1 comment
Open

Google Script to Sync with Calendar #98

fstab opened this issue Dec 18, 2018 · 1 comment

Comments

@fstab
Copy link

fstab commented Dec 18, 2018

I wrote a little Google script that creates a cfp calendar from your list. To try it, go to drive.google.com, click New -> Google Apps Script, copy the code into the editor, select syncCfpCalendar as the function to be executed and click the play button. Reload calendar.google.com and you should see a new cfp calendar. Google also provides triggers so you can run this nightly (click Edit -> Current Project's Triggers).

If you think this is useful, feel free to publish it, put it in a readme, provide a public calendar, whatever you want.

var sources = [
  {
    rawUrl: 'https://raw.githubusercontent.com/softwaremill/it-cfp-list/master/README.md',
    link: 'https://github.com/softwaremill/it-cfp-list'
  }
]

// name of the calendar to be updated / created.
var calendarName = 'cfp';

// All calendar events are tagged, so we don't accidentally delete unrelated events.
var tag = {
  key: 'generated-by',
  value: 'cfp-calendar-sync'
};

// main function
function syncCfpCalendar() {
  var cal = openCalendar();
  for each (source in sources) {
    var markdownContent = loadMarkdownContent(source.rawUrl);
    processMarkdownContent(markdownContent, source.link, cal);
  }
}

function loadMarkdownContent(url) {
  var response = UrlFetchApp.fetch(sources[0].rawUrl);
  if (response.getResponseCode() != 200) {
    throw new Error("Got HTTP " + response.getResponseCode() + " when fetching " + cfpListUrl);
  }
  return response.getContentText();
}

function processMarkdownContent(markdownContent, link, cal) {
  var tableStart = false
  for each (line in markdownContent.split(/\r?\n/)) {
    if (!tableStart) {
      if (line.match(/\|-+\|-+\|-+\|-+\|-+\|-+\|/)) {
        tableStart = true;
      }
    } else {
      if (!startsWith(line, "|")) {
        return; // end of table
      }
      processTableLine(line, link, cal)
    }
  }
}

function processTableLine(line, link, cal) {
  if (!line.match(/\|.*\|.*\|.*\|.*\|.*\|.*\|/)) {
    Logger.log("skipping line " + line)
  }
  line = line.substring(1, line.length-1); // strip "|" from the beginning and end.
  fields = line.split("|")
  var cfpStart = startDate(fields[0].trim());
  var cfpEnd = endDate(fields[0].trim());
  var confStart = startDate(fields[1].trim());
  var confEnd = endDate(fields[1].trim());
  var location = fields[2].trim();
  var name = stripLink(fields[3].trim());
  var keywords = fields[5].trim();
  if (cfpStart && cfpEnd && cfpStart <= cfpEnd) {
    createEvent('CFP ' + name, cfpStart, cfpEnd, keywords, location, link, cal)
  }
  if (confStart && confEnd && confStart <= confEnd) {
    createEvent(name, confStart, confEnd, keywords, location, link, cal)
  }
}

function createEvent(name, start, endInclusive, keywords, location, link, cal) {
  // end date is exclusive, so add 1 day
  var evt = cal.createAllDayEvent(name, start, addDays(endInclusive, 1), {description: keywords + ', see ' + link, location: location});
  evt.setTag(tag.key, tag.value);
}

function startDate(dateString) {
  // Date formats used in the CFP List:
  // 2018.12.08
  // 2018.11.27-29
  // 2018.11.30-12.01
  // For the start date, we can ignore stuff following the '-' character.
  match = dateString.match(/^([0-9]{4})\.([0-9]{2})\.([0-9]{2})(-.*)?$/)
  if (match) {
    return dateFromCaptureGroups(match, 1, 2, 3);
  }
}

function endDate(dateString) {
  // Date formats used in the CFP List:
  // 2018.12.08
  // 2018.11.27-29
  // 2018.11.30-12.01
  match = dateString.match(/^([0-9]{4})\.([0-9]{2})\.([0-9]{2})$/)
  if (match) {
    return dateFromCaptureGroups(match, 1, 2, 3);
  }
  match = dateString.match(/^([0-9]{4})\.([0-9]{2})\.([0-9]{2})-([0-9]{2})$/)
  if (match) {
    return dateFromCaptureGroups(match, 1, 2, 4);
  }
  match = dateString.match(/^([0-9]{4})\.([0-9]{2})\.([0-9]{2})-([0-9]{2})\.([0-9]{2})$/)
  if (match) {
    return dateFromCaptureGroups(match, 1, 4, 5);
  }
}

function dateFromCaptureGroups(match, yearIndex, monthIndex, dayIndex) {
  return new Date(parseInt(match[yearIndex], 10), parseInt(match[monthIndex], 10) - 1, dayInt = parseInt(match[dayIndex], 10));
}

function stripLink(markdownString) {
  // "[devoxx](https://devoxx.be)" -> "devoxx" 
  return markdownString.replace(/\[([^\]]+)\]\([^)]+\)/, '$1')
}

function startsWith(s, c) {
  return s.indexOf(c) == 0;
}

function addYears(date, years) {
  var result = new Date(date);
  result.setFullYear(date.getFullYear()+years);
  return result;
}

function subtractYears(date, years) {
  return addYears(date, 0-years);
}

function addDays(date, days) {
  var result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}

function openCalendar() {
  var calendars = CalendarApp.getCalendarsByName(calendarName);
  switch (calendars.length) {
    case 0:
      // not found. create a new calendar.
      return CalendarApp.createCalendar(calendarName, {summary: 'CFP calendar generated from ...'});
    case 1:
      // found. clear existing events, because events will be re-created
      var today = new Date(),
          twoYearsAgo = subtractYears(today, 2), // API requires a time range for getEvents. take a 4 year time range so we get all events.
          twoYearsFromNow = addYears(today, 2),
          events = calendars[0].getEvents(twoYearsAgo, twoYearsFromNow);
      for (var i = 0; i < events.length; i++) {
        if (events[i].getTag(tag.key) === tag.value) {
          events[i].deleteEvent()
        }
      }
      return calendars[0]
    default:
      throw new Error('found ' + calendars.length + ' calendars with name ' + calendarName + '.');
  }
}
@it-cfp-list
Copy link
Collaborator

Thanks!! sorry for the big delay. There is the link to the public calendar: https://calendar.google.com/calendar?cid=c29mdHdhcmVtaWxsLnBsX3Y4azM0anZoc25jdm5mNjdhdmFyajI4Z3Q4QGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20 . I will put this in the readme, and probably blog about it in a while :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants