Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -888,66 +888,154 @@ def split_message_for_whatsapp(message: str, max_length: int = 1000) -> list:
|
|
888 |
return [message[i:i+max_length] for i in range(0, len(message), max_length)]
|
889 |
|
890 |
def send_whatsjet_message(phone_number: str, message: str, media_type: str = None, media_path: str = None, filename: str = None) -> bool:
|
891 |
-
"""Send a message using WhatsJet API with optional media attachment"""
|
892 |
if not all([WHATSJET_API_URL, WHATSJET_VENDOR_UID, WHATSJET_API_TOKEN]):
|
893 |
logger.error("[WhatsJet] Missing environment variables.")
|
894 |
return False
|
895 |
|
896 |
url = f"{WHATSJET_API_URL}/{WHATSJET_VENDOR_UID}/contact/send-message?token={WHATSJET_API_TOKEN}"
|
897 |
|
898 |
-
#
|
899 |
-
if media_type and media_path:
|
900 |
try:
|
901 |
-
|
902 |
-
|
903 |
-
|
|
|
|
|
|
|
|
|
|
|
904 |
payload = {
|
905 |
-
"phone_number": phone_number,
|
906 |
-
"
|
907 |
-
|
908 |
-
|
909 |
-
|
910 |
}
|
911 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
912 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
913 |
response = httpx.post(
|
914 |
url,
|
915 |
json=payload,
|
916 |
-
timeout=
|
917 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
918 |
response.raise_for_status()
|
919 |
-
logger.info(f"[WhatsJet] Media message sent successfully to {phone_number}")
|
920 |
return True
|
921 |
except Exception as e:
|
922 |
-
|
|
|
923 |
return False
|
924 |
except Exception as e:
|
925 |
-
|
|
|
926 |
return False
|
927 |
|
928 |
-
# Handle text messages
|
929 |
if not message.strip():
|
930 |
return True # Don't send empty messages
|
931 |
|
932 |
for chunk in split_message_for_whatsapp(message):
|
933 |
try:
|
934 |
payload = {"phone_number": phone_number, "message_body": chunk}
|
935 |
-
#
|
936 |
-
|
937 |
-
|
938 |
-
|
939 |
-
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
|
946 |
-
|
|
|
947 |
except Exception as e:
|
948 |
-
|
|
|
949 |
return False
|
950 |
-
|
951 |
logger.info(f"[WhatsJet] Successfully sent complete text message to {phone_number}")
|
952 |
return True
|
953 |
|
@@ -3366,35 +3454,38 @@ load_products_data()
|
|
3366 |
|
3367 |
def get_product_image_path(product_name: str) -> str:
|
3368 |
"""
|
3369 |
-
Get the
|
3370 |
-
Returns the
|
3371 |
"""
|
3372 |
try:
|
3373 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3374 |
images_dir = "static/images"
|
3375 |
os.makedirs(images_dir, exist_ok=True)
|
3376 |
-
|
3377 |
-
# Clean product name for filename
|
3378 |
safe_name = re.sub(r'[^\w\s-]', '', product_name).replace(' ', '_').lower()
|
3379 |
-
|
3380 |
-
# Check for common image extensions
|
3381 |
image_extensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif']
|
3382 |
-
|
3383 |
for ext in image_extensions:
|
3384 |
image_path = os.path.join(images_dir, f"{safe_name}{ext}")
|
3385 |
if os.path.exists(image_path):
|
3386 |
logger.info(f"[Image] Found product image: {image_path}")
|
3387 |
return image_path
|
3388 |
-
|
3389 |
-
# If no specific product image found, check for a default image
|
3390 |
default_image_path = os.path.join(images_dir, "default_product.jpg")
|
3391 |
if os.path.exists(default_image_path):
|
3392 |
logger.info(f"[Image] Using default product image: {default_image_path}")
|
3393 |
return default_image_path
|
3394 |
-
|
3395 |
logger.warning(f"[Image] No image found for product: {product_name}")
|
3396 |
return None
|
3397 |
-
|
3398 |
except Exception as e:
|
3399 |
logger.error(f"[Image] Error getting product image path: {e}")
|
3400 |
return None
|
@@ -3576,11 +3667,20 @@ async def send_product_image_with_caption(from_number: str, product: Dict[str, A
|
|
3576 |
product_name = product.get('Product Name', 'Unknown Product')
|
3577 |
details = generate_veterinary_product_response(product, user_context)
|
3578 |
image_url = product.get('Images', '').strip() if 'Images' in product else ''
|
3579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3580 |
try:
|
|
|
3581 |
if image_url:
|
3582 |
# Convert Google Drive link to direct download if needed
|
3583 |
if 'drive.google.com' in image_url:
|
|
|
3584 |
if '/d/' in image_url:
|
3585 |
file_id = image_url.split('/d/')[1].split('/')[0]
|
3586 |
elif 'id=' in image_url:
|
@@ -3589,58 +3689,97 @@ async def send_product_image_with_caption(from_number: str, product: Dict[str, A
|
|
3589 |
file_id = ''
|
3590 |
if file_id:
|
3591 |
image_url = f"https://drive.google.com/uc?export=download&id={file_id}"
|
3592 |
-
|
3593 |
-
|
3594 |
-
|
3595 |
-
|
3596 |
-
|
3597 |
-
|
3598 |
-
|
3599 |
-
|
3600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3601 |
success = send_whatsjet_message(
|
3602 |
from_number,
|
3603 |
details,
|
3604 |
media_type=media_type,
|
3605 |
-
media_path=
|
3606 |
filename=filename
|
3607 |
)
|
|
|
3608 |
if success:
|
3609 |
-
logger.info(f"[Product]
|
3610 |
return
|
3611 |
else:
|
3612 |
-
logger.warning(f"[Product] Failed to send image from CSV link,
|
3613 |
-
|
3614 |
-
|
3615 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3616 |
image_path = get_product_image_path(product_name)
|
3617 |
-
if image_path and os.path.exists(image_path):
|
3618 |
media_type = get_product_image_media_type(image_path)
|
3619 |
filename = f"{product_name.replace(' ', '_')}.jpg"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3620 |
success = send_whatsjet_message(
|
3621 |
from_number,
|
3622 |
details,
|
3623 |
media_type=media_type,
|
3624 |
-
media_path=
|
3625 |
filename=filename
|
3626 |
)
|
|
|
3627 |
if success:
|
3628 |
-
logger.info(f"[Product]
|
3629 |
else:
|
3630 |
logger.warning(f"[Product] Failed to send image, sending text only: {product_name}")
|
3631 |
send_whatsjet_message(from_number, details)
|
3632 |
else:
|
|
|
|
|
3633 |
send_whatsjet_message(from_number, details)
|
3634 |
-
|
3635 |
except Exception as e:
|
3636 |
logger.error(f"[Product] Error sending product image with caption: {e}")
|
|
|
3637 |
send_whatsjet_message(from_number, details)
|
3638 |
-
finally:
|
3639 |
-
if temp_file_path and os.path.exists(temp_file_path):
|
3640 |
-
try:
|
3641 |
-
os.remove(temp_file_path)
|
3642 |
-
except Exception:
|
3643 |
-
pass
|
3644 |
|
3645 |
@app.get("/test-product-image-with-caption")
|
3646 |
async def test_product_image_with_caption(phone: str):
|
@@ -3660,7 +3799,202 @@ async def test_product_image_with_caption(phone: str):
|
|
3660 |
except Exception as e:
|
3661 |
return {"error": str(e)}
|
3662 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3663 |
if __name__ == "__main__":
|
3664 |
# Launch FastAPI app
|
3665 |
import uvicorn
|
3666 |
-
uvicorn.run(app, host="0.0.0.0", port=7860)
|
|
|
888 |
return [message[i:i+max_length] for i in range(0, len(message), max_length)]
|
889 |
|
890 |
def send_whatsjet_message(phone_number: str, message: str, media_type: str = None, media_path: str = None, filename: str = None) -> bool:
|
891 |
+
"""Send a message using WhatsJet API with optional media attachment. For images, send the image as a media message (no caption), then send the caption as a separate text message."""
|
892 |
if not all([WHATSJET_API_URL, WHATSJET_VENDOR_UID, WHATSJET_API_TOKEN]):
|
893 |
logger.error("[WhatsJet] Missing environment variables.")
|
894 |
return False
|
895 |
|
896 |
url = f"{WHATSJET_API_URL}/{WHATSJET_VENDOR_UID}/contact/send-message?token={WHATSJET_API_TOKEN}"
|
897 |
|
898 |
+
# For images: use /contact/send-media-message endpoint with media_url, media_type, caption, and file_name
|
899 |
+
if media_type in ["image/jpeg", "image/png"] and media_path:
|
900 |
try:
|
901 |
+
# Use direct public URL for WhatsJet (media_url)
|
902 |
+
if media_path.startswith("http://") or media_path.startswith("https://"):
|
903 |
+
image_url = media_path
|
904 |
+
else:
|
905 |
+
# If local file, upload to a public location or return False (not supported here)
|
906 |
+
logger.error("[WhatsJet] Local file paths are not supported for media_url. Please provide a public URL.")
|
907 |
+
return False
|
908 |
+
media_api_url = f"{WHATSJET_API_URL}/{WHATSJET_VENDOR_UID}/contact/send-media-message"
|
909 |
payload = {
|
910 |
+
"phone_number": phone_number,
|
911 |
+
"media_type": "image",
|
912 |
+
"media_url": image_url,
|
913 |
+
"caption": message,
|
914 |
+
"file_name": filename or "image.jpg"
|
915 |
}
|
916 |
+
logger.info(f"[WhatsJet][DEBUG] Media API URL: {media_api_url}")
|
917 |
+
logger.info(f"[WhatsJet][DEBUG] Media payload: {payload}")
|
918 |
+
response = httpx.post(
|
919 |
+
media_api_url,
|
920 |
+
json=payload,
|
921 |
+
timeout=30
|
922 |
+
)
|
923 |
+
logger.info(f"[WhatsJet][DEBUG] Media response status: {response.status_code}")
|
924 |
+
logger.info(f"[WhatsJet][DEBUG] Media response headers: {dict(response.headers)}")
|
925 |
+
logger.info(f"[WhatsJet][DEBUG] Media response body: {response.text}")
|
926 |
+
response.raise_for_status()
|
927 |
+
logger.info(f"[WhatsJet] Media image sent successfully via media_url to {phone_number}")
|
928 |
+
return True
|
929 |
+
except Exception as e:
|
930 |
+
import traceback
|
931 |
+
logger.error(f"[WhatsJet][ERROR] Exception sending image via media_url: {str(e)}\nTraceback: {traceback.format_exc()}")
|
932 |
+
# Fallback: send text only
|
933 |
+
return send_whatsjet_message(phone_number, message)
|
934 |
+
|
935 |
+
# Handle other media messages (existing logic)
|
936 |
+
if media_type and media_path:
|
937 |
+
try:
|
938 |
+
if isinstance(media_path, str) and (media_path.startswith("http://") or media_path.startswith("https://")):
|
939 |
+
response = requests.get(media_path, stream=True, timeout=15)
|
940 |
+
response.raise_for_status()
|
941 |
+
media_content = response.content
|
942 |
+
logger.info(f"[WhatsJet][DEBUG] Downloaded media content-type: {response.headers.get('Content-Type')}")
|
943 |
+
logger.info(f"[WhatsJet][DEBUG] First 20 bytes: {media_content[:20]}")
|
944 |
+
else:
|
945 |
+
with open(media_path, 'rb') as f:
|
946 |
+
media_content = f.read()
|
947 |
+
# Try multipart first, then base64
|
948 |
try:
|
949 |
+
files = {
|
950 |
+
'media': (filename or 'file.bin', media_content, media_type)
|
951 |
+
}
|
952 |
+
data = {
|
953 |
+
'phone_number': phone_number,
|
954 |
+
'message_body': message
|
955 |
+
}
|
956 |
+
# Enhanced logging
|
957 |
+
logger.info(f"[WhatsJet][DEBUG] URL: {url}")
|
958 |
+
logger.info(f"[WhatsJet][DEBUG] Multipart data: {data}")
|
959 |
+
logger.info(f"[WhatsJet][DEBUG] Multipart files: {list(files.keys())}")
|
960 |
+
response = httpx.post(
|
961 |
+
url,
|
962 |
+
data=data,
|
963 |
+
files=files,
|
964 |
+
timeout=30
|
965 |
+
)
|
966 |
+
logger.info(f"[WhatsJet][DEBUG] Multipart response status: {response.status_code}")
|
967 |
+
logger.info(f"[WhatsJet][DEBUG] Multipart response headers: {dict(response.headers)}")
|
968 |
+
try:
|
969 |
+
response_text = response.text
|
970 |
+
logger.info(f"[WhatsJet][DEBUG] Multipart response body: {response_text[:1000]}" + ("..." if len(response_text) > 1000 else ""))
|
971 |
+
except Exception as e:
|
972 |
+
logger.info(f"[WhatsJet][DEBUG] Multipart response body: Unable to read: {e}")
|
973 |
+
response.raise_for_status()
|
974 |
+
logger.info(f"[WhatsJet] Media message sent successfully via multipart to {phone_number}")
|
975 |
+
return True
|
976 |
+
except Exception as e:
|
977 |
+
import traceback
|
978 |
+
logger.warning(f"[WhatsJet][ERROR] Multipart upload failed for media, trying base64: {e}\nTraceback: {traceback.format_exc()}")
|
979 |
+
|
980 |
+
media_b64 = base64.b64encode(media_content).decode('utf-8')
|
981 |
+
payload = {
|
982 |
+
"phone_number": phone_number,
|
983 |
+
"message_body": message,
|
984 |
+
'media_type': media_type,
|
985 |
+
'media_content': media_b64,
|
986 |
+
'media_filename': filename or os.path.basename(media_path) if not media_path.startswith('http') else filename or 'file.bin'
|
987 |
+
}
|
988 |
+
# Enhanced logging
|
989 |
+
logger.info(f"[WhatsJet][DEBUG] URL: {url}")
|
990 |
+
logger.info(f"[WhatsJet][DEBUG] Base64 payload: {{'phone_number': payload['phone_number'], 'media_type': payload['media_type'], 'media_filename': payload['media_filename'], 'message_body': payload['message_body'][:50] + '...', 'media_content_length': len(payload['media_content'])}}")
|
991 |
response = httpx.post(
|
992 |
url,
|
993 |
json=payload,
|
994 |
+
timeout=30
|
995 |
)
|
996 |
+
logger.info(f"[WhatsJet][DEBUG] Base64 response status: {response.status_code}")
|
997 |
+
logger.info(f"[WhatsJet][DEBUG] Base64 response headers: {dict(response.headers)}")
|
998 |
+
try:
|
999 |
+
response_text = response.text
|
1000 |
+
logger.info(f"[WhatsJet][DEBUG] Base64 response body: {response_text[:1000]}" + ("..." if len(response_text) > 1000 else ""))
|
1001 |
+
except Exception as e:
|
1002 |
+
logger.info(f"[WhatsJet][DEBUG] Base64 response body: Unable to read: {e}")
|
1003 |
response.raise_for_status()
|
1004 |
+
logger.info(f"[WhatsJet] Media message sent successfully via base64 to {phone_number}")
|
1005 |
return True
|
1006 |
except Exception as e:
|
1007 |
+
import traceback
|
1008 |
+
logger.error(f"[WhatsJet][ERROR] Exception sending media message: {e}\nTraceback: {traceback.format_exc()}")
|
1009 |
return False
|
1010 |
except Exception as e:
|
1011 |
+
import traceback
|
1012 |
+
logger.error(f"[WhatsJet][ERROR] Exception preparing media message: {str(e)}\nTraceback: {traceback.format_exc()}")
|
1013 |
return False
|
1014 |
|
1015 |
+
# Handle text messages (existing logic)
|
1016 |
if not message.strip():
|
1017 |
return True # Don't send empty messages
|
1018 |
|
1019 |
for chunk in split_message_for_whatsapp(message):
|
1020 |
try:
|
1021 |
payload = {"phone_number": phone_number, "message_body": chunk}
|
1022 |
+
# Enhanced logging
|
1023 |
+
logger.info(f"[WhatsJet][DEBUG] URL: {url}")
|
1024 |
+
logger.info(f"[WhatsJet][DEBUG] Payload: {json.dumps(payload)}")
|
1025 |
+
response = httpx.post(
|
1026 |
+
url,
|
1027 |
+
json=payload,
|
1028 |
+
timeout=15
|
1029 |
+
)
|
1030 |
+
logger.info(f"[WhatsJet][DEBUG] Response status: {response.status_code}")
|
1031 |
+
logger.info(f"[WhatsJet][DEBUG] Response headers: {dict(response.headers)}")
|
1032 |
+
logger.info(f"[WhatsJet][DEBUG] Response body: {response.text}")
|
1033 |
+
response.raise_for_status()
|
1034 |
+
logger.info(f"[WhatsJet] Text chunk sent successfully to {phone_number}")
|
1035 |
except Exception as e:
|
1036 |
+
import traceback
|
1037 |
+
logger.error(f"[WhatsJet][ERROR] Exception sending text chunk: {e}\nTraceback: {traceback.format_exc()}")
|
1038 |
return False
|
|
|
1039 |
logger.info(f"[WhatsJet] Successfully sent complete text message to {phone_number}")
|
1040 |
return True
|
1041 |
|
|
|
3454 |
|
3455 |
def get_product_image_path(product_name: str) -> str:
|
3456 |
"""
|
3457 |
+
Get the public URL for a product image if it exists in the uploads directory.
|
3458 |
+
Returns the public URL if found, otherwise falls back to static/images or None.
|
3459 |
"""
|
3460 |
try:
|
3461 |
+
# Check uploads directory for exact match (case and spaces preserved)
|
3462 |
+
uploads_dir = "uploads"
|
3463 |
+
image_extensions = ['.jpg', '.jpeg', '.png']
|
3464 |
+
for ext in image_extensions:
|
3465 |
+
filename = f"{product_name}{ext}"
|
3466 |
+
local_path = os.path.join(uploads_dir, filename)
|
3467 |
+
if os.path.exists(local_path):
|
3468 |
+
# Construct the public URL for Hugging Face Space
|
3469 |
+
# (Assumes the Space is named dreamstream-1-chatbot)
|
3470 |
+
public_url = f"https://dreamstream-1-chatbot.hf.space/uploads/{filename.replace(' ', '%20')}"
|
3471 |
+
logger.info(f"[Image] Found product image in uploads: {public_url}")
|
3472 |
+
return public_url
|
3473 |
+
# Fallback to static/images (old logic)
|
3474 |
images_dir = "static/images"
|
3475 |
os.makedirs(images_dir, exist_ok=True)
|
|
|
|
|
3476 |
safe_name = re.sub(r'[^\w\s-]', '', product_name).replace(' ', '_').lower()
|
|
|
|
|
3477 |
image_extensions = ['.jpg', '.jpeg', '.png', '.webp', '.gif']
|
|
|
3478 |
for ext in image_extensions:
|
3479 |
image_path = os.path.join(images_dir, f"{safe_name}{ext}")
|
3480 |
if os.path.exists(image_path):
|
3481 |
logger.info(f"[Image] Found product image: {image_path}")
|
3482 |
return image_path
|
|
|
|
|
3483 |
default_image_path = os.path.join(images_dir, "default_product.jpg")
|
3484 |
if os.path.exists(default_image_path):
|
3485 |
logger.info(f"[Image] Using default product image: {default_image_path}")
|
3486 |
return default_image_path
|
|
|
3487 |
logger.warning(f"[Image] No image found for product: {product_name}")
|
3488 |
return None
|
|
|
3489 |
except Exception as e:
|
3490 |
logger.error(f"[Image] Error getting product image path: {e}")
|
3491 |
return None
|
|
|
3667 |
product_name = product.get('Product Name', 'Unknown Product')
|
3668 |
details = generate_veterinary_product_response(product, user_context)
|
3669 |
image_url = product.get('Images', '').strip() if 'Images' in product else ''
|
3670 |
+
|
3671 |
+
# Force image URL for Respira Aid Plus (use cPanel public URL)
|
3672 |
+
if product_name.lower().strip() == "respira aid plus":
|
3673 |
+
image_url = "https://amgocus.com/uploads/images/Respira%20Aid%20Plus.jpg"
|
3674 |
+
|
3675 |
+
logger.info(f"[Product] Processing image for product: {product_name}")
|
3676 |
+
logger.info(f"[Product] Image URL from CSV: {image_url}")
|
3677 |
+
|
3678 |
try:
|
3679 |
+
# First, check if we have an image URL from CSV
|
3680 |
if image_url:
|
3681 |
# Convert Google Drive link to direct download if needed
|
3682 |
if 'drive.google.com' in image_url:
|
3683 |
+
logger.info(f"[Product] Converting Google Drive link: {image_url}")
|
3684 |
if '/d/' in image_url:
|
3685 |
file_id = image_url.split('/d/')[1].split('/')[0]
|
3686 |
elif 'id=' in image_url:
|
|
|
3689 |
file_id = ''
|
3690 |
if file_id:
|
3691 |
image_url = f"https://drive.google.com/uc?export=download&id={file_id}"
|
3692 |
+
logger.info(f"[Product] Converted to direct download URL: {image_url}")
|
3693 |
+
|
3694 |
+
# Use the public URL directly for WhatsApp API
|
3695 |
+
media_type = 'image/jpeg'
|
3696 |
+
filename = f"{product_name.replace(' ', '_')}.jpg"
|
3697 |
+
|
3698 |
+
# Test the image URL first
|
3699 |
+
try:
|
3700 |
+
logger.info(f"[Product] Testing image URL accessibility: {image_url}")
|
3701 |
+
test_response = requests.head(image_url, timeout=10)
|
3702 |
+
if test_response.status_code != 200:
|
3703 |
+
logger.warning(f"[Product] Image URL not accessible (status {test_response.status_code}): {image_url}")
|
3704 |
+
raise Exception(f"Image URL not accessible: {test_response.status_code}")
|
3705 |
+
logger.info(f"[Product] Image URL is accessible")
|
3706 |
+
except Exception as e:
|
3707 |
+
logger.warning(f"[Product] Failed to test image URL {image_url}: {e}")
|
3708 |
+
image_url = None
|
3709 |
+
|
3710 |
+
# Send using public URL (not local file)
|
3711 |
+
if image_url:
|
3712 |
+
logger.info(f"[Product] Attempting to send image from CSV URL for: {product_name}")
|
3713 |
success = send_whatsjet_message(
|
3714 |
from_number,
|
3715 |
details,
|
3716 |
media_type=media_type,
|
3717 |
+
media_path=image_url, # Use public URL directly
|
3718 |
filename=filename
|
3719 |
)
|
3720 |
+
|
3721 |
if success:
|
3722 |
+
logger.info(f"[Product] Successfully sent image from CSV link with caption for product: {product_name}")
|
3723 |
return
|
3724 |
else:
|
3725 |
+
logger.warning(f"[Product] Failed to send image from CSV link, trying fallback: {product_name}")
|
3726 |
+
|
3727 |
+
# Fallback 1: Try with a known public test image
|
3728 |
+
logger.info(f"[Product] Trying public test image for: {product_name}")
|
3729 |
+
test_image_url = "https://www.w3schools.com/w3images/lights.jpg"
|
3730 |
+
media_type = 'image/jpeg'
|
3731 |
+
filename = f"{product_name.replace(' ', '_')}.jpg"
|
3732 |
+
|
3733 |
+
success = send_whatsjet_message(
|
3734 |
+
from_number,
|
3735 |
+
details,
|
3736 |
+
media_type=media_type,
|
3737 |
+
media_path=test_image_url,
|
3738 |
+
filename=filename
|
3739 |
+
)
|
3740 |
+
|
3741 |
+
if success:
|
3742 |
+
logger.info(f"[Product] Successfully sent test image with caption for product: {product_name}")
|
3743 |
+
return
|
3744 |
+
|
3745 |
+
# Fallback 2: Try local uploads directory (public URL)
|
3746 |
+
logger.info(f"[Product] Trying local uploads directory for: {product_name}")
|
3747 |
image_path = get_product_image_path(product_name)
|
3748 |
+
if image_path and (image_path.startswith('http') or os.path.exists(image_path)):
|
3749 |
media_type = get_product_image_media_type(image_path)
|
3750 |
filename = f"{product_name.replace(' ', '_')}.jpg"
|
3751 |
+
|
3752 |
+
# If it's already a public URL, use it directly
|
3753 |
+
if image_path.startswith('http'):
|
3754 |
+
media_path = image_path
|
3755 |
+
logger.info(f"[Product] Using existing public URL: {media_path}")
|
3756 |
+
else:
|
3757 |
+
# Convert local path to public URL
|
3758 |
+
media_path = f"https://dreamstream-1-chatbot.hf.space/uploads/{os.path.basename(image_path).replace(' ', '%20')}"
|
3759 |
+
logger.info(f"[Product] Converted local path to public URL: {media_path}")
|
3760 |
+
|
3761 |
success = send_whatsjet_message(
|
3762 |
from_number,
|
3763 |
details,
|
3764 |
media_type=media_type,
|
3765 |
+
media_path=media_path, # Use public URL
|
3766 |
filename=filename
|
3767 |
)
|
3768 |
+
|
3769 |
if success:
|
3770 |
+
logger.info(f"[Product] Successfully sent image with caption for product: {product_name}")
|
3771 |
else:
|
3772 |
logger.warning(f"[Product] Failed to send image, sending text only: {product_name}")
|
3773 |
send_whatsjet_message(from_number, details)
|
3774 |
else:
|
3775 |
+
# No image available, send text only
|
3776 |
+
logger.info(f"[Product] No image available, sending text only for: {product_name}")
|
3777 |
send_whatsjet_message(from_number, details)
|
3778 |
+
|
3779 |
except Exception as e:
|
3780 |
logger.error(f"[Product] Error sending product image with caption: {e}")
|
3781 |
+
logger.info(f"[Product] Falling back to text-only message for: {product_name}")
|
3782 |
send_whatsjet_message(from_number, details)
|
|
|
|
|
|
|
|
|
|
|
|
|
3783 |
|
3784 |
@app.get("/test-product-image-with-caption")
|
3785 |
async def test_product_image_with_caption(phone: str):
|
|
|
3799 |
except Exception as e:
|
3800 |
return {"error": str(e)}
|
3801 |
|
3802 |
+
@app.get("/test-image-sending")
|
3803 |
+
async def test_image_sending(phone: str, image_url: str = "https://www.w3schools.com/w3images/lights.jpg"):
|
3804 |
+
"""
|
3805 |
+
Test endpoint to send a test image with caption to debug image sending functionality.
|
3806 |
+
"""
|
3807 |
+
try:
|
3808 |
+
test_message = f"""🧪 *Test Image Message*
|
3809 |
+
|
3810 |
+
This is a test message to verify image sending functionality.
|
3811 |
+
|
3812 |
+
📸 *Image Details:*
|
3813 |
+
• URL: {image_url}
|
3814 |
+
• Type: JPEG
|
3815 |
+
• Purpose: Testing WhatsJet API
|
3816 |
+
|
3817 |
+
💬 *Test Options:*
|
3818 |
+
1️⃣ Send another test image
|
3819 |
+
2️⃣ Test with different URL
|
3820 |
+
3️⃣ Back to main menu
|
3821 |
+
|
3822 |
+
Please confirm if you received both the image and this text message."""
|
3823 |
+
|
3824 |
+
success = send_whatsjet_message(
|
3825 |
+
phone,
|
3826 |
+
test_message,
|
3827 |
+
media_type="image/jpeg",
|
3828 |
+
media_path=image_url,
|
3829 |
+
filename="test_image.jpg"
|
3830 |
+
)
|
3831 |
+
|
3832 |
+
if success:
|
3833 |
+
return {
|
3834 |
+
"status": "success",
|
3835 |
+
"phone": phone,
|
3836 |
+
"image_url": image_url,
|
3837 |
+
"message": "Test image and caption sent successfully"
|
3838 |
+
}
|
3839 |
+
else:
|
3840 |
+
return {
|
3841 |
+
"status": "failed",
|
3842 |
+
"phone": phone,
|
3843 |
+
"image_url": image_url,
|
3844 |
+
"message": "Failed to send test image"
|
3845 |
+
}
|
3846 |
+
except Exception as e:
|
3847 |
+
return {"error": str(e), "phone": phone, "image_url": image_url}
|
3848 |
+
|
3849 |
+
@app.get("/debug-whatsjet")
|
3850 |
+
async def debug_whatsjet():
|
3851 |
+
"""
|
3852 |
+
Debug endpoint to check WhatsJet configuration and test basic functionality.
|
3853 |
+
"""
|
3854 |
+
try:
|
3855 |
+
# Check environment variables
|
3856 |
+
config_status = {
|
3857 |
+
"WHATSJET_API_URL": bool(WHATSJET_API_URL),
|
3858 |
+
"WHATSJET_VENDOR_UID": bool(WHATSJET_VENDOR_UID),
|
3859 |
+
"WHATSJET_API_TOKEN": bool(WHATSJET_API_TOKEN),
|
3860 |
+
"all_configured": all([WHATSJET_API_URL, WHATSJET_VENDOR_UID, WHATSJET_API_TOKEN])
|
3861 |
+
}
|
3862 |
+
|
3863 |
+
# Test basic text message if configured
|
3864 |
+
test_result = None
|
3865 |
+
if config_status["all_configured"]:
|
3866 |
+
try:
|
3867 |
+
# Test with a simple text message
|
3868 |
+
test_phone = "1234567890" # Dummy phone for testing
|
3869 |
+
test_success = send_whatsjet_message(
|
3870 |
+
test_phone,
|
3871 |
+
"🧪 WhatsJet API Test Message\n\nThis is a test to verify API connectivity.",
|
3872 |
+
)
|
3873 |
+
test_result = {
|
3874 |
+
"success": test_success,
|
3875 |
+
"message": "API test completed (dummy phone number used)"
|
3876 |
+
}
|
3877 |
+
except Exception as e:
|
3878 |
+
test_result = {
|
3879 |
+
"success": False,
|
3880 |
+
"error": str(e)
|
3881 |
+
}
|
3882 |
+
|
3883 |
+
return {
|
3884 |
+
"timestamp": datetime.now().isoformat(),
|
3885 |
+
"config_status": config_status,
|
3886 |
+
"test_result": test_result,
|
3887 |
+
"api_url": WHATSJET_API_URL if config_status["all_configured"] else "Not configured",
|
3888 |
+
"vendor_uid": WHATSJET_VENDOR_UID if config_status["all_configured"] else "Not configured"
|
3889 |
+
}
|
3890 |
+
except Exception as e:
|
3891 |
+
return {"error": str(e), "timestamp": datetime.now().isoformat()}
|
3892 |
+
|
3893 |
+
@app.get("/test-whatsjet-payloads")
|
3894 |
+
async def test_whatsjet_payloads(phone: str):
|
3895 |
+
"""
|
3896 |
+
Test different WhatsJet API payload formats to identify the correct one for image sending.
|
3897 |
+
"""
|
3898 |
+
if not all([WHATSJET_API_URL, WHATSJET_VENDOR_UID, WHATSJET_API_TOKEN]):
|
3899 |
+
return {"error": "WhatsJet not configured"}
|
3900 |
+
|
3901 |
+
url = f"{WHATSJET_API_URL}/{WHATSJET_VENDOR_UID}/contact/send-message?token={WHATSJET_API_TOKEN}"
|
3902 |
+
test_image_url = "https://www.w3schools.com/w3images/lights.jpg"
|
3903 |
+
|
3904 |
+
# Download test image
|
3905 |
+
try:
|
3906 |
+
response = requests.get(test_image_url, timeout=10)
|
3907 |
+
response.raise_for_status()
|
3908 |
+
image_content = response.content
|
3909 |
+
image_b64 = base64.b64encode(image_content).decode('utf-8')
|
3910 |
+
except Exception as e:
|
3911 |
+
return {"error": f"Failed to download test image: {e}"}
|
3912 |
+
|
3913 |
+
results = {}
|
3914 |
+
|
3915 |
+
# Test different payload formats
|
3916 |
+
test_payloads = [
|
3917 |
+
{
|
3918 |
+
"name": "Format 1: Standard with media_content",
|
3919 |
+
"payload": {
|
3920 |
+
"phone_number": phone,
|
3921 |
+
"media_type": "image/jpeg",
|
3922 |
+
"media_content": image_b64,
|
3923 |
+
"media_filename": "test.jpg",
|
3924 |
+
"message_body": ""
|
3925 |
+
}
|
3926 |
+
},
|
3927 |
+
{
|
3928 |
+
"name": "Format 2: With media_url instead of media_content",
|
3929 |
+
"payload": {
|
3930 |
+
"phone_number": phone,
|
3931 |
+
"media_type": "image/jpeg",
|
3932 |
+
"media_url": test_image_url,
|
3933 |
+
"media_filename": "test.jpg",
|
3934 |
+
"message_body": ""
|
3935 |
+
}
|
3936 |
+
},
|
3937 |
+
{
|
3938 |
+
"name": "Format 3: With file field",
|
3939 |
+
"payload": {
|
3940 |
+
"phone_number": phone,
|
3941 |
+
"file": image_b64,
|
3942 |
+
"file_type": "image/jpeg",
|
3943 |
+
"filename": "test.jpg",
|
3944 |
+
"message_body": ""
|
3945 |
+
}
|
3946 |
+
},
|
3947 |
+
{
|
3948 |
+
"name": "Format 4: With attachment field",
|
3949 |
+
"payload": {
|
3950 |
+
"phone_number": phone,
|
3951 |
+
"attachment": image_b64,
|
3952 |
+
"attachment_type": "image/jpeg",
|
3953 |
+
"attachment_name": "test.jpg",
|
3954 |
+
"message_body": ""
|
3955 |
+
}
|
3956 |
+
},
|
3957 |
+
{
|
3958 |
+
"name": "Format 5: With image field",
|
3959 |
+
"payload": {
|
3960 |
+
"phone_number": phone,
|
3961 |
+
"image": image_b64,
|
3962 |
+
"image_type": "image/jpeg",
|
3963 |
+
"image_name": "test.jpg",
|
3964 |
+
"message_body": ""
|
3965 |
+
}
|
3966 |
+
}
|
3967 |
+
]
|
3968 |
+
|
3969 |
+
for test in test_payloads:
|
3970 |
+
try:
|
3971 |
+
logger.info(f"[WhatsJet] Testing payload format: {test['name']}")
|
3972 |
+
response = httpx.post(url, json=test['payload'], timeout=30)
|
3973 |
+
|
3974 |
+
results[test['name']] = {
|
3975 |
+
"status_code": response.status_code,
|
3976 |
+
"success": response.status_code == 200,
|
3977 |
+
"response_body": response.text[:200] if response.text else "No response body"
|
3978 |
+
}
|
3979 |
+
|
3980 |
+
logger.info(f"[WhatsJet] {test['name']} - Status: {response.status_code}")
|
3981 |
+
|
3982 |
+
except Exception as e:
|
3983 |
+
results[test['name']] = {
|
3984 |
+
"status_code": "Error",
|
3985 |
+
"success": False,
|
3986 |
+
"error": str(e)
|
3987 |
+
}
|
3988 |
+
logger.error(f"[WhatsJet] {test['name']} - Error: {e}")
|
3989 |
+
|
3990 |
+
return {
|
3991 |
+
"timestamp": datetime.now().isoformat(),
|
3992 |
+
"phone": phone,
|
3993 |
+
"test_image_url": test_image_url,
|
3994 |
+
"results": results
|
3995 |
+
}
|
3996 |
+
|
3997 |
if __name__ == "__main__":
|
3998 |
# Launch FastAPI app
|
3999 |
import uvicorn
|
4000 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|