Skip to content

Instantly share code, notes, and snippets.

@EmilePerron
Created August 2, 2022 18:18
Show Gist options
  • Save EmilePerron/1b7dc81c73b84aa0097d6068263ab8c8 to your computer and use it in GitHub Desktop.
Save EmilePerron/1b7dc81c73b84aa0097d6068263ab8c8 to your computer and use it in GitHub Desktop.
Arc Boost to make Microsoft Teams show notification 2 mins before events
const notifiedEvents = new Map();
let events = [];
// Load and refresh events when the calendar view is open
function refreshEvents() {
const eventNodes = document.querySelectorAll("[aria-label*='organized by'][aria-label*='for more options'][title*='from']");
if (!eventNodes.length) {
return;
}
events = [];
for (const eventNode of eventNodes) {
const name = eventNode.title.split('from')[0].trim();
const datetimeString = eventNode.title.split('from')[1].trim().split('to')[0].trim();
const dateString = eventNode.title.split('from')[1].trim().split('to')[0].trim().replace(/(^.+?,\s.+?\s\d+).+$/, '$1');
const timeString = eventNode.title.split('from')[1].trim().split('to')[0].trim().replace(/^.+?,\s.+?\s\d+(.+)$/, '$1').trim();
const date = formatDate(dateString);
const time = convertTimeTo24HrsFormat(timeString);
events.push({
name,
datetime: new Date(`${date} ${time}`),
date,
time,
datetimeString,
dateString,
timeString,
id: `${datetimeString}${name}`,
});
}
}
setInterval(refreshEvents, 1000);
// Check for upcoming events
setInterval(() => {
const now = (new Date());
for (const event of events) {
if (notifiedEvents.has(event.id)) {
continue;
}
const secondsUntilEventStart = (event.datetime.getTime() - now.getTime()) / 1000;
if (secondsUntilEventStart > 0 && secondsUntilEventStart <= 120) {
if (Notification.permission === "granted") {
sendEventNotification(event);
} else if (Notification.permission !== "denied") {
Notification.requestPermission().then(function (permission) {
if (permission === "granted") {
sendEventNotification(event);
}
});
}
}
}
window.events = events;
}, 5000);
function sendEventNotification(event) {
notifiedEvents.set(event.id, true);
new Notification(`Heads up! ${event.name} is starting at ${event.timeString}`);
}
// utils
function formatDate(date) {
const d = new Date(date);
const year = new Date().getFullYear();
let month = '' + (d.getMonth() + 1);
let day = '' + d.getDate();
if (month.length < 2) {
month = '0' + month;
}
if (day.length < 2) {
day = '0' + day;
}
return `${year}-${month}-${day}`;
}
function convertTimeTo24HrsFormat(time) {
const slicedTime = time.split(/(PM|AM)/gm)[0].trim();
let [hours, minutes] = slicedTime.split(':');
if (hours === '12') {
hours = '00';
}
let updateHourAndMin;
function addition(hoursOrMin) {
updateHourAndMin =
hoursOrMin.length < 2
? (hoursOrMin = `${0}${hoursOrMin}`)
: hoursOrMin;
return updateHourAndMin;
}
if (time.endsWith('PM')) {
hours = parseInt(hours, 10) + 12;
}
return `${addition(hours)}:${addition(minutes)}`;
}
@EmilePerron
Copy link
Author

EmilePerron commented Aug 2, 2022

This isn't a perfect gist, but it works for my purpose and I figured it might be a good starting point for others as well.

Basically:

  • Every second, the Boost scans for events, parses them and stores them as objects in an array.
    • If you are in the Calendar view, this will happen.
    • If you aren't in the Calendar view, this won't do anything, and will leave previously found events as is.
    • That does mean you have to open the Calendar view at least once for the script to work.
  • Every 5 seconds, the Boost loops over the events and checks if they're two minutes away or less. If so, it sends a notification.

Please don't mind the code quality: I hadn't planned on sharing this 😅

@AndrewLeedham
Copy link

I made a few tweaks to get this working in British English. Since there are a few differences like date format and "organised" instead of "organized":

Source code (en-gb)
const notifiedEvents = new Map();
let events = [];

// Load and refresh events when the calendar view is open

function refreshEvents() {
  const eventNodes = document.querySelectorAll("[aria-label*='organised by'][aria-label*='for more options'][title*='from']");

  if (!eventNodes.length) {
    return;
  }

  events = [];

  for (const eventNode of eventNodes) {
    const name = eventNode.title.split('from')[0].trim();
    const datetimeString = eventNode.title.split('from')[1].trim().split('to')[0].trim();
    const dateString = eventNode.title.split('from')[1].trim().split('to')[0].trim().slice(0, -6);
    const time = eventNode.title.split('from')[1].trim().split('to')[0].trim().substr(-5);
    const date = formatDate(dateString);

    events.push({
      name,
      datetime: new Date(`${date} ${time}`),
      date,
      time,
      datetimeString,
      dateString,
      id: `${datetimeString}${name}`,
    });
  }
}

setInterval(refreshEvents, 1000);

// Check for upcoming events
setInterval(() => {
  const now = (new Date());

  for (const event of events) {
    if (notifiedEvents.has(event.id)) {
      continue;
    }

    const secondsUntilEventStart = (event.datetime.getTime() - now.getTime()) / 1000;
    
    if (secondsUntilEventStart > 0 && secondsUntilEventStart <= 120) {
      if (Notification.permission === "granted") {
        sendEventNotification(event);
      } else if (Notification.permission !== "denied") {
        Notification.requestPermission().then(function (permission) {
          if (permission === "granted") {
            sendEventNotification(event);
          }
        });
      }
    }
  }

  window.events = events;
}, 5000);

function sendEventNotification(event) {
  notifiedEvents.set(event.id, true);
  new Notification(`Heads up! ${event.name} is starting at ${event.time}`);
}



// utils

function formatDate(date) {
  const d = new Date(date);
  const year = new Date().getFullYear();
  let month = '' + (d.getMonth() + 1);
  let day = '' + d.getDate();

  if (month.length < 2) {
    month = '0' + month;
  }

  if (day.length < 2) {
    day = '0' + day;
  }

  return `${year}-${month}-${day}`;
}

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