-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 69e22cf
Showing
2 changed files
with
157 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# MS Teams Meetings Launcher in SwiftBar | ||
|
||
This plugin checks your calendar for upcoming MS Teams meetings and links them with direct links to the MS Teams desktop client. No more weird redirects through the browser, just open the meeting right away. | ||
|
||
## Features | ||
|
||
* Finds calendar entries for the current day that contain links to https://teams.microsoft.com and turns them to `msteams:` links that are opened by the MS Teams Desktop client right away | ||
* Indicates an upcoming meeting 15 min before it starts with the name in the toolbar | ||
* Shows you the name of the currently running meeting | ||
* Warns you about a meeting that is about to start and turns the item to a direct link for this meeting | ||
* Shows you the the warning about the next meeting, even if there is a meeting running (back to back) | ||
|
||
## Prerequisites | ||
|
||
1. SwiftBar. This will likely also work with XBar but I have not tried. | ||
2. `icalBuddy`. | ||
Usually this can be installed via `brew install ical-buddy`, but the version there is not working correctly in SwiftBar. | ||
See below in Troubleshooting. | ||
|
||
## Troubleshooting | ||
|
||
* SwiftBar needs to have Calendar permissions. The current version 1.4.3 (and 1.4.4 beta) were not asking for permission. Version 1.2.1 did ask for permission. | ||
* The version currently (2022-05-25) installed via Homebrew has a bug that will not ask for Calendar access and will not show any results. A pre-built version [in the KeyboardMaestro Forum](https://forum.keyboardmaestro.com/t/icalbuddy-doesnt-work-within-keyboard-maestro-mojave-calendar-permissions/15446/6) works fine. Install at your own risk. There is hope that the upstream maintainer of the Homebrew release will include the patch soon and I can remove this section. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# metadata | ||
# <xbar.title>Teams Next Meetings</xbar.title> | ||
# <xbar.version>v0.1</xbar.version> | ||
# <xbar.author>Alexander Lais</xbar.author> | ||
# <xbar.author.github>peanball</xbar.author.github> | ||
# <xbar.desc>Next Team Meetings</xbar.desc> | ||
# <xbar.abouturl>https://github.com/peanball/teams-swiftbar/README.md<xbar.abouturl> | ||
# <xbar.image></xbar.image> | ||
# <bitbar.dependencies>python3, icalBuddy(fixed)</bitbar.dependencies> | ||
# <swiftbar.runInBash>true</swiftbar.runInBash> | ||
|
||
import datetime | ||
import os | ||
|
||
import re | ||
from dateutil import parser | ||
|
||
from urllib.parse import unquote | ||
|
||
BULLET = "__BULLET__" | ||
|
||
LOCAL_TIMEZONE = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo | ||
now = datetime.datetime.now(tz=LOCAL_TIMEZONE) | ||
|
||
cmd = ( | ||
f"icalBuddy -b '{BULLET}' -nnr " | ||
' -tf "%Y-%m-%dT%H:%M:%S.000%z" -nc -iep "title,datetime,notes" -ps "|\t|" eventsToday ' | ||
'| tr "\r" " "' | ||
) | ||
|
||
teams_link = r"(?: |<|<)https://teams.microsoft.com(.*?)(?:>|>| )" | ||
|
||
teams_meetings = [] | ||
|
||
with os.popen(cmd) as result: | ||
entries = [entry.strip() for entry in result.readlines()] | ||
|
||
for entry in entries: | ||
split = entry.split("\t") | ||
link = None | ||
|
||
if len(split) < 3: | ||
continue | ||
|
||
name, notes, time = split | ||
name = name.replace(BULLET, "") | ||
startDate, endDate = re.sub(r"^.* at", "", time).split(" - ") | ||
end = parser.parse(endDate) | ||
if end < now: | ||
continue | ||
|
||
start = parser.parse(startDate) | ||
|
||
links = re.findall(teams_link, notes) | ||
if links: | ||
link = f"msteams:{unquote(links[0])}" | ||
|
||
teams_meetings.append( | ||
[ | ||
name, | ||
start, | ||
end, | ||
link, | ||
] | ||
) | ||
|
||
if not teams_meetings: | ||
exit(0) | ||
|
||
running_meeting = [m for m in teams_meetings if m[1] <= now and m[2] > now] | ||
|
||
# time in minutes when a 'countdown' is shown in the item's main text | ||
upcoming_time = 15 | ||
# time in minutes when a 'countdown' is shown with a link to the meeting in the item's main text | ||
pending_time = 5 | ||
|
||
upcoming_meeting = [ | ||
m for m in teams_meetings if m[1] > now and (m[1] - now) < datetime.timedelta(minutes=upcoming_time) and (m[1] - now) > datetime.timedelta(minutes=pending_time) | ||
] | ||
|
||
pending_meeting = [ | ||
m for m in teams_meetings if m[1] > now and (m[1] - now) < datetime.timedelta(minutes=pending_time) | ||
] | ||
|
||
def format_duration(seconds): | ||
words = ["y", "d", "h", "m", "s"] | ||
|
||
if not seconds: | ||
return "now" | ||
else: | ||
m, s = divmod(seconds, 60) | ||
h, m = divmod(m, 60) | ||
d, h = divmod(h, 24) | ||
y, d = divmod(d, 365) | ||
|
||
time = [y, d, h, m, s] | ||
|
||
duration = [] | ||
|
||
for x, i in enumerate(time): | ||
if i > 0: | ||
duration.append(f"{int(i)}{words[x]}") | ||
|
||
return "".join(duration) | ||
|
||
by_start=lambda m: m[1] | ||
|
||
if pending_meeting: | ||
(name, start, end, link) = sorted(pending_meeting, key=by_start)[0] | ||
timespan = (start - now).total_seconds() - (start - now).total_seconds() % 60 | ||
time_to_meeting = format_duration(timespan) | ||
print( | ||
f":rectangle.3.group.bubble.left.fill: {name} in {time_to_meeting} | sfcolor=#CC0000,#FF3300 href={link}" | ||
) | ||
elif running_meeting: | ||
(name, start, end, link) = sorted(running_meeting, key=by_start)[0] | ||
print(f":rectangle.3.group.bubble.left.fill: {name} | href={link}") | ||
elif upcoming_meeting: | ||
(name, start, end, link) = sorted(upcoming_meeting, key=by_start)[0] | ||
timespan = (start - now).total_seconds() - (start - now).total_seconds() % 60 | ||
time_to_meeting = format_duration(timespan) | ||
print(f":rectangle.3.group.bubble.left.fill: {name} in {time_to_meeting}") | ||
else: | ||
print(":rectangle.3.group.bubble.left:") | ||
print("---") | ||
|
||
for (name, start, end, link) in sorted(teams_meetings, key=by_start): | ||
starttime = start.strftime("%H:%M") | ||
endtime = end.strftime("%H:%M") | ||
duration = format_duration((end - start).total_seconds()) | ||
|
||
print(f"{name.strip()} - {starttime} ({duration}) | href={link}") |