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.

201 lines
5.8 KiB

  1. from enum import IntEnum
  2. import datetime
  3. import re
  4. from dateutil import rrule
  5. from .calendar_api import calendar_api
  6. from .utils import to_dateTime, from_dateTime
  7. class weekdays(IntEnum):
  8. SUN = 6
  9. MON = 0
  10. TUE = 1
  11. WED = 2
  12. THU = 3
  13. FRI = 4
  14. SAT = 5
  15. class Event:
  16. """Model for a calendar event that can be uploaded"""
  17. def __init__(
  18. self,
  19. start,
  20. end,
  21. name,
  22. description=None,
  23. recurrences=None,
  24. reminders=None,
  25. attendees=None,
  26. location=None,
  27. id=None,
  28. ):
  29. self.start = start
  30. self.attendees = attendees
  31. self.end = end
  32. self.name = name
  33. self.description = description
  34. self.location = location
  35. if recurrences is None:
  36. self.recurrences = []
  37. else:
  38. self.recurrences = recurrences
  39. self.id = id
  40. if reminders is None:
  41. self.reminders = {"useDefault": True}
  42. else:
  43. self.reminders = reminders
  44. def add_reminder(self, until, method="popup"):
  45. """Add a reminder minutes before an event.
  46. Use either a notification (popup) or email"""
  47. assert method in ("email", "popup")
  48. self.reminders["useDefault"] = False
  49. if isinstance(until, datetime.timedelta):
  50. minutes_until = until.days * 24 * 60
  51. minutes_until += until.seconds // 60
  52. else:
  53. minutes_until = until
  54. self.reminders.setdefault("overrides", []).append(
  55. {"method": method, "minutes": minutes_until}
  56. )
  57. def add_weekly_recurrence(self, until: datetime.datetime, *days):
  58. if not until.tzinfo:
  59. until = until.astimezone()
  60. self.recurrences.append(
  61. rrule.rrule(
  62. freq=rrule.WEEKLY,
  63. dtstart=self.start,
  64. wkst=weekdays.SUN,
  65. until=until,
  66. byweekday=days,
  67. )
  68. )
  69. def serialize_recurrences(self):
  70. for _rrule in self.recurrences:
  71. ret_str = str(_rrule).split("\n")[-1]
  72. ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str)
  73. yield ret_str
  74. def to_json(self):
  75. keys = ("attendees", "description", "location", "reminders")
  76. ret = {
  77. "summary": self.name,
  78. "start": to_dateTime(self.start),
  79. "end": to_dateTime(self.end),
  80. }
  81. for _rrule in self.serialize_recurrences():
  82. ret.setdefault("recurrence", []).append(_rrule)
  83. for key in keys:
  84. try:
  85. value = self.__getattribute__(key)
  86. if value:
  87. ret[key] = value
  88. except AttributeError:
  89. pass
  90. return ret
  91. def add_attendees(self):
  92. pass
  93. @classmethod
  94. def from_json(cls, body):
  95. args = {}
  96. args["name"] = body.get("summary", "unnamed")
  97. args["start"] = from_dateTime(body["start"])
  98. args["end"] = from_dateTime(body["end"])
  99. keys = (
  100. "attendees",
  101. "description",
  102. "location",
  103. "recurrences",
  104. "reminders",
  105. "id",
  106. )
  107. for key in keys:
  108. try:
  109. args[key] = body[key]
  110. except KeyError:
  111. pass
  112. instance = cls(**args)
  113. return instance
  114. @classmethod
  115. def from_id(cls, api: calendar_api, calendar_id, event_id):
  116. """creates an event model from specified calendar and event_id"""
  117. event = api.get_event_by_id(calendar_id, event_id)
  118. return cls.from_json(event)
  119. def upload(self, api: calendar_api, calendar_id):
  120. """Upload an event to calendar.
  121. Either modifies an event in place or creates a new event."""
  122. if self.id is not None:
  123. event_id = api.update_event(calendar_id, self.id, self.to_json())
  124. else:
  125. event_id = api.create_event(calendar_id, self.to_json())
  126. self.id = event_id
  127. return event_id
  128. class Calendar:
  129. """Model for representing a Google calendar"""
  130. def __init__(self, api: calendar_api, calendar_id):
  131. self.api = api
  132. if calendar_id in api.ids.keys():
  133. self.name = calendar_id
  134. self.id = self.api.ids[self.name]
  135. elif calendar_id in api.ids.values():
  136. self.name = self.api.calendars[calendar_id]["summary"]
  137. self.id = calendar_id
  138. else:
  139. raise ValueError("Non-existent calendar specified")
  140. self.calendar_id = calendar_id
  141. def update_or_add_event(self, event: Event):
  142. event.upload(self.api, self.calendar_id)
  143. def get_events(
  144. self, start: datetime.datetime = None, end: datetime.datetime = None
  145. ):
  146. return (
  147. Event(event_representation)
  148. for event_representation in self.api.get_events_in_range(
  149. self.id, start, end
  150. )
  151. )
  152. def delete_event(self, event: Event):
  153. if event.id is not None:
  154. return self.api.delete_event(self.calendar_id, event.id)
  155. def search_events(
  156. self, event_name_regex=None, event_description_regex=None, start=None, end=None
  157. ):
  158. for event in self.get_events(start, end):
  159. will_yield = False
  160. if event_name_regex is not None:
  161. will_yield = re.search(event_name_regex, Event.name) is not None
  162. if event_description_regex is not None:
  163. will_yield = (
  164. re.search(event_description_regex, Event.description) is not None
  165. or will_yield # noqa
  166. )
  167. if will_yield:
  168. yield event
  169. def calendar_dict(api: calendar_api):
  170. return dict(
  171. (calendar_name, Calendar(api, calendar_name))
  172. for calendar_name in api.ids.keys()
  173. )