Spaces:
Sleeping
Sleeping
| 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 = """ | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const dateDropdown = document.querySelector('select[id*="date"]'); | |
| if (dateDropdown) { | |
| dateDropdown.style.background = '#E6F0FA'; | |
| dateDropdown.style.border = '2px solid #003087'; | |
| dateDropdown.style.borderRadius = '8px'; | |
| dateDropdown.style.padding = '12px'; | |
| dateDropdown.style.fontSize = '16px'; | |
| const today = new Date().toISOString().split('T')[0]; | |
| Array.from(dateDropdown.options).forEach(option => { | |
| if (option.value.includes(today)) { | |
| option.style.fontWeight = 'bold'; | |
| option.style.color = '#003087'; | |
| } | |
| }); | |
| } | |
| }); | |
| </script> | |
| """ | |
| # === 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""" | |
| <img src="https://ik.imagekit.io/kqeykkpy5/WhatsApp_Image_2024-12-20_at_02.57.39-removebg-preview.png?updatedAt=1747857266922" class="logo" alt="Mubashir Hussain Logo"> | |
| <div class="welcome">Schedule Your Appointment</div> | |
| <p style="text-align: center; color: #333;">Select a date and time below to book with Mubashir Hussain. You'll receive a confirmation email with a Google Calendar link.</p> | |
| {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( | |
| """ | |
| <div class="footer">Powered by <a href="https://mubashirdev.com" style="color: #003087; text-decoration: none;">mubashirdev.com</a></div> | |
| """ | |
| ) | |
| 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) |