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.

279 lines
8.6 KiB

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