mubashirBot / app.py
mubashirhussaindev's picture
Update app.py
7a7b5e4 verified
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)