from flask import Flask, request, make_response import os from datetime import datetime import logging from dotenv import load_dotenv from heyoo import WhatsApp import assemblyai as aai import openai from utility import generateResponse, parse_multiple_transactions, create_inventory, create_sale, read_datalake, delete_transaction from google.cloud import firestore import ast import base64 import requests # load env data load_dotenv() # messenger object messenger = WhatsApp( os.environ["whatsapp_token"], phone_number_id=os.environ["phone_number_id"] ) aai.settings.api_key = os.environ["aai_key"] transcriber = aai.Transcriber() # Authenticate to Firestore with the JSON account key db = firestore.Client.from_service_account_json("firestore-key.json") app = Flask(__name__) VERIFY_TOKEN = "30cca545-3838-48b2-80a7-9e43b1ae8ce4" img_ID = "344744a88ad1098" img_secret = "3c542a40c215327045d7155bddfd8b8bc84aebbf" url = "https://api.imgur.com/3/image" headers = {"Authorization": f"Client-ID {img_ID}"} # Interactive button def messenger_button(recipient_phone, message, header='Transaction Confirmation', footer='', btn_name='Confirm Details'): messenger.send_button( recipient_id=recipient_phone, button={ "header": f"{header}", "body": f"{message}", "footer": f"{footer}", "action": { "button": f"{btn_name}", "sections": [ { "title": "iBank", "rows": [ {"id": "confirm", "title": "Record Transaction", "description": ""}, {"id": "cancel", "title": "Cancel Transaction", "description": ""}, ], } ], }, }, ) def messenger_reply_button(recipient_phone, message, header_message = "*Please confirm the details below before we proceed*:\n\n"): messenger.send_reply_button( recipient_id=f"{recipient_phone}", button={ "type": "button", "body": { "text": f"{header_message} {message}" }, "action": { "buttons": [ { "type": "reply", "reply": { "id": "confirm", "title": "Confirm" } }, { "type": "reply", "reply": { "id": "cancel", "title": "Cancel" } } ] } }, ) def respond(query_str: str): response = "hello, I don't have a brain yet" return response def persist_temporary_transaction(transactions, mobile): """ Persists the transaction data temporarily in Firestore. """ # intent = transactions[0]['intent'].lower() temp_ref = db.collection("users").document(mobile).collection("temp_transactions").document('pending-user-action') data = { "transactions": transactions, "status": "pending", "created_at": datetime.now().isoformat() } temp_ref.set(data) return True def process_user_msg(message, mobile): response = str(generateResponse(message)) parsed_trans_data = parse_multiple_transactions(response) # messenger.send_message(f"{response} \n\n {parsed_trans_data}", mobile) logging.info(f"\nAnswer: {response}\n") intent = parsed_trans_data[0]['intent'].lower() if intent == 'read': response2 = str(read_datalake(mobile, message)) # Check if response is a string and represents a valid image path if isinstance(response2, str) and os.path.isfile(os.path.join(response2)): image_path = os.path.join(response2) print("My image path:", image_path) with open(image_path, "rb") as file: data = file.read() base64_data = base64.b64encode(data) # Upload image to Imgur and get URL response2 = requests.post(url, headers=headers, data={"image": base64_data}) url1= response2.json()["data"]["link"] print(url1) messenger.send_image(image=url1, recipient_id=mobile) else: messenger.send_message(f"{response} \n\n {response2}", recipient_id=mobile) else: # Persist transaction data temporarily for Create, Update, or Delete operations persist = persist_temporary_transaction(parsed_trans_data, mobile) if persist: # messenger.send_message(f"{response} \n\n {parsed_trans_data}", mobile) # Give user the chance to confirm/cancel transaction before processing other intents messenger_reply_button(mobile, f"{response}") else: messenger.send_message("Please try again!", mobile) return True def process_intent(parsed_trans_data, mobile): intent = parsed_trans_data[0]['intent'].lower() trans_type = parsed_trans_data[0]['transaction_type'].lower() if intent == 'create': if trans_type in ('purchase', 'purchases', 'inventory'): if create_inventory(mobile, parsed_trans_data): firestore_msg = "Transaction recorded successfully!" else: firestore_msg = "Sorry, could not record transaction!" elif trans_type in ('sale', 'sales'): if create_sale(mobile, parsed_trans_data): firestore_msg = "Transaction recorded successfully!" else: firestore_msg = "Sorry, could not record transaction!" elif intent == 'update': pass elif intent == 'delete': item = parsed_trans_data[0]['details']['item'] item_deleted = delete_transaction(mobile, parsed_trans_data) if item_deleted: firestore_msg = f"You successfully deleted {item} from {trans_type}!" else: firestore_msg = f"Sorry, could not delete the {item} from {trans_type}!" # elif intent == 'read': # response = str(read_datalake(mobile, message)) # parsed_trans_data = "" # firestore_msg = response else: firestore_msg = f'The detected intent, {intent}, is not currently supported!' # return firestore_msg, parsed_trans_data, response return firestore_msg def handle_interactive_response(mobile, button_id): """ Handles the user's button response (Confirm or Cancel). """ doc_id = 'pending-user-action' temp_ref = db.collection("users").document(mobile).collection("temp_transactions").document(doc_id) transaction = temp_ref.get() if transaction.exists: transaction_data = transaction.to_dict() if button_id == "confirm": # Move to the appropriate collection transactions = transaction_data["transactions"] msg = process_intent(transactions, mobile) # Mark as confirmed or delete temporary record temp_ref.delete() messenger.send_message(f"{msg}", recipient_id = mobile) elif button_id == "cancel": # Delete the temporary record temp_ref.delete() messenger.send_message("Transaction has been canceled.", recipient_id = mobile) else: messenger.send_message("Invalid action. Please try again.", recipient_id = mobile) else: messenger.send_message("No pending transaction found.", recipient_id = mobile) @app.route("/", methods=["GET", "POST"]) def hook(): if request.method == "GET": if request.args.get("hub.verify_token") == VERIFY_TOKEN: logging.info("Verified webhook") response = make_response(request.args.get("hub.challenge"), 200) response.mimetype = "text/plain" return response logging.error("Webhook Verification failed") return "Invalid verification token" # get message update.. data = request.get_json() changed_field = messenger.changed_field(data) if changed_field == "messages": new_message = messenger.get_mobile(data) if new_message: mobile = messenger.get_mobile(data) message_type = messenger.get_message_type(data) if message_type == "text": message = messenger.get_message(data) # Handle greetings if message.lower() in ("hi", "hello", "hola", "help", "how are you", "sawubona"): response = "Hi there! My name is Qx-SmartLedger. How can I help you today?" messenger.send_message(message=f"{response}", recipient_id=mobile) else: process_user_msg(message, mobile) # messenger.send_message(message=f"User message processed!", recipient_id=mobile) elif message_type == "audio": audio = messenger.get_audio(data) audio_id, mime_type = audio["id"], audio["mime_type"] audio_url = messenger.query_media_url(audio_id) audio_filename = messenger.download_media(audio_url, mime_type) transcript = transcriber.transcribe(audio_filename) print(audio_filename) print(transcript.text) transcribed_message = transcript.text # logging.info(f"\nAudio: {audio}\n") process_user_msg(transcribed_message, mobile) # firestore_msg1, parsed_trans_data1, response1 = process_user_msg(transcribed_message, mobile) # messenger.send_message(message=f"Raw Response: {response1}, \n \n Parsed Response: {parsed_trans_data1}, \n \n Final Response: {firestore_msg1}", recipient_id=mobile) elif message_type == "interactive": message_response = messenger.get_interactive_response(data) interactive_type = message_response.get("type") message_id = message_response[interactive_type]["id"] message_text = message_response[interactive_type]["title"] logging.info(f"Interactive Message: {interactive_type} {message_id}: {message_text}") # messenger.send_message(message = f"Interactive type: {interactive_type}\n Message ID: {message_id} \n Message body: {message_body}", recipient_id=mobile) # Handle either create, update, or delete operation based on user action handle_interactive_response(mobile, message_id) else: messenger.send_message(message=f"Please send me text or audio messages", recipient_id=mobile) return "ok" if __name__ == '__main__': app.run(debug=True, host="0.0.0.0", port=7860)