Last active
September 25, 2018 09:09
-
-
Save hugoShaka/2d43a8c8756a2f82b4ef4cb8ec30aeef to your computer and use it in GitHub Desktop.
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
""" | |
This is a bot monitoring WebDAV calendar and issuing slack alerts. | |
""" | |
import os | |
import time | |
import ics | |
import requests | |
from slackclient import SlackClient | |
class Notifier: | |
"""Handles notifications for slack.""" | |
def __init__(self, token, channel): | |
"""Constructor. Creates the connection.""" | |
self.slack_token = token | |
self.channel = channel | |
self.slack_client = SlackClient(self.slack_token) | |
def notify(self, payload): | |
"""Send a notification to the given channel.""" | |
result = self.slack_client.api_call( | |
"chat.postMessage", channel=self.channel, text=payload | |
) | |
return result["ts"] | |
def notify_created(self, event): | |
"""Send a notification about a created event.""" | |
state = ":heavy_check_mark: Added" | |
payload = self.craft_text_message(event) | |
thread_state = self.notify("%s %s" % (payload, state)) | |
self.add_absolute_date(event, thread_state) | |
def notify_deleted(self, event): | |
"""Send a notification about a deleted event.""" | |
state = ":x: Deleted" | |
payload = self.craft_text_message(event) | |
thread_state = self.notify("%s %s" % (payload, state)) | |
self.add_absolute_date(event, thread_state) | |
def notify_edited(self, event_new, event_old): | |
"""Send a notification to the given channel concatenating the event | |
string and the state.""" | |
state = self.compute_editions(event_new, event_old) | |
payload = self.craft_text_message(event_new) | |
thread_state = self.notify("%s %s" % (payload, state)) | |
self.add_absolute_date(event_new, thread_state) | |
def add_absolute_date(self, event, thread_state): | |
"""Adds details about the event on the thread""" | |
payload = "Absolute date : %s" % event.begin.format( | |
fmt="DD MMM YYYY - HH:mm", locale="en_us" | |
) | |
self.slack_client.api_call( | |
"chat.postMessage", channel=self.channel, text=payload, thread_ts=thread_state | |
) | |
def craft_text_message(self, evt): | |
"""Takes an event and return a textual representation of it.""" | |
if evt.location is not None: | |
loc = "( %s )" % evt.location | |
else: | |
loc = "" | |
payload = "%s - %s %s" % (evt.name, evt.begin.humanize(), loc) | |
return payload | |
def compute_editions(self, new, old): | |
"""Takes the new version of an event and the old one. | |
Returns a string describing the chages""" | |
assert new.uid == old.uid | |
description = "" | |
if new.name != old.name: | |
description += " :pencil2: renamed" | |
if new.begin != old.begin: | |
description += " :calendar: recheduled" | |
if new.location != old.location: | |
description += " :door: changed location" | |
return description | |
def monkey_patch(self, other): | |
"""Dirty monkey patching of ics module. | |
This function will replace the Events __eq__() relation in order to be able | |
to compare events with the same UID but different data. | |
Two events are identical if : | |
- They have the same name | |
- They start at the same date | |
- They are at the same place | |
""" | |
if isinstance(other, ics.Event): | |
return ( | |
(self.name == other.name) | |
and (self.begin == other.begin) | |
and (self.location == other.location) | |
) | |
raise NotImplementedError("Cannot compare Event and {}".format(type(other))) | |
ics.Event.__eq__ = monkey_patch | |
def get_calendar(url, auth): | |
"""Takes a WebDav URL and auth objects, returns the calendar.""" | |
answer = requests.get(url, auth=auth).text | |
return ics.Calendar(answer) | |
def compare_events(new, old): | |
"""Takes two lists of events and returns two lists representing the | |
differences between them. The first list are elements that are added, and | |
the second elements removed. | |
""" | |
created = list(set(new) - set(old)) | |
deleted = list(set(old) - set(new)) | |
return (created, deleted) | |
def compute_changes(new, old): | |
"""Takes the lists of old and new events and returns 3 lists of events: | |
events created, deleted and edited. Edited events are detected when a | |
created event and a deleted event share the same uid. | |
""" | |
created, deleted = compare_events(new, old) | |
edited = [] | |
for i, evt_created in enumerate(created): | |
for j, evt_deleted in enumerate(deleted): | |
if evt_created.uid == evt_deleted.uid: | |
edited.append((evt_created, evt_deleted)) | |
created.pop(i) | |
deleted.pop(j) | |
return (created, deleted, edited) | |
def main(): | |
"""Main loop""" | |
version = "2.0" | |
url = os.getenv("DAV_URL") | |
password = os.getenv("DAV_PASSWORD") | |
user = os.getenv("DAV_USER") | |
channel = os.getenv("SLACK_CHANNEL") | |
slack_token = os.getenv("SLACK_TOKEN") | |
slack = Notifier(slack_token, channel) | |
slack.notify("Bot started (v%s) :tada:" % version) | |
errcount = 0 | |
auth = requests.auth.HTTPBasicAuth(user, password) | |
old_calendar = get_calendar(url, auth) | |
while True: | |
time.sleep(20) | |
try: | |
new_calendar = get_calendar(url, auth) | |
(created, deleted, edited) = compute_changes( | |
new_calendar.events, old_calendar.events | |
) | |
for evt in created: | |
slack.notify_created(evt) | |
for evt in deleted: | |
slack.notify_deleted(evt) | |
for evt in edited: | |
slack.notify_edited(evt[0], evt[1]) | |
old_calendar = new_calendar | |
errcount = 0 | |
except Exception: | |
print("Supelec failing") | |
errcount += 1 | |
if errcount >= 15: | |
raise | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment