diff --git a/cloud/APIs/twilio.py b/cloud/APIs/twilio.py index e69de29..b4f6d16 100644 --- a/cloud/APIs/twilio.py +++ b/cloud/APIs/twilio.py @@ -0,0 +1,58 @@ +import os +import logging +from twilio.rest import Client + +logger = logging.getLogger(__name__) + +def send_alert_sms(to_phone: str, room_title: str, room_link: str = None, client_email: str = None) -> bool: + """ + Sends an SMS alert using Twilio notifying the client of an issue and support room creation. + """ + account_sid = os.environ.get("TWILIO_SID") + auth_token = os.environ.get("TWILIO_ACCOUNT_SECRET") + from_phone = os.environ.get("TWILIO_PHONE_NUMBER") # Requires your Twilio sender phone number + + if not account_sid or not auth_token or not from_phone: + logger.error("Twilio environment variables (TWILIO_SID, TWILIO_ACCOUNT_SECRET, TWILIO_PHONE_NUMBER) missing.") + return False + + if not to_phone: + logger.warning("No recipient phone number provided for SMS.") + return False + + # --- PRINT LINK/INFO BEFORE SENDING --- + print("\n" + "="*50) + print(f"[Twilio] Preparing SMS for: {to_phone}") + print(f"[Twilio] Room Title: {room_title}") + if room_link: + print(f"[Twilio] Room Link: {room_link}") + else: + print(f"[Twilio] Link: Invitation email sent to {client_email or 'client inbox'}") + print("="*50 + "\n") + + # --- BUILD SMS CONTENT --- + body = ( + f"Alert: A technical issue occurred with your equipment/microwave. " + f"A support chat room '{room_title}' has been created to assist you.\n\n" + ) + + if room_link: + body += f"Join chat room: {room_link}\n" + + if client_email: + body += f"An invitation link has also been sent to your email ({client_email})." + else: + body += "An invitation link has been sent to your inbox." + + try: + client = Client(account_sid, auth_token) + message = client.messages.create( + body=body, + from_=from_phone, + to=to_phone + ) + logger.info(f"SMS sent successfully to {to_phone}. Message SID: {message.sid}") + return True + except Exception as e: + logger.exception(f"Failed to send SMS via Twilio to {to_phone}: {e}") + return False \ No newline at end of file diff --git a/cloud/app.py b/cloud/app.py index b878448..379f171 100644 --- a/cloud/app.py +++ b/cloud/app.py @@ -13,6 +13,7 @@ from APIs.webex import WebexManager from microwaveCookPlanner import MicrowaveCookPlanner import safety_checker import pymongo +from APIs.twilio import send_alert_sms sys.path.insert(0, '..') try: @@ -43,6 +44,7 @@ WEBEX_CLIENT_SECRET = os.getenv("WEBEX_CLIENT_SECRET", "YOUR_WEBEX_CLIENT_SECRET WEBEX_REDIRECT_URI = os.getenv("WEBEX_REDIRECT_URI", "https://smartwave.matthiasg.dev/oauth/callback") WEBEX_TEAM_ID = os.getenv("WEBEX_TEAM_ID", "YOUR_WEBEX_TEAM_ID") WEBEX_NINLUC_ID = os.getenv("WEBEX_NINLUC_ID", "YOUR_WEBEX_NINLUC_ID") +SAVEUP_TWILIO_API_TOKEN = os.getenv("SAVEUP_TWILIO_API_TOKEN", False) # Ensure the camera image storage directory exists when the app starts CAMERA_IMAGE_DIR = "storage/dishPhotos" @@ -176,14 +178,12 @@ async def cooking_params(): def alert(): data = request.get_json() or {} - # 1. Inject a UTC timestamp so we can easily query the 3-minute window + # 1. Inject UTC timestamp data["created_at"] = datetime.now(timezone.utc) saved_alert = alert_collection.insert_one(data) - # Extract orchestrator_id from the incoming alert orchestrator_id = data.get("orchestrator_id") - # Safely extract alert info (prevents errors if "alert" is missing) alert_info = data.get("alert", {}) alert_type = alert_info.get("type", "Unknown") alert_message = alert_info.get("message", "No message provided") @@ -191,6 +191,7 @@ def alert(): # --- GET CLIENT METADATA --- client_name = f"Unknown Client (Orchestrator {orchestrator_id})" if orchestrator_id else "Unknown Client" client_email = None + client_phone = None if orchestrator_id: client_doc = client_collection.find_one({ @@ -204,25 +205,28 @@ def alert(): if client_doc: client_name = client_doc.get("client_name", client_name) - client_email = client_doc.get("contact_info", {}).get("email") + contact_info = client_doc.get("contact_info", {}) + client_email = contact_info.get("email") + client_phone = contact_info.get("phone") # Extract phone number for Twilio # --- WEBEX ROOM LOGIC --- webex_room_id = None + room_title = f"Support - {client_name}" + room_link = None webex_status = "skipped" - # 2. Check if a room was created for this orchestrator in the last 3 minutes three_minutes_ago = datetime.now(timezone.utc) - timedelta(minutes=3) recent_alert = alert_collection.find_one({ "orchestrator_id": orchestrator_id, "webex_room_id": {"$exists": True, "$ne": None}, "created_at": {"$gte": three_minutes_ago}, - "_id": {"$ne": saved_alert.inserted_id} # Exclude the alert we just inserted + "_id": {"$ne": saved_alert.inserted_id} }, sort=[("created_at", pymongo.DESCENDING)]) if recent_alert: - # A room was created recently -> Reuse it and just send a message webex_room_id = recent_alert["webex_room_id"] + room_title = recent_alert.get("webex_room_title", room_title) try: webex_manager.send_alert_info_message( room_id=webex_room_id, @@ -236,40 +240,58 @@ def alert(): webex_status = f"message_failed: {str(e)}" else: - # No recent room -> Create a new one try: - # Assuming your modified create_support_room returns a dict with {"id": ...} room_details = webex_manager.create_support_room( client_name, client_email, alert_type, alert_message, - str(saved_alert.inserted_id) # Cast ObjectId to string for JSON serialization + str(saved_alert.inserted_id) ) - # Extract ID based on whether your function returns a dict or just the ID string - webex_room_id = room_details["id"] if isinstance(room_details, dict) else room_details + if isinstance(room_details, dict): + webex_room_id = room_details.get("id") + room_title = room_details.get("title", room_title) + # Set room_link if your webex_manager returns meetingLink or custom URL + room_link = room_details.get("meetingLink") or f"webexteams://im?space={webex_room_id}" + else: + webex_room_id = room_details + room_link = f"webexteams://im?space={webex_room_id}" + webex_status = "created_new_room" except Exception as e: current_app.logger.exception("Failed to create Webex support room") webex_status = f"creation_failed: {str(e)}" - # 3. Update the alert document with the Webex Room ID (whether new or reused) + # Update document with room details if webex_room_id: alert_collection.update_one( {"_id": saved_alert.inserted_id}, - {"$set": {"webex_room_id": webex_room_id}} + {"$set": {"webex_room_id": webex_room_id, "webex_room_title": room_title}} ) - # --- SEND SMS WITH DETAILS --- - # TODO: Add SMS logic here + # --- SEND SMS --- + sms_sent = False + if client_phone and not SAVEUP_TWILIO_API_TOKEN: + sms_sent = send_alert_sms( + to_phone=client_phone, + room_title=room_title, + room_link=room_link, + client_email=client_email + ) + + alert_collection.update_one( + {"_id": saved_alert.inserted_id}, + {"$set": {"sms_sent": sms_sent}} + ) return jsonify({ "status": "success", "message": "Alert saved", "webex_room_id": webex_room_id, - "webex_status": webex_status + "webex_status": webex_status, + "sms_sent": sms_sent }), 200