-
-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Love the idea of this app but I don't use discord so I asked Gemini to modify your calendar-to-discord.py script to output to email instead of discord. Amazingly it created a totally usable script. I was surprised and am a bit worried for devs and programmers if AI is getting this smart.
Anyways, anyone who would prefer output to email instead of discord can implement it on their system with these instructions.
Add the following to the environment section of your docker-compose.yml, example assumes gmail is the mail server
environment:
EMAIL_SENDER: youremail@gmail.com
EMAIL_RECEIVER: youremail@gmail.com
EMAIL_PASSWORD: youremailapppassword
SMTP_SERVER: smtp.gmail.com
SMTP_PORT: 587
Add the following to the volumes section of docker-compose.yml. This causes the container to run this version of the script instead of the discord version in the container
volumes:
- ./calendarr-to-email.py:/app/src/main.py
Save the following script as calendarr-to-email.py in the same folder that you have your docker-compose.yml file in. This makes the script available to map using the volumes entry you added above.
import requests
import icalendar
import recurring_ical_events
import datetime
import sys
import os
import json
import re
import smtplib
from email.mime.text import MIMEText
def get_events_for_week(ical_urls, start_week_on_monday=True):
# Use today's date
base_date = datetime.date.today()
# Calculate start of week based on preference
if start_week_on_monday:
# Start on Monday (0 = Monday in our calculation)
start_offset = (7 - base_date.weekday()) % 7
else:
# Start on Sunday (6 = Sunday in our calculation)
start_offset = (7 - (base_date.weekday() + 1) % 7) % 7
if start_offset == 0:
start_offset = 7
start_of_week_date = base_date + datetime.timedelta(days=start_offset)
end_of_week_date = start_of_week_date + datetime.timedelta(days=6)
# Convert those dates to full datetimes
start_of_week = datetime.datetime.combine(start_of_week_date, datetime.time.min)
end_of_week = datetime.datetime.combine(end_of_week_date, datetime.time.max)
all_events = []
for url_info in ical_urls:
url = url_info["url"]
source_type = url_info["type"] # "tv" or "movie"
response = requests.get(url)
if response.status_code != 200:
print(f"Failed to fetch iCal from {url}: {response.status_code}")
continue
calendar = icalendar.Calendar.from_ical(response.content)
events = recurring_ical_events.of(calendar).between(
start_of_week,
end_of_week
)
# Convert date-only events to datetime
for event in events:
dtstart = event.get('DTSTART').dt
# Strip timezone if present
if isinstance(dtstart, datetime.datetime) and dtstart.tzinfo is not None:
dtstart = dtstart.replace(tzinfo=None)
if isinstance(dtstart, datetime.date) and not isinstance(dtstart, datetime.datetime):
dtstart = datetime.datetime(dtstart.year, dtstart.month, dtstart.day)
event['DTSTART'].dt = dtstart
# Add source type to each event
for event in events:
event["SOURCE_TYPE"] = source_type
all_events.extend(events)
return all_events, start_of_week, end_of_week
def create_show_report(events, start_date, end_date):
if not events:
return ""
# Count TV episodes and movies
tv_count = sum(1 for e in events if e.get("SOURCE_TYPE") == "tv")
movie_count = sum(1 for e in events if e.get("SOURCE_TYPE") == "movie")
# Sort events by date
sorted_events = sorted(events, key=lambda e: e.get('DTSTART').dt)
# Group events by day
days = {}
for event in sorted_events:
start = event.get('DTSTART').dt
source_type = event.get("SOURCE_TYPE")
day_key = start.strftime('%A, %b %d')
if day_key not in days:
days[day_key] = {"tv": [], "movie": []}
summary = event.get('SUMMARY', 'Untitled Event')
# Check if this is a season premiere
is_premiere = False
if source_type == "tv" and re.search(r'[-\s](?:s\d+e01|(?:\d+x01))\b', summary.lower()):
is_premiere = True
# Process TV show titles
if source_type == "tv":
parts = re.split(r'\s+-\s+', summary, 1)
if len(parts) == 2:
show_name = parts[0]
episode_info = parts[1]
sub_parts = re.split(r'\s+-\s+', episode_info, 1)
if len(sub_parts) == 2:
episode_num, episode_title = sub_parts
else:
episode_num = episode_info
episode_title = ""
line = f"{start.strftime('%I:%M %p')}: {show_name} - {episode_num} - {episode_title}"
else:
line = f"{start.strftime('%I:%M %p')}: {summary}"
if is_premiere:
line += " 🎉"
days[day_key]["tv"].append(line)
else: # movie
days[day_key]["movie"].append(f"🎬 {summary}")
# Generate the full report text
report_parts = []
# Header
header_text = f"TV Guide ({start_date.strftime('%b %d')} - {end_date.strftime('%b %d')})"
report_parts.append(f"Subject: {header_text}")
report_parts.append("\n" + "="*len(header_text) + "\n")
report_parts.append(f"{tv_count} new episodes and {movie_count} movies this week.\n\n")
# Body
day_order = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
# Sort days dictionary by day of the week
sorted_days = sorted(days.items(), key=lambda item: day_order.index(item[0].split(',')[0]))
for day, content in sorted_days:
report_parts.append(f"**{day}**")
if content["tv"]:
report_parts.append("\n".join(content["tv"]))
if content["movie"]:
report_parts.append("\n\n**MOVIES**")
report_parts.append("\n".join(content["movie"]))
report_parts.append("\n\n")
return "\n".join(report_parts)
def send_report_to_email(sender, receiver, password, smtp_server, smtp_port, report_content):
try:
msg = MIMEText(report_content)
# Extract subject from the first line of the report
subject = report_content.split('\n')[0].replace("Subject: ", "")
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = receiver
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(sender, password)
server.send_message(msg)
print(f"Report successfully sent to {receiver}")
return True
except Exception as e:
print(f"Failed to send email: {e}")
return False
def main():
print("Starting Calendar to Email script")
# Get environment variables
email_sender = os.environ.get("EMAIL_SENDER")
email_receiver = os.environ.get("EMAIL_RECEIVER")
email_password = os.environ.get("EMAIL_PASSWORD")
smtp_server = os.environ.get("SMTP_SERVER")
smtp_port = os.environ.get("SMTP_PORT", 587) # Default to common TLS port
calendar_urls_json = os.environ.get("CALENDAR_URLS")
start_week_on_monday = os.environ.get("START_WEEK_ON_MONDAY", "true").lower() == "true"
# Validate required email variables
if not all([email_sender, email_receiver, email_password, smtp_server]):
print("Error: EMAIL_SENDER, EMAIL_RECEIVER, EMAIL_PASSWORD, and SMTP_SERVER environment variables must be set.")
sys.exit(1)
if not calendar_urls_json:
print("Error: CALENDAR_URLS environment variable not set")
sys.exit(1)
try:
calendar_urls = json.loads(calendar_urls_json)
if not isinstance(calendar_urls, list):
raise ValueError("CALENDAR_URLS must be a JSON array")
except json.JSONDecodeError:
print("Error: CALENDAR_URLS is not valid JSON")
sys.exit(1)
try:
# Get events
print(f"Fetching events from {len(calendar_urls)} calendars")
events, start_date, end_date = get_events_for_week(calendar_urls, start_week_on_monday)
events_count = len(events)
print(f"Found {events_count} events")
# Create report string
print("Creating email report")
report_content = create_show_report(events, start_date, end_date)
if not report_content:
print("No events found for the week. Skipping email.")
sys.exit(0)
# Send report via email
print(f"Sending email to {email_receiver}")
success = send_report_to_email(
sender=email_sender,
receiver=email_receiver,
password=email_password,
smtp_server=smtp_server,
smtp_port=int(smtp_port),
report_content=report_content
)
if success:
print("Script completed successfully")
else:
print("Script failed to send email")
sys.exit(1)
except Exception as e:
print(f"Error in main function: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Start the container. You should receive a startup email. If not, check the container logs for errors (like bad app password for email).
Those who want output other than discord or email might consider using this approach and asking Gemini to create a version. Have fun and thanks to @jordanlambrecht for creating this useful app!
Metadata
Metadata
Assignees
Labels
Projects
Status