Compare commits

...

1 Commits

Author SHA1 Message Date
Ibraheem Saleh 9bb6595f43 email-attempt-failed 8 months ago

@ -140,6 +140,37 @@ Scheduled reservation configurations:
}
```
### Email Notifications (Optional)
Configure email notifications to receive alerts when courts are successfully booked:
```json
{
"Email": {
"Enabled": true,
"SmtpServer": "smtp.gmail.com",
"SmtpPort": 587,
"SmtpUsername": "your-email@gmail.com",
"SmtpPassword": "your-app-password",
"FromAddress": "your-email@gmail.com",
"ToAddresses": [
"recipient1@example.com",
"recipient2@example.com"
],
"UseTLS": true
}
}
```
**Gmail Setup:**
1. Enable 2-factor authentication on your Google account
2. Generate an App Password: Google Account → Security → App Passwords
3. Use the 16-character app password as `SmtpPassword`
**Other SMTP Providers:**
- Outlook: `smtp.office365.com`, port 587
- Yahoo: `smtp.mail.yahoo.com`, port 587
- Custom SMTP: Use your provider's settings
## Files
- `config.json` - Main configuration

@ -10,6 +10,9 @@ import os
import sys
import time
import re
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta, timezone
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
@ -84,6 +87,18 @@ class Reservation:
courts: List[CourtReservation]
@dataclass
class EmailConfig:
enabled: bool
smtp_server: str
smtp_port: int
smtp_username: str
smtp_password: str
from_address: str
to_addresses: List[str]
use_tls: bool = True
@dataclass
class Config:
base_api_url: str
@ -97,6 +112,7 @@ class Config:
courts: Dict[str, Court]
reservation_behaviors: Dict[str, ReservationBehavior]
reservations: List[Reservation]
email_config: Optional[EmailConfig] = None
@dataclass
@ -257,6 +273,30 @@ def load_config(config_path: str = "config.json") -> Config:
courts=court_reservations
))
# Parse email configuration (optional)
email_config = None
if "Email" in data:
email_data = data["Email"]
if email_data.get("Enabled", False):
required_email_fields = ["SmtpServer", "SmtpPort", "SmtpUsername", "SmtpPassword", "FromAddress", "ToAddresses"]
for field_name in required_email_fields:
if field_name not in email_data:
raise ValueError(f"Email is enabled but missing required field: {field_name}")
if not isinstance(email_data["ToAddresses"], list) or len(email_data["ToAddresses"]) == 0:
raise ValueError("Email ToAddresses must be a non-empty list of email addresses")
email_config = EmailConfig(
enabled=True,
smtp_server=email_data["SmtpServer"],
smtp_port=email_data["SmtpPort"],
smtp_username=email_data["SmtpUsername"],
smtp_password=email_data["SmtpPassword"],
from_address=email_data["FromAddress"],
to_addresses=email_data["ToAddresses"],
use_tls=email_data.get("UseTLS", True)
)
return Config(
base_api_url=data["BaseAPIUrl"],
company_id=data["CompanyId"],
@ -268,7 +308,8 @@ def load_config(config_path: str = "config.json") -> Config:
accounts=accounts,
courts=courts,
reservation_behaviors=behaviors,
reservations=reservations
reservations=reservations,
email_config=email_config
)
@ -345,6 +386,106 @@ class HarborBot:
with open(self.success_file, 'w') as f:
json.dump(self.success_tracking, f, indent=2)
def _send_success_email(self, court: Court, account: Account, reservation_datetime: str):
"""
Send email notification for successful booking.
Args:
court: The court that was booked
account: The account the booking was made under
reservation_datetime: The datetime of the reservation
"""
if not self.config.email_config or not self.config.email_config.enabled:
return
email_cfg = self.config.email_config
# Parse the reservation datetime for display
try:
# Format: "2026-01-19T17:30:00-08:00"
dt_part = reservation_datetime[:19]
tz_part = reservation_datetime[19:]
dt = datetime.strptime(dt_part, "%Y-%m-%dT%H:%M:%S")
formatted_date = dt.strftime("%A, %B %d, %Y")
formatted_time = dt.strftime("%I:%M %p")
except:
formatted_date = reservation_datetime
formatted_time = ""
subject = f"✅ Court Booked: {court.name} on {formatted_date}"
body_text = f"""Harbor Bot - Successful Court Reservation
Court: {court.name}
Date: {formatted_date}
Time: {formatted_time} (Pacific)
Booked Under:
Name: {account.name}
Account ID: {account.id}
Reservation Details:
Full DateTime: {reservation_datetime}
Court ID: {court.id}
---
This is an automated message from Harbor Bot.
"""
body_html = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background-color: #4CAF50; color: white; padding: 20px; text-align: center;">
<h1 style="margin: 0;"> Court Booked!</h1>
</div>
<div style="padding: 20px; background-color: #f9f9f9;">
<h2 style="color: #333; margin-top: 0;">{court.name}</h2>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Date:</strong></td>
<td style="padding: 10px; border-bottom: 1px solid #ddd;">{formatted_date}</td>
</tr>
<tr>
<td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Time:</strong></td>
<td style="padding: 10px; border-bottom: 1px solid #ddd;">{formatted_time} (Pacific)</td>
</tr>
<tr>
<td style="padding: 10px; border-bottom: 1px solid #ddd;"><strong>Booked Under:</strong></td>
<td style="padding: 10px; border-bottom: 1px solid #ddd;">{account.name} (ID: {account.id})</td>
</tr>
</table>
</div>
<div style="padding: 10px; text-align: center; color: #666; font-size: 12px;">
This is an automated message from Harbor Bot.
</div>
</body>
</html>
"""
try:
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = email_cfg.from_address
msg["To"] = ", ".join(email_cfg.to_addresses)
msg.attach(MIMEText(body_text, "plain"))
msg.attach(MIMEText(body_html, "html"))
if email_cfg.use_tls:
server = smtplib.SMTP(email_cfg.smtp_server, email_cfg.smtp_port)
server.starttls()
else:
server = smtplib.SMTP_SSL(email_cfg.smtp_server, email_cfg.smtp_port)
server.login(email_cfg.smtp_username, email_cfg.smtp_password)
server.sendmail(email_cfg.from_address, email_cfg.to_addresses, msg.as_string())
server.quit()
print(f" [EMAIL] Notification sent to {len(email_cfg.to_addresses)} recipient(s)")
except Exception as e:
print(f" [EMAIL ERROR] Failed to send notification: {e}")
def _get_reservation_key(self, reservation_date: str, court: str, account: str) -> str:
"""Generate unique key for tracking reservation success."""
return f"{reservation_date}_{court}_{account}"
@ -690,6 +831,7 @@ class HarborBot:
if result.get("Success"):
print(f" [SUCCESS] Booked {court.name} for {account.name}")
self._mark_reservation_successful(reservation_date_str, court_res.court, court_res.account)
self._send_success_email(court, account, reservation_datetime)
break
else:
print(f" [FAILED] {result.get('Message', 'Unknown error')}")
@ -871,6 +1013,7 @@ class HarborBot:
if result.get("Success"):
print(f" [SUCCESS] Booked {court.name} for {account.name}")
self._mark_reservation_successful(reservation_date_str, court_res.court, court_res.account)
self._send_success_email(court, account, reservation_datetime)
break
else:
print(f" [FAILED] {result.get('Message', 'Unknown error')}")
@ -963,6 +1106,15 @@ class HarborBot:
print(f" - {res.day} @ {res.time} ({res.reservation_behavior}){trigger_info}")
for cr in res.courts:
print(f" {cr.court} -> {cr.account}")
print(f"\nEmail Notifications:")
if self.config.email_config and self.config.email_config.enabled:
print(f" Status: ENABLED")
print(f" SMTP Server: {self.config.email_config.smtp_server}:{self.config.email_config.smtp_port}")
print(f" From: {self.config.email_config.from_address}")
print(f" To: {', '.join(self.config.email_config.to_addresses)}")
else:
print(f" Status: DISABLED")
def test_booking(self, court_name: str, account_name: str, date_str: str, time_str: str, dry_run: bool = True):
"""
@ -1005,10 +1157,132 @@ class HarborBot:
if result.get("Success"):
print(f"\n[SUCCESS] Booked {court.name} for {account.name}")
self._send_success_email(court, account, reservation_datetime)
else:
print(f"\n[FAILED] {result.get('Message', 'Unknown error')}")
print(f"[DEBUG] Full response: {json.dumps(result, indent=2)}")
def test_email(self):
"""Send a test email to verify email configuration."""
if not self.config.email_config:
print("[ERROR] No email configuration found in config.json")
print("[INFO] Add an 'Email' section to your config.json file")
return False
if not self.config.email_config.enabled:
print("[ERROR] Email is disabled in configuration")
print("[INFO] Set 'Enabled': true in the Email section of config.json")
return False
email_cfg = self.config.email_config
print(f"\n[TEST EMAIL] Sending test email...")
print(f" SMTP Server: {email_cfg.smtp_server}:{email_cfg.smtp_port}")
print(f" From: {email_cfg.from_address}")
print(f" To: {', '.join(email_cfg.to_addresses)}")
print(f" TLS: {email_cfg.use_tls}")
# Create test email content
subject = "🏓 Harbor Bot - Test Email"
body_text = f"""Harbor Bot - Email Test
This is a test email from Harbor Bot to verify your email configuration is working correctly.
Configuration:
SMTP Server: {email_cfg.smtp_server}:{email_cfg.smtp_port}
From: {email_cfg.from_address}
TLS Enabled: {email_cfg.use_tls}
If you received this email, your configuration is correct!
---
Sent at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
body_html = f"""
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background-color: #2196F3; color: white; padding: 20px; text-align: center;">
<h1 style="margin: 0;">🏓 Harbor Bot</h1>
<p style="margin: 10px 0 0 0;">Email Test</p>
</div>
<div style="padding: 20px; background-color: #f9f9f9;">
<p>This is a test email from Harbor Bot to verify your email configuration is working correctly.</p>
<h3 style="color: #333;">Configuration</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="padding: 8px; border-bottom: 1px solid #ddd;"><strong>SMTP Server:</strong></td>
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{email_cfg.smtp_server}:{email_cfg.smtp_port}</td>
</tr>
<tr>
<td style="padding: 8px; border-bottom: 1px solid #ddd;"><strong>From:</strong></td>
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{email_cfg.from_address}</td>
</tr>
<tr>
<td style="padding: 8px; border-bottom: 1px solid #ddd;"><strong>TLS Enabled:</strong></td>
<td style="padding: 8px; border-bottom: 1px solid #ddd;">{email_cfg.use_tls}</td>
</tr>
</table>
<p style="margin-top: 20px; padding: 15px; background-color: #e8f5e9; border-radius: 5px; color: #2e7d32;">
If you received this email, your configuration is correct!
</p>
</div>
<div style="padding: 10px; text-align: center; color: #666; font-size: 12px;">
Sent at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
</div>
</body>
</html>
"""
try:
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = email_cfg.from_address
msg["To"] = ", ".join(email_cfg.to_addresses)
msg.attach(MIMEText(body_text, "plain"))
msg.attach(MIMEText(body_html, "html"))
print(f"\n[INFO] Connecting to SMTP server...")
if email_cfg.use_tls:
server = smtplib.SMTP(email_cfg.smtp_server, email_cfg.smtp_port)
server.set_debuglevel(1) # Enable debug output
print(f"[INFO] Starting TLS...")
server.starttls()
else:
server = smtplib.SMTP_SSL(email_cfg.smtp_server, email_cfg.smtp_port)
server.set_debuglevel(1)
print(f"[INFO] Logging in as {email_cfg.smtp_username}...")
server.login(email_cfg.smtp_username, email_cfg.smtp_password)
print(f"[INFO] Sending email...")
server.sendmail(email_cfg.from_address, email_cfg.to_addresses, msg.as_string())
server.quit()
print(f"\n[SUCCESS] Test email sent successfully!")
print(f"[INFO] Check inbox of: {', '.join(email_cfg.to_addresses)}")
return True
except smtplib.SMTPAuthenticationError as e:
print(f"\n[ERROR] Authentication failed: {e}")
print(f"\n[HELP] For Gmail:")
print(f" 1. Enable 2-factor authentication on your Google account")
print(f" 2. Go to: https://myaccount.google.com/apppasswords")
print(f" 3. Generate an App Password for 'Mail'")
print(f" 4. Use the 16-character app password (no spaces) as SmtpPassword")
return False
except smtplib.SMTPException as e:
print(f"\n[ERROR] SMTP error: {e}")
return False
except Exception as e:
print(f"\n[ERROR] Failed to send test email: {e}")
return False
def main():
@ -1018,6 +1292,7 @@ def main():
parser = argparse.ArgumentParser(description="Harbor Isles Pickleball Court Reservation Bot")
parser.add_argument("--config", default="config.json", help="Path to configuration file")
parser.add_argument("--test-login", action="store_true", help="Test login functionality")
parser.add_argument("--test-email", action="store_true", help="Send a test email to verify email configuration")
parser.add_argument("--show-config", action="store_true", help="Display loaded configuration")
parser.add_argument("--run", action="store_true", help="Run once and check for reservations (one-shot mode)")
parser.add_argument("--daemon", action="store_true", help="Run continuously as a daemon (recommended)")
@ -1044,6 +1319,9 @@ def main():
if args.test_login:
bot.test_login()
if args.test_email:
bot.test_email()
if args.test_book:
court, account, date, time_val = args.test_book
dry_run = not args.live
@ -1057,7 +1335,7 @@ def main():
dry_run = not args.live
bot.run_daemon(dry_run=dry_run)
if not any([args.show_config, args.test_login, args.run, args.test_book, args.daemon]):
if not any([args.show_config, args.test_login, args.test_email, args.run, args.test_book, args.daemon]):
parser.print_help()
except FileNotFoundError as e:

Loading…
Cancel
Save