import os import smtplib import ssl from datetime import datetime, timedelta import pytz from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import pandas as pd import gradio as gr import logging from urllib.parse import urlencode # === Logging Setup === logging.basicConfig(filename='appointment_bot.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # === Constants === EXCEL_FILE = "appointments.xlsx" TIME_SLOTS = [f"{hour:02d}:00" for hour in range(9, 18)] # 9 AM to 5 PM DATE_OPTIONS = [ (datetime.now() + timedelta(days=i)).strftime("%Y-%m-%d (%A)") for i in range(0, 15) ] COMMON_TIMEZONES = [ "UTC", "Asia/Karachi", "US/Pacific", "US/Eastern", "Europe/London", "Asia/Tokyo", "America/New_York", "Asia/Dubai" ] # === Email Configuration === SMTP_SERVER = "mail.mubashirdev.com" SMTP_PORT = 465 SMTP_USERNAME = os.getenv("SMTP_USERNAME") SMTP_PASSWORD = os.getenv("SMTP_PASSWORD") FROM_NAME = "Mubashir Hussain" # Validate environment variables if not SMTP_USERNAME or not SMTP_PASSWORD: raise Exception("SMTP_USERNAME and SMTP_PASSWORD environment variables are not set.") FROM_EMAIL = SMTP_USERNAME # === Utility Functions === def send_email(to_email, subject, body): try: msg = MIMEMultipart() msg["From"] = f"{FROM_NAME} <{FROM_EMAIL}>" msg["To"] = to_email msg["Subject"] = subject msg.attach(MIMEText(body, "plain")) context = ssl.create_default_context() # Try custom SMTP server first try: with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, context=context, timeout=5) as server: server.login(SMTP_USERNAME, SMTP_PASSWORD) server.send_message(msg) except Exception as e: logging.warning(f"Custom SMTP failed: {e}. Falling back to Gmail.") # Fallback to Gmail with smtplib.SMTP("smtp.gmail.com", 587) as server: server.starttls(context=context) server.login(SMTP_USERNAME, SMTP_PASSWORD) server.send_message(msg) logging.info(f"Email sent to {to_email}") return True except Exception as e: logging.error(f"Email error: {e}") raise Exception(f"Failed to send email: {str(e)}") def save_to_excel(data): try: df = pd.DataFrame([data]) mode = 'a' if os.path.exists(EXCEL_FILE) else 'w' with pd.ExcelWriter(EXCEL_FILE, engine='openpyxl', mode=mode, if_sheet_exists='overlay' if mode == 'a' else None) as writer: df.to_excel(writer, index=False, sheet_name='Sheet1', header=not os.path.exists(EXCEL_FILE)) logging.info(f"Appointment saved: {data['Name']}, {data['Email']}") return True except Exception as e: logging.error(f"Excel save error: {e}") raise Exception(f"Failed to save appointment: {str(e)}") def create_calendar_link(name, email, date, time, timezone): try: tz = pytz.timezone(timezone) start_time = datetime.strptime(f"{date} {time}", "%Y-%m-%d %H:%M").replace(tzinfo=tz) end_time = start_time + timedelta(hours=1) start_time_utc = start_time.astimezone(pytz.UTC).strftime("%Y%m%dT%H%M%SZ") end_time_utc = end_time.astimezone(pytz.UTC).strftime("%Y%m%dT%H%M%SZ") event = { "action": "TEMPLATE", "text": f"Appointment with {FROM_NAME}", "details": f"Appointment for {name} ({email}). Contact: https://mubashirdev.com", "dates": f"{start_time_utc}/{end_time_utc}", "ctz": timezone } return f"https://www.google.com/calendar/render?{urlencode(event)}" except Exception as e: logging.error(f"Calendar link error: {e}") raise Exception("Failed to generate calendar link.") # === Core Logic === def book_appointment(name, email, date, time, timezone, message): try: # Input validation if not all([name, email, date, time, timezone]): raise Exception("Please fill out all required fields.") date_clean = date.split(" ")[0] # Extract YYYY-MM-DD tz = pytz.timezone(timezone) appointment_time = datetime.strptime(f"{date_clean} {time}", "%Y-%m-%d %H:%M").replace(tzinfo=tz) if appointment_time < datetime.now(tz): raise Exception("Appointments must be in the future.") # Prepare data data = { "Name": name, "Email": email, "Date": date_clean, "Time": time, "Timezone": timezone, "Message": message or "No message", "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } # Save to Excel save_to_excel(data) # Generate calendar link calendar_link = create_calendar_link(name, email, date_clean, time, timezone) # Send emails admin_msg = f"New Appointment:\nName: {name}\nEmail: {email}\nDate: {date_clean}\nTime: {time}\nTimezone: {timezone}\nMessage: {message or 'No message'}" send_email("admin@mubashirdev.com", "📅 New Appointment", admin_msg) user_msg = f"Hi {name},\n\nYour appointment is confirmed for {date_clean} at {time} ({timezone}).\nAdd to Google Calendar: {calendar_link}\n\nYour message: {message or 'No message'}\n\nThank you!\nMubashir Hussain\nhttps://mubashirdev.com" send_email(email, "✅ Appointment Confirmed", user_msg) # Generate chart chart_data = { "type": "bar", "data": { "labels": [f"{date_clean} {time}"], "datasets": [{ "label": "Appointment", "data": [1], "backgroundColor": "#003087", "borderColor": "#001F5C", "borderWidth": 2 }] }, "options": { "scales": { "y": {"beginAtZero": True, "ticks": {"stepSize": 1, "color": "#333"}, "title": {"display": True, "text": "Confirmed", "color": "#333"}}, "x": {"title": {"display": True, "text": "Date & Time", "color": "#333"}, "ticks": {"color": "#333"}} }, "plugins": { "title": {"display": True, "text": f"Appointment for {name}", "color": "#003087", "font": {"size": 18}} } } } return ( f"✅ Appointment confirmed for {date_clean} at {time} ({timezone}). Email sent to {email}.", [chart_data] ) except Exception as e: logging.error(f"Booking error: {str(e)}") return str(e), [] # === UI Enhancements === JS_ENHANCEMENT = """ """ # === Gradio UI === with gr.Blocks(theme=gr.themes.Soft(), css=""" .gradio-container { max-width: 700px; margin: 20px auto; background: #E6F0FA; padding: 24px; border-radius: 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); } .welcome { text-align: center; font-size: 28px; color: #003087; margin: 0 0 20px; font-weight: 700; } .gr-textbox, .gr-dropdown { border-radius: 8px; border: 2px solid #003087; padding: 12px; font-size: 16px; background: white; transition: border-color 0.2s; } .gr-textbox:focus, .gr-dropdown:focus { border-color: #001F5C; outline: none; } .gr-button { background: #003087; color: white; font-size: 16px; font-weight: 600; padding: 12px 32px; border-radius: 8px; border: none; margin: 24px auto; display: block; transition: background 0.2s; } .gr-button:hover { background: #001F5C; } .gr-textbox[label="Result"] { background: #E8F5E9; border-color: #2E7D32; color: #1B5E20; font-weight: 500; padding: 16px; border-radius: 8px; } .gr-column { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); } .footer { text-align: center; font-size: 14px; color: #003087; margin-top: 20px; } .logo { display: block; margin: 0 auto 16px; max-width: 150px; } .required::after { content: " *"; color: #D32F2F; } @media (max-width: 600px) { .gradio-container { padding: 16px; margin: 10px; } .welcome { font-size: 24px; } .gr-button { padding: 10px 24px; } } """) as demo: gr.HTML( f"""
Schedule Your Appointment

Select a date and time below to book with Mubashir Hussain. You'll receive a confirmation email with a Google Calendar link.

{JS_ENHANCEMENT} """ ) with gr.Column(): name = gr.Textbox(label="Your Name", placeholder="Enter your name", elem_classes="required") email = gr.Textbox(label="Your Email", placeholder="Enter your email", elem_classes="required") date = gr.Dropdown(label="Date", choices=DATE_OPTIONS, value=DATE_OPTIONS[0], elem_classes="required") time = gr.Dropdown(label="Time", choices=TIME_SLOTS, value="09:00", elem_classes="required") timezone = gr.Dropdown(label="Timezone", choices=COMMON_TIMEZONES, value="Asia/Karachi", elem_classes="required") message = gr.Textbox(label="Message (Optional)", placeholder="Add any details or notes", lines=3) submit_btn = gr.Button("Book Appointment") output = gr.Textbox(label="Result", interactive=False) chart_output = gr.Plot(label="Confirmation") gr.Markdown( """ """ ) submit_btn.click( fn=book_appointment, inputs=[name, email, date, time, timezone, message], outputs=[output, chart_output] ) # Launch the app (no port specification for Hugging Face Spaces) demo.launch(share=False)