You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
8.5 KiB
278 lines
8.5 KiB
from dateutil import rrule, tz
|
|
from oauth2client import tools
|
|
from googleapiclient.errors import HttpError
|
|
from dateutil.parser import parse as date_parse
|
|
import re
|
|
import tzlocal
|
|
import argparse
|
|
import datetime
|
|
|
|
if __name__ == "__main__":
|
|
from api import API
|
|
else:
|
|
from gapi.api import API
|
|
|
|
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
|
|
APPLICATION_NAME = "Google Calendar API Python"
|
|
|
|
SUN = 6
|
|
MON = 0
|
|
TUE = 1
|
|
WED = 2
|
|
THU = 3
|
|
FRI = 4
|
|
SAT = 5
|
|
|
|
|
|
def to_dateTime(datetime: datetime.datetime):
|
|
"""converts a datetime into json format for rest api"""
|
|
if not datetime.tzinfo:
|
|
datetime = datetime.astimezone()
|
|
zone = tzlocal.get_localzone().zone
|
|
datetime = datetime.isoformat(timespec="seconds")
|
|
return {"timeZone": zone, "dateTime": datetime}
|
|
|
|
|
|
def from_dateTime(dateTime):
|
|
"""converts to a datetime from json format returned by rest api"""
|
|
timezone = tz.gettz(dateTime["timeZone"])
|
|
datetime = date_parse(dateTime["dateTime"])
|
|
datetime.replace(tzinfo=timezone)
|
|
return datetime
|
|
|
|
|
|
class calendar_api(API):
|
|
def __init__(
|
|
self,
|
|
app_name,
|
|
client_secret_file,
|
|
credentials_dir,
|
|
scopes="https://www.googleapis.com/auth/calendar",
|
|
version="v3",
|
|
):
|
|
super().__init__(
|
|
"calendar", scopes, app_name, client_secret_file, credentials_dir, version
|
|
)
|
|
|
|
self.calendars = self.get_calendars()
|
|
self.ids = dict(
|
|
(calendar["summary"].lower(), calendar["id"]) for calendar in self.calendars
|
|
)
|
|
|
|
def create_event(self, calendar_id, body):
|
|
|
|
try:
|
|
calendar_id = self.ids[calendar_id]
|
|
except KeyError:
|
|
pass
|
|
service = self.service
|
|
event_service = service.events()
|
|
event = event_service.insert(calendarId=calendar_id, body=body).execute()
|
|
return event["id"]
|
|
|
|
def update_event(self, calendar_id, event_id, body):
|
|
try:
|
|
calendar_id = self.ids[calendar_id]
|
|
except KeyError:
|
|
pass
|
|
|
|
service = self.service
|
|
event_service = service.events()
|
|
try:
|
|
event = event_service.get(
|
|
calendarId=calendar_id, eventId=event_id
|
|
).execute()
|
|
except HttpError as e:
|
|
if e.resp.status == 404:
|
|
return self.create_event(calendar_id, body)
|
|
|
|
updated_event = (
|
|
service.events()
|
|
.update(calendarId=calendar_id, eventId=event["id"], body=body)
|
|
.execute()
|
|
)
|
|
return updated_event["id"]
|
|
|
|
def get_calendars(self):
|
|
page_token = None
|
|
cl = []
|
|
first = True
|
|
while page_token or first:
|
|
first = False
|
|
calendar_list_service = self.service.calendarList()
|
|
calendar_list = calendar_list_service.list(pageToken=page_token).execute()
|
|
cl += list(calendar_entry for calendar_entry in calendar_list["items"])
|
|
page_token = calendar_list.get("nextPageToken")
|
|
return cl
|
|
|
|
def get_events(self, cal_id):
|
|
service = self.service
|
|
try:
|
|
cal_id = self.ids[cal_id]
|
|
except KeyError:
|
|
pass
|
|
page_token = None
|
|
ret = []
|
|
while True:
|
|
event_service = service.events()
|
|
events = event_service.list(
|
|
calendarId=cal_id, pageToken=page_token
|
|
).execute()
|
|
ret += events["items"]
|
|
page_token = events.get("nextPageToken")
|
|
if not page_token:
|
|
break
|
|
return ret
|
|
|
|
def get_event_by_id(self, cal_id, event_id):
|
|
"""Retrieves event from cal_id and event_id"""
|
|
service = self.service
|
|
event_service = service.events()
|
|
return event_service.get(calendarId=cal_id, eventId=event_id).execute()
|
|
|
|
|
|
class Event:
|
|
"""Model for a calendar event that can be uploaded"""
|
|
|
|
def __init__(
|
|
self,
|
|
start,
|
|
end,
|
|
name,
|
|
description=None,
|
|
recurrence=None,
|
|
reminders=None,
|
|
attendees=None,
|
|
location=None,
|
|
id=None,
|
|
):
|
|
self.start = start
|
|
self.attendees = attendees
|
|
self.end = end
|
|
self.name = name
|
|
self.description = description
|
|
self.location = location
|
|
self.recurrence = recurrence
|
|
self.id = id
|
|
if reminders is None:
|
|
self.reminders = {"useDefault": True}
|
|
else:
|
|
self.reminders = reminders
|
|
|
|
def add_reminder(self, until, method="popup"):
|
|
"""Add a reminder minutes before an event.
|
|
Use either a notification (popup) or email"""
|
|
assert method in ("email", "popup")
|
|
self.reminders["useDefault"] = False
|
|
if isinstance(until, datetime.timedelta):
|
|
minutes_until = until.days * 24 * 60
|
|
minutes_until += until.seconds // 60
|
|
else:
|
|
minutes_until = until
|
|
self.reminders.setdefault("overrides", []).append(
|
|
{"method": method, "minutes": minutes_until}
|
|
)
|
|
|
|
def add_weekly_recurrence(self, until: datetime.datetime, *days):
|
|
if not until.tzinfo:
|
|
until = until.astimezone()
|
|
ret = rrule.rrule(
|
|
freq=rrule.WEEKLY, dtstart=self.start, wkst=SUN, until=until, byweekday=days
|
|
)
|
|
ret_str = str(ret).split("\n")[-1]
|
|
ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str)
|
|
if self.recurrence is None:
|
|
self.recurrence = []
|
|
self.recurrence.append(ret_str)
|
|
|
|
def to_json(self):
|
|
keys = ("attendees", "description", "location", "recurrence", "reminders")
|
|
ret = {
|
|
"summary": self.name,
|
|
"start": to_dateTime(self.start),
|
|
"end": to_dateTime(self.end),
|
|
}
|
|
for key in keys:
|
|
try:
|
|
value = self.__getattribute__(key)
|
|
if value:
|
|
ret[key] = value
|
|
except AttributeError:
|
|
pass
|
|
return ret
|
|
|
|
def add_attendees(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def from_json(cls, body):
|
|
args = {}
|
|
args["name"] = body.get("summary", "unnamed")
|
|
args["start"] = from_dateTime(body["start"])
|
|
args["end"] = from_dateTime(body["end"])
|
|
keys = ("attendees", "description", "location", "recurrence", "reminders", "id")
|
|
for key in keys:
|
|
try:
|
|
args[key] = body[key]
|
|
except KeyError:
|
|
pass
|
|
instance = cls(**args)
|
|
return instance
|
|
|
|
@classmethod
|
|
def from_id(cls, api: calendar_api, calendar_id, event_id):
|
|
"""creates an event model from specified calendar and event_id"""
|
|
event = api.get_event_by_id(calendar_id, event_id)
|
|
return cls.from_json(event)
|
|
|
|
def upload(self, api: calendar_api, calendar_id):
|
|
"""Upload an event to calendar.
|
|
Either modifies an event in place or creates a new event."""
|
|
if self.id is not None:
|
|
event_id = api.update_event(calendar_id, self.id, self.to_json())
|
|
else:
|
|
event_id = api.create_event(calendar_id, self.to_json())
|
|
return event_id
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import os
|
|
|
|
# ~~BODY EXAMPLE~~##~~BODY EXAMPLE~~
|
|
example = {
|
|
"attendees": [{"email": "lpage@example.com"}, {"email": "sbrin@example.com"}],
|
|
"description": "A chance to hear more about Google's\
|
|
developer products.",
|
|
"end": {
|
|
"dateTime": "2015-05-28T17:00:00-07:00",
|
|
"timeZone": "America/Los_Angeles",
|
|
},
|
|
"location": "800 Howard St., San Francisco, CA 94103",
|
|
"recurrence": ["RRULE:FREQ=DAILY;COUNT=2"],
|
|
"reminders": {
|
|
"overrides": [
|
|
{"method": "email", "minutes": 1440},
|
|
{"method": "popup", "minutes": 10},
|
|
],
|
|
"useDefault": False,
|
|
},
|
|
"start": {
|
|
"dateTime": "2015-05-28T09:00:00-07:00",
|
|
"timeZone": "America/Los_Angeles",
|
|
},
|
|
"summary": "Google I/O 2015",
|
|
}
|
|
e = Event(
|
|
date_parse("march 16, 2019 10:00 am"),
|
|
date_parse("march 16, 2019 3:30 pm"),
|
|
"Hang out with Matt",
|
|
)
|
|
path = r"C:\Users\Raphael\Documents\local_repo\cred"
|
|
my_api = my_api = calendar_api(
|
|
"python", os.path.join(path, "client_secret.json", path), path
|
|
)
|
|
cal_id = "raphael.roberts48@gmail.com"
|
|
e2 = Event.from_id(my_api, cal_id, "qmrsd88ma8ko67ri98d8pbhd7s")
|
|
until = datetime.datetime.today() + datetime.timedelta(days=20)
|
|
e2.add_weekly_recurrence(until, MON, TUE)
|
|
# e2.upload(my_api, cal_id)
|