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.

185 lines
5.5 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. recurrence=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. self.recurrence = recurrence
  36. self.id = id
  37. if reminders is None:
  38. self.reminders = {"useDefault": True}
  39. else:
  40. self.reminders = reminders
  41. def add_reminder(self, until, method="popup"):
  42. """Add a reminder minutes before an event.
  43. Use either a notification (popup) or email"""
  44. assert method in ("email", "popup")
  45. self.reminders["useDefault"] = False
  46. if isinstance(until, datetime.timedelta):
  47. minutes_until = until.days * 24 * 60
  48. minutes_until += until.seconds // 60
  49. else:
  50. minutes_until = until
  51. self.reminders.setdefault("overrides", []).append(
  52. {"method": method, "minutes": minutes_until}
  53. )
  54. def add_weekly_recurrence(self, until: datetime.datetime, *days):
  55. if not until.tzinfo:
  56. until = until.astimezone()
  57. ret = rrule.rrule(
  58. freq=rrule.WEEKLY,
  59. dtstart=self.start,
  60. wkst=weekdays.SUN,
  61. until=until,
  62. byweekday=days,
  63. )
  64. ret_str = str(ret).split("\n")[-1]
  65. ret_str = re.sub(r"(UNTIL=[^;]+)", r"\1Z", ret_str)
  66. if self.recurrence is None:
  67. self.recurrence = []
  68. self.recurrence.append(ret_str)
  69. def to_json(self):
  70. keys = ("attendees", "description", "location", "recurrence", "reminders")
  71. ret = {
  72. "summary": self.name,
  73. "start": to_dateTime(self.start),
  74. "end": to_dateTime(self.end),
  75. }
  76. for key in keys:
  77. try:
  78. value = self.__getattribute__(key)
  79. if value:
  80. ret[key] = value
  81. except AttributeError:
  82. pass
  83. return ret
  84. def add_attendees(self):
  85. pass
  86. @classmethod
  87. def from_json(cls, body):
  88. args = {}
  89. args["name"] = body.get("summary", "unnamed")
  90. args["start"] = from_dateTime(body["start"])
  91. args["end"] = from_dateTime(body["end"])
  92. keys = ("attendees", "description", "location", "recurrence", "reminders", "id")
  93. for key in keys:
  94. try:
  95. args[key] = body[key]
  96. except KeyError:
  97. pass
  98. instance = cls(**args)
  99. return instance
  100. @classmethod
  101. def from_id(cls, api: calendar_api, calendar_id, event_id):
  102. """creates an event model from specified calendar and event_id"""
  103. event = api.get_event_by_id(calendar_id, event_id)
  104. return cls.from_json(event)
  105. def upload(self, api: calendar_api, calendar_id):
  106. """Upload an event to calendar.
  107. Either modifies an event in place or creates a new event."""
  108. if self.id is not None:
  109. event_id = api.update_event(calendar_id, self.id, self.to_json())
  110. else:
  111. event_id = api.create_event(calendar_id, self.to_json())
  112. self.id = event_id
  113. return event_id
  114. class Calendar:
  115. """Model for representing a Google calendar"""
  116. def __init__(self, api: calendar_api, calendar_id):
  117. self.api = api
  118. if calendar_id in api.ids.keys():
  119. self.name = calendar_id
  120. self.calendar_id = self.api.ids[self.name]
  121. elif calendar_id in api.ids.values():
  122. self.name = self.api.calendars[calendar_id]["summary"]
  123. self.id = calendar_id
  124. else:
  125. raise ValueError("Non-existent calendar specified")
  126. self.calendar_id = calendar_id
  127. def update_or_add_event(self, event: Event):
  128. event.upload(self.api, self.calendar_id)
  129. def get_events(
  130. self, start: datetime.datetime = None, end: datetime.datetime = None
  131. ):
  132. return (
  133. Event(event_representation)
  134. for event_representation in self.api.get_events_in_range(
  135. self.cal_id, start, end
  136. )
  137. )
  138. def delete_event(self, event: Event):
  139. if event.id is not None:
  140. return self.api.delete_event(self.calendar_id, event.id)
  141. def search_events(
  142. self, event_name_regex=None, event_description_regex=None, start=None, end=None
  143. ):
  144. for event in self.get_events(start, end):
  145. will_yield = False
  146. if event_name_regex is not None:
  147. will_yield = re.search(event_name_regex, Event.name) is not None
  148. if event_name_regex is not None:
  149. will_yield = (
  150. re.search(event_description_regex, Event.description) is not None
  151. or will_yield # noqa
  152. )
  153. if will_yield:
  154. yield event
  155. def calendar_dict(api: calendar_api):
  156. return dict(
  157. (calendar_name, Calendar(api, calendar_name))
  158. for calendar_name in api.ids.keys()
  159. )