Skip to content

Commit

Permalink
main flow with dynamodb works
Browse files Browse the repository at this point in the history
  • Loading branch information
lbernhard95 committed Oct 30, 2024
1 parent 894efac commit 51956c9
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 148 deletions.
73 changes: 72 additions & 1 deletion lambda_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,75 @@
from datetime import datetime

import boto3

from scheduler import gmail, bitpoll, scheduler
from scheduler.dynamodb import poll_table, email_table
from scheduler.dynamodb.poll_table import PollItem


def lambda_handler(event, context):
print(event)
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)
return

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:
print("Start a new poll")
print("Generate csrf token")
csrf_token = bitpoll.get_valid_csrf_token()
print("csrf token:", csrf_token)

print("Create new poll")
poll_id = bitpoll.create_new_poll(csrf_token=csrf_token)
print("New poll created")

print("Generate dates to vote on")
dates = scheduler.generate_working_days_for_next_weeks(weeks=2)

print("Add dates to poll as choices")
bitpoll.add_choices_to_poll(poll_id=poll_id, csrf_token=csrf_token, dates=dates)
new_poll_website = bitpoll.get_website_from_poll_id(poll_id)
print("Poll created:", new_poll_website)

print("Send out email notifications")
gmail.send_bitpoll_invitation(
receivers=email_table.load_all_mails(dynamodb),
bitpoll_link=new_poll_website
)
return PollItem(
running_poll_id=poll_id,
start_next_poll_date=max(dates)
)


def schedule_next_schafkopf_event(dynamodb, poll_id: str):
poll_website = bitpoll.get_website_from_poll_id(poll_id)
print("Try to schedule next schafkopf event for:", poll_website)
page = bitpoll.get_poll_webpage(poll_id=poll_id)
votes = bitpoll.collect_vote_dates(page)
best_date = scheduler.find_best_date(votes)
print("Most promising date:", best_date)

if best_date:
print("Found valid date, sending out invitation")
gmail.send_schafkopf_meeting_invitation(
receivers=email_table.load_all_mails(dynamodb),
day=best_date.date,
bitpoll_link=poll_website
)


if __name__ == '__main__':
from scheduler import env # ensure loading aws credentials
lambda_handler({}, {})
50 changes: 0 additions & 50 deletions main.py

This file was deleted.

67 changes: 0 additions & 67 deletions scheduler/aws/dynamodb.py

This file was deleted.

48 changes: 26 additions & 22 deletions scheduler/bitpoll.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@

from datetime import datetime
from typing import List
from typing import List, Optional
from uuid import uuid4
from pydantic import BaseModel, computed_field
import requests
from bs4 import BeautifulSoup

import locale

BITPOLL_URL = 'https://bitpoll.de'
BROWSER_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Referer': 'https://bitpoll.de/',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://bitpoll.de',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Priority': 'u=0, i',
'TE': 'trailers',
}

class VoteDate(BaseModel):
date: datetime
Expand All @@ -34,6 +18,7 @@ class VoteDate(BaseModel):

@staticmethod
def from_bitpoll_date(date: str) -> "VoteDate":
locale.setlocale(locale.LC_TIME, 'de_DE')
return VoteDate(
date=datetime.strptime(date, "%a, %d. %b. %Y"),
yes_count=0,
Expand Down Expand Up @@ -112,7 +97,7 @@ def create_new_poll(csrf_token: str):
'one_vote_per_user': 'on',
}

response = requests.post(f"{BITPOLL_URL}/", headers=BROWSER_HEADERS, data=data)
response = requests.post(f"{BITPOLL_URL}/", headers=get_headers(csrf_token), data=data)

if response.status_code != 200:
raise ValueError(response.text)
Expand All @@ -124,7 +109,7 @@ def get_valid_csrf_token() -> str:
session = requests.Session()

# Step 1: Make an initial GET request to the Bitpoll homepage to get the CSRF token
response = session.get(f"{BITPOLL_URL}/", headers=BROWSER_HEADERS)
response = session.get(f"{BITPOLL_URL}/", headers=get_headers(None))
response.raise_for_status() # Ensure the request was successful

# Step 2: Parse the HTML to find the CSRF token
Expand All @@ -137,11 +122,30 @@ def add_choices_to_poll(poll_id: str, csrf_token: str, dates: List[datetime]):
'csrfmiddlewaretoken': csrf_token,
"dates": ",".join([d.strftime("%Y-%m-%d") for d in dates])
}
response = requests.post(f"{get_website_from_poll_id(poll_id)}/edit/choices/date/", headers=BROWSER_HEADERS, data=data)
response = requests.post(f"{get_website_from_poll_id(poll_id)}/edit/choices/date/", headers=get_headers(csrf_token), data=data)

if response.status_code != 200:
raise ValueError(response.text)


def get_website_from_poll_id(poll_id: str) -> str:
return f"{BITPOLL_URL}/poll/{poll_id}"
return f"{BITPOLL_URL}/poll/{poll_id}"

def get_headers(csrf_token: Optional[str]) -> {}:
return {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Referer': 'https://bitpoll.de/',
'Cookie': f'csrftoken = {csrf_token}' if csrf_token else '',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://bitpoll.de',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Priority': 'u=0, i',
'TE': 'trailers',
}
File renamed without changes.
38 changes: 38 additions & 0 deletions scheduler/dynamodb/email_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import json
from typing import List

from pydantic import BaseModel


class EmailItem(BaseModel):
email: str


def add(dynamodb, email: EmailItem):
table = dynamodb.Table("schafkopf_emails")
table.put_item(Item=json.loads(email.model_dump_json()))


def load_all_mails(dynamodb) -> List[str]:
return [i.email for i in load_all(dynamodb)]


def load_all(dynamodb) -> List[EmailItem]:
try:
table = dynamodb.Table("schafkopf_emails")
response = table.scan()
items = response["Items"]
while "LastEvaluatedKey" in response:
response = table.scan(ExclusiveStartKey=response["LastEvaluatedKey"])
items.extend(response["Items"])
result = []
for item in items:
try:
result.append(EmailItem(**item))
except Exception as e:
print(f"Could not load {item}: {e}")
return result
except Exception as e:
raise RuntimeError(
f"Could not load emails: {e}"
)
33 changes: 33 additions & 0 deletions scheduler/dynamodb/poll_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json
from datetime import datetime
from typing import Optional

from pydantic import BaseModel

POLL_ITEM_UUID = '021dae01-ac37-4c3c-bc6c-952d3e4a57d5'


class PollItem(BaseModel):
running_poll_id: Optional[str] = None
start_next_poll_date: Optional[datetime] = None

def load(dynamodb) -> PollItem:
try:
table = dynamodb.Table("schafkopf_polls")
response = table.query(
KeyConditionExpression=f"#u = :a",
ExpressionAttributeNames={'#u': "uuid"},
ExpressionAttributeValues={":a": POLL_ITEM_UUID},
Limit=1,
)
return PollItem(**response["Items"][0])
except Exception as e:
raise ValueError(
f"Could not find poll item", e
)

def update(dynamodb, poll_item: PollItem):
table = dynamodb.Table("schafkopf_polls")
item_dict = json.loads(poll_item.model_dump_json())
item_dict['uuid'] = POLL_ITEM_UUID
table.put_item(Item=item_dict)
Loading

0 comments on commit 51956c9

Please sign in to comment.