111 lines
3.1 KiB
Python
Executable file
111 lines
3.1 KiB
Python
Executable file
#!/usr/bin/python3
|
|
"""Watch https://osmcal.org/ for another Paris OSM hack weekend."""
|
|
|
|
import email.message
|
|
import email.utils
|
|
from urllib.parse import urlparse
|
|
import smtplib
|
|
import sys
|
|
|
|
import typing
|
|
import requests
|
|
import simplejson.errors
|
|
|
|
MAIL_FROM = "edward@4angle.com"
|
|
MAIL_TO = "edward@4angle.com"
|
|
SMTP_HOST = "4angle.com"
|
|
|
|
URL = "https://osmcal.org/api/v2/events/?in=France"
|
|
|
|
SEEN_EVENT_IDS = {4064, 4606, 4641}
|
|
|
|
|
|
def get_event_id(event: dict[str, typing.Any]) -> int | None:
|
|
"""Extract the numeric event ID from API data."""
|
|
if "id" in event:
|
|
return typing.cast(int, event["id"])
|
|
|
|
path_parts = [part for part in urlparse(event["url"]).path.split("/") if part]
|
|
if len(path_parts) >= 2 and path_parts[-2] == "event":
|
|
try:
|
|
return int(path_parts[-1])
|
|
except ValueError:
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
def get_detailed_location(event: dict[str, typing.Any]) -> str:
|
|
"""Return an event's detailed location, or an empty string if absent."""
|
|
location = event.get("location")
|
|
if not isinstance(location, dict):
|
|
return ""
|
|
|
|
detailed = location.get("detailed")
|
|
return detailed if isinstance(detailed, str) else ""
|
|
|
|
|
|
def send_mail(subject: str, body: str) -> None:
|
|
"""Send an e-mail."""
|
|
msg = email.message.EmailMessage()
|
|
|
|
msg["Subject"] = subject
|
|
msg["To"] = f"Edward Betts <{MAIL_TO}>"
|
|
msg["From"] = f"OSM Calendar alert <{MAIL_FROM}>"
|
|
msg["Date"] = email.utils.formatdate()
|
|
msg["Message-ID"] = email.utils.make_msgid()
|
|
|
|
msg.set_content(body)
|
|
|
|
s = smtplib.SMTP(SMTP_HOST)
|
|
s.sendmail(MAIL_FROM, [MAIL_TO], msg.as_string())
|
|
s.quit()
|
|
|
|
|
|
def check_for_paris_events() -> None:
|
|
"""Check for upcoming OSM events in Paris and send an email if found."""
|
|
r = requests.get(URL)
|
|
try:
|
|
events = r.json()
|
|
except simplejson.errors.JSONDecodeError:
|
|
print(r.text)
|
|
raise
|
|
|
|
all_paris_events = [
|
|
event
|
|
for event in events
|
|
if "paris" in get_detailed_location(event).lower()
|
|
]
|
|
seen_paris_events = [
|
|
event for event in all_paris_events if get_event_id(event) in SEEN_EVENT_IDS
|
|
]
|
|
paris_events = [
|
|
event for event in all_paris_events if get_event_id(event) not in SEEN_EVENT_IDS
|
|
]
|
|
|
|
if not paris_events:
|
|
if sys.stdin.isatty():
|
|
total_events = len(events)
|
|
seen_count = len(seen_paris_events)
|
|
paris_label = "Paris event" if seen_count == 1 else "Paris events"
|
|
print(
|
|
f"{total_events} events found in France; "
|
|
+ f"{seen_count} {paris_label} already seen; "
|
|
+ "no new Paris events."
|
|
)
|
|
return
|
|
|
|
subject = "Upcoming Paris OSM Hack Weekend Found!"
|
|
count = len(paris_events)
|
|
body = f"Found {count} {'event' if count == 1 else 'events'} in Paris:\n\n"
|
|
for event in paris_events:
|
|
body += (
|
|
f"{event['name']} - {event['url']}\n"
|
|
+ f"Date: {event['date']['human']}\n"
|
|
+ f"Location: {get_detailed_location(event)}\n\n"
|
|
)
|
|
send_mail(subject, body)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
check_for_paris_events()
|