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

  1. from dateutil import rrule, tz
  2. from oauth2client import tools
  3. from googleapiclient.errors import HttpError
  4. from dateutil.parser import parse as date_parse
  5. import re
  6. import tzlocal
  7. import argparse
  8. import datetime
  9. if __name__ == "__main__":
  10. from api import API
  11. else:
  12. from gapi.api import API
  13. flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
  14. APPLICATION_NAME = "Google Calendar API Python"
  15. SUN = 6
  16. MON = 0
  17. TUE = 1
  18. WED = 2
  19. THU = 3
  20. FRI = 4
  21. SAT = 5
  22. def to_dateTime(datetime: datetime.datetime):
  23. """converts a datetime into json format for rest api"""
  24. if not datetime.tzinfo:
  25. datetime = datetime.astimezone()
  26. zone = tzlocal.get_localzone().zone
  27. datetime = datetime.isoformat(timespec="seconds")
  28. return {"timeZone": zone, "dateTime": datetime}
  29. def from_dateTime(dateTime):
  30. """converts to a datetime from json format returned by rest api"""
  31. timezone = tz.gettz(dateTime["timeZone"])
  32. datetime = date_parse(dateTime["dateTime"])
  33. datetime.replace(tzinfo=timezone)
  34. return datetime
  35. class calendar_api(API):
  36. def __init__(
  37. self,
  38. app_name,
  39. client_secret_file,
  40. credentials_dir,
  41. scopes="https://www.googleapis.com/auth/calendar",
  42. version="v3",
  43. ):
  44. super().__init__(
  45. "calendar", scopes, app_name, client_secret_file, credentials_dir, version
  46. )
  47. self.calendars = self.get_calendars()
  48. self.ids = dict(
  49. (calendar["summary"].lower(), calendar["id"]) for calendar in self.calendars
  50. )
  51. def create_event(self, calendar_id, body):
  52. try:
  53. calendar_id = self.ids[calendar_id]
  54. except KeyError:
  55. pass
  56. service = self.service
  57. event_service = service.events()
  58. event = event_service.insert(calendarId=calendar_id, body=body).execute()
  59. return event["id"]
  60. def update_event(self, calendar_id, event_id, body):
  61. try:
  62. calendar_id = self.ids[calendar_id]
  63. except KeyError:
  64. pass
  65. service = self.service
  66. event_service = service.events()
  67. try:
  68. event = event_service.get(
  69. calendarId=calendar_id, eventId=event_id
  70. ).execute()
  71. except HttpError as e:
  72. if e.resp.status == 404:
  73. return self.create_event(calendar_id, body)
  74. updated_event = (
  75. service.events()
  76. .update(calendarId=calendar_id, eventId=event["id"], body=body)
  77. .execute()
  78. )
  79. return updated_event["id"]
  80. def get_calendars(self):
  81. page_token = None
  82. cl = []
  83. first = True
  84. while page_token or first:
  85. first = False
  86. calendar_list_service = self.service.calendarList()
  87. calendar_list = calendar_list_service.list(pageToken=page_token).execute()
  88. cl += list(calendar_entry for calendar_entry in calendar_list["items"])
  89. page_token = calendar_list.get("nextPageToken")
  90. return cl
  91. def get_events(self, cal_id):
  92. service = self.service
  93. try:
  94. cal_id = self.ids[cal_id]
  95. except KeyError:
  96. pass
  97. page_token = None
  98. ret = []
  99. while True:
  100. event_service = service.events()
  101. events = event_service.list(
  102. calendarId=cal_id, pageToken=page_token
  103. ).execute()
  104. ret += events["items"]
  105. page_token = events.get("nextPageToken")
  106. if not page_token:
  107. break
  108. return ret
  109. def get_event_by_id(self, cal_id, event_id):
  110. """Retrieves event from cal_id and event_id"""
  111. service = self.service
  112. event_service = service.events()
  113. return event_service.get(calendarId=cal_id, eventId=event_id).execute()
  114. class Event:
  115. """Model for a calendar event that can be uploaded"""
  116. def __init__(
  117. self,
  118. start,
  119. end,
  120. name,
  121. description=None,
  122. recurrence=None,
  123. reminders=None,
  124. attendees=None,
  125. location=None,
  126. id=None,
  127. ):
  128. self.start = start
  129. self.attendees = attendees
  130. self.end = end
  131. self.name = name
  132. self.description = description
  133. self.location = location
  134. self.recurrence = recurrence
  135. self.id = id
  136. if reminders is None:
  137. self.reminders = {"useDefault": True}
  138. else:
  139. self.reminders = reminders
  140. def add_reminder(self, until, method="popup"):
  141. """Add a reminder minutes before an event.
  142. Use either a notification (popup) or email"""
  143. assert method in ("email", "popup")
  144. self.reminders["useDefault"] = False
  145. if isinstance(until, datetime.timedelta):
  146. minutes_until = until.days * 24 * 60
  147. minutes_until += until.seconds // 60
  148. else:
  149. minutes_until = until
  150. self.reminders.setdefault("overrides", []).append(
  151. {"method": method, "minutes": minutes_until}
  152. )
  153. def add_weekly_recurrence(self, until: datetime.datetime, *days):
  154. if not until.tzinfo:
  155. until = until.astimezone()
  156. ret = rrule.rrule(
  157. freq=rrule.WEEKLY, dtstart=self.start, wkst=SUN, until=until, byweekday=days
  158. )
  159. ret_str = str(ret).split("\n")[-1]
  160. ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str)
  161. if self.recurrence is None:
  162. self.recurrence = []
  163. self.recurrence.append(ret_str)
  164. def to_json(self):
  165. keys = ("attendees", "description", "location", "recurrence", "reminders")
  166. ret = {
  167. "summary": self.name,
  168. "start": to_dateTime(self.start),
  169. "end": to_dateTime(self.end),
  170. }
  171. for key in keys:
  172. try:
  173. value = self.__getattribute__(key)
  174. if value:
  175. ret[key] = value
  176. except AttributeError:
  177. pass
  178. return ret
  179. def add_attendees(self):
  180. pass
  181. @classmethod
  182. def from_json(cls, body):
  183. args = {}
  184. args["name"] = body.get("summary", "unnamed")
  185. args["start"] = from_dateTime(body["start"])
  186. args["end"] = from_dateTime(body["end"])
  187. keys = ("attendees", "description", "location", "recurrence", "reminders", "id")
  188. for key in keys:
  189. try:
  190. args[key] = body[key]
  191. except KeyError:
  192. pass
  193. instance = cls(**args)
  194. return instance
  195. @classmethod
  196. def from_id(cls, api: calendar_api, calendar_id, event_id):
  197. """creates an event model from specified calendar and event_id"""
  198. event = api.get_event_by_id(calendar_id, event_id)
  199. return cls.from_json(event)
  200. def upload(self, api: calendar_api, calendar_id):
  201. """Upload an event to calendar.
  202. Either modifies an event in place or creates a new event."""
  203. if self.id is not None:
  204. event_id = api.update_event(calendar_id, self.id, self.to_json())
  205. else:
  206. event_id = api.create_event(calendar_id, self.to_json())
  207. return event_id
  208. if __name__ == "__main__":
  209. import os
  210. # ~~BODY EXAMPLE~~##~~BODY EXAMPLE~~
  211. example = {
  212. "attendees": [{"email": "lpage@example.com"}, {"email": "sbrin@example.com"}],
  213. "description": "A chance to hear more about Google's\
  214. developer products.",
  215. "end": {
  216. "dateTime": "2015-05-28T17:00:00-07:00",
  217. "timeZone": "America/Los_Angeles",
  218. },
  219. "location": "800 Howard St., San Francisco, CA 94103",
  220. "recurrence": ["RRULE:FREQ=DAILY;COUNT=2"],
  221. "reminders": {
  222. "overrides": [
  223. {"method": "email", "minutes": 1440},
  224. {"method": "popup", "minutes": 10},
  225. ],
  226. "useDefault": False,
  227. },
  228. "start": {
  229. "dateTime": "2015-05-28T09:00:00-07:00",
  230. "timeZone": "America/Los_Angeles",
  231. },
  232. "summary": "Google I/O 2015",
  233. }
  234. e = Event(
  235. date_parse("march 16, 2019 10:00 am"),
  236. date_parse("march 16, 2019 3:30 pm"),
  237. "Hang out with Matt",
  238. )
  239. path = r"C:\Users\Raphael\Documents\local_repo\cred"
  240. my_api = my_api = calendar_api(
  241. "python", os.path.join(path, "client_secret.json", path), path
  242. )
  243. cal_id = "raphael.roberts48@gmail.com"
  244. e2 = Event.from_id(my_api, cal_id, "qmrsd88ma8ko67ri98d8pbhd7s")
  245. until = datetime.datetime.today() + datetime.timedelta(days=20)
  246. e2.add_weekly_recurrence(until, MON, TUE)
  247. # e2.upload(my_api, cal_id)