diff --git a/lambda_handler.py b/lambda_handler.py index bc433a8..261a2af 100644 --- a/lambda_handler.py +++ b/lambda_handler.py @@ -1,4 +1,4 @@ -from datetime import datetime +from typing import List import boto3 @@ -10,21 +10,20 @@ def lambda_handler(event, context): print(event) dynamodb = boto3.resource("dynamodb") - poll_item = poll_table.load(dynamodb) - if is_poll_running(poll_item): - schedule_next_schafkopf_event(dynamodb, poll_item.running_poll_id) + poll = poll_table.load(dynamodb) + subscribed_emails = email_table.load_all_mails(dynamodb) + if poll.poll(): + new_poll = schedule_next_schafkopf_event(subscribed_emails, poll) + elif poll.is_time_to_start_new_poll(): + new_poll = start_new_poll(subscribed_emails) + else: + print("No action required, waiting before scheduling new poll:", poll) return + print("Store new poll item:", new_poll) + poll_table.update(dynamodb, new_poll) - new_poll_item = start_new_poll(dynamodb) - print("Store new poll item:", new_poll_item) - poll_table.update(dynamodb, new_poll_item) - -def is_poll_running(item: PollItem) -> bool: - return item.running_poll_id and datetime.now() < item.start_next_poll_date - - -def start_new_poll(dynamodb) -> PollItem: +def start_new_poll(subscribed_emails) -> PollItem: print("Start a new poll") print("Generate csrf token") csrf_token = bitpoll.get_valid_csrf_token() @@ -44,30 +43,33 @@ def start_new_poll(dynamodb) -> PollItem: print("Send out email notifications") gmail.send_bitpoll_invitation( - receivers=email_table.load_all_mails(dynamodb), + receivers=subscribed_emails, bitpoll_link=new_poll_website ) - return PollItem( - running_poll_id=poll_id, - start_next_poll_date=max(dates) + return PollItem.create_new( + poll_id=poll_id, + next_poll_date=max(dates), ) - -def schedule_next_schafkopf_event(dynamodb, poll_id: str): - poll_website = bitpoll.get_website_from_poll_id(poll_id) +def schedule_next_schafkopf_event(emails: List[str], poll: PollItem) -> PollItem: + poll_website = bitpoll.get_website_from_poll_id(poll.running_poll_id) print("Try to schedule next schafkopf event for:", poll_website) - page = bitpoll.get_poll_webpage(poll_id=poll_id) + page = bitpoll.get_poll_webpage(poll_id=poll.running_poll_id) votes = bitpoll.collect_vote_dates(page) - best_date = scheduler.find_best_date(votes) - print("Most promising date:", best_date) + best_vote = scheduler.find_best_date(votes) + print("Most promising vote:", best_vote) - if best_date: + if best_vote: print("Found valid date, sending out invitation") + best_date = best_vote.date + # todo show "screenshot" of poll gmail.send_schafkopf_meeting_invitation( - receivers=email_table.load_all_mails(dynamodb), - day=best_date.date, - bitpoll_link=poll_website + receivers=emails, + start=best_date, + bitpoll_link=poll_website, ) + poll.event_scheduled_update(event_date=best_date) + return poll if __name__ == '__main__': diff --git a/scheduler/bitpoll.py b/scheduler/bitpoll.py index 904f04b..3116758 100644 --- a/scheduler/bitpoll.py +++ b/scheduler/bitpoll.py @@ -19,8 +19,10 @@ class VoteDate(BaseModel): @staticmethod def from_bitpoll_date(date: str) -> "VoteDate": locale.setlocale(locale.LC_TIME, 'de_DE') + date = datetime.strptime(date, "%a, %d. %b. %Y") + date = date.replace(hour=18, minute=30) return VoteDate( - date=datetime.strptime(date, "%a, %d. %b. %Y"), + date=date, yes_count=0, no_count=0, probably_no_count=0, diff --git a/scheduler/dynamodb/email_table.py b/scheduler/dynamodb/email_table.py index 036b3bb..f785c3b 100644 --- a/scheduler/dynamodb/email_table.py +++ b/scheduler/dynamodb/email_table.py @@ -3,6 +3,8 @@ from pydantic import BaseModel +from scheduler import env + class EmailItem(BaseModel): email: str @@ -14,7 +16,8 @@ def add(dynamodb, email: EmailItem): def load_all_mails(dynamodb) -> List[str]: - return [i.email for i in load_all(dynamodb)] + registered = [i.email for i in load_all(dynamodb)] + return list(set(registered + [env.get_gmail_sender_address()])) def load_all(dynamodb) -> List[EmailItem]: diff --git a/scheduler/dynamodb/poll_table.py b/scheduler/dynamodb/poll_table.py index 6d67c86..a75ab57 100644 --- a/scheduler/dynamodb/poll_table.py +++ b/scheduler/dynamodb/poll_table.py @@ -8,8 +8,33 @@ class PollItem(BaseModel): - running_poll_id: Optional[str] = None - start_next_poll_date: Optional[datetime] = None + running_poll_id: str + start_next_poll_date: datetime + new_poll_email_sent: datetime + event_invitation_email_sent: Optional[datetime] = None + + @staticmethod + def create_new(poll_id: str, next_poll_date: datetime) -> "PollItem": + return PollItem( + running_poll_id=poll_id, + start_next_poll_date=next_poll_date, + new_poll_email_sent=datetime.now() + ) + + def event_scheduled_update(self, event_date: datetime): + self.start_next_poll_date = event_date + self.event_invitation_email_sent = datetime.now() + + def poll(self) -> bool: + return ( + self.running_poll_id and + datetime.now() < self.start_next_poll_date and + self.event_invitation_email_sent is None + ) + + def is_time_to_start_new_poll(self) -> bool: + return datetime.now() >= self.start_next_poll_date + def load(dynamodb) -> PollItem: try: diff --git a/scheduler/gmail.py b/scheduler/gmail.py index 8252b37..e546bea 100644 --- a/scheduler/gmail.py +++ b/scheduler/gmail.py @@ -14,42 +14,43 @@ def send_bitpoll_invitation(receivers: List[str], bitpoll_link: str): html = load_html_template("templates/poll_invitation.html") html = html.replace("YOUR_BITPOLL_LINK_HERE", bitpoll_link) - send_email( + send( receivers=receivers, - subject=f"Schafkopfen", - body=MIMEText(html, "html"), + subject="New Schafkopf Round", + body=MIMEText(html, "html") ) -def send_schafkopf_meeting_invitation(receivers: List[str], day: datetime, bitpoll_link: str): - start=datetime(year=day.year, month=day.month, day=day.day, hour=18, minute=30) - end=datetime(year=day.year, month=day.month, day=day.day, hour=23) - +def send_schafkopf_meeting_invitation(receivers: List[str], start: datetime, bitpoll_link: str): html = load_html_template("templates/schafkopf_scheduled.html") html = html.replace("SCHEDULED_DATE_PLACEHOLDER", format_datetime(start)) html = html.replace("YOUR_BITPOLL_LINK_HERE", bitpoll_link) - send_email( - receivers=list(set(receivers + [env.get_gmail_sender_address()])), - subject=f"Schafkopfen on {day.strftime('%d.%m')}", + send( + receivers=receivers, + subject=f"Schafkopfen on {start.strftime('%d.%m')}", body=MIMEText(html, "html"), - event=create_calendar_entry( + attachment=create_calendar_entry( summary="[at] Schafkopfen", - start=start, end=end, + start=start, end=start.replace(hour=23, minute=0), ) ) -def send_email(receivers: List[str], subject: str, body: MIMEText, event: Optional[MIMEBase]=None): +def send( + receivers: List[str], + subject: str, + body: MIMEText, + attachment: Optional[MIMEBase]=None +): sender = env.get_gmail_sender_address() - message = MIMEMultipart() message['From'] = sender message['To'] = ", ".join(receivers) message['Subject'] = subject message.attach(body) - if event: - message.attach(event) + if attachment: + message.attach(attachment) smtpserver = smtplib.SMTP_SSL('smtp.gmail.com', 465) smtpserver.ehlo() @@ -83,6 +84,7 @@ def create_calendar_entry(start: datetime, end: datetime, summary: str) -> MIMEB ) return part + def format_datetime(dt: datetime): locale.setlocale(locale.LC_TIME, 'en_US.UTF-8') weekday_name = dt.strftime('%A')