Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5481e60f83 |
@@ -55,7 +55,6 @@ simple-websocket==1.1.0
|
|||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
sqlalchemy==2.0.23
|
sqlalchemy==2.0.23
|
||||||
starlette==0.27.0
|
starlette==0.27.0
|
||||||
twilio==8.10.0
|
|
||||||
typing-extensions==4.14.1
|
typing-extensions==4.14.1
|
||||||
uritemplate==4.2.0
|
uritemplate==4.2.0
|
||||||
urllib3==2.5.0
|
urllib3==2.5.0
|
||||||
|
|||||||
+10
-8
@@ -11,7 +11,7 @@ from database import (
|
|||||||
get_last_incoming_outgoing,
|
get_last_incoming_outgoing,
|
||||||
get_thread_messages,
|
get_thread_messages,
|
||||||
)
|
)
|
||||||
from whatsapp_sender import WhatsAppSender
|
from slack_sender import SlackSender
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -68,7 +68,7 @@ def _format_alert(
|
|||||||
|
|
||||||
|
|
||||||
def process_alerts(db: Session, cfg: dict) -> List[int]:
|
def process_alerts(db: Session, cfg: dict) -> List[int]:
|
||||||
"""Analyze threads and send WhatsApp alerts when thresholds are met."""
|
"""Analyze threads and send Slack alerts when thresholds are met."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
account_email = (cfg.get("email_address") or cfg.get("zoho_email") or "").lower()
|
account_email = (cfg.get("email_address") or cfg.get("zoho_email") or "").lower()
|
||||||
@@ -97,9 +97,11 @@ def process_alerts(db: Session, cfg: dict) -> List[int]:
|
|||||||
|
|
||||||
logging.info(f"Found {len(threads)} threads requiring reply")
|
logging.info(f"Found {len(threads)} threads requiring reply")
|
||||||
|
|
||||||
to_number = cfg.get("whatsapp_to") or None
|
webhook_url = cfg.get("slack_webhook_url") or None
|
||||||
logging.info(f"WhatsApp target: {to_number}")
|
logging.info(
|
||||||
sender = WhatsAppSender(to_number=to_number)
|
f"Slack webhook URL: {'✓ Configured' if webhook_url else '✗ Not configured'}"
|
||||||
|
)
|
||||||
|
sender = SlackSender(webhook_url=webhook_url)
|
||||||
alerted = []
|
alerted = []
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
@@ -179,11 +181,11 @@ def process_alerts(db: Session, cfg: dict) -> List[int]:
|
|||||||
logging.info(f" Thread {t.id}: Not actionable, skipping alert")
|
logging.info(f" Thread {t.id}: Not actionable, skipping alert")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Compose and send WhatsApp
|
# Compose and send Slack
|
||||||
msg = _format_alert(target_level, target_hours, t, ai, last_in)
|
msg = _format_alert(target_level, target_hours, t, ai, last_in)
|
||||||
logging.info(f" Thread {t.id}: Sending alert via WhatsApp")
|
logging.info(f" Thread {t.id}: Sending alert via Slack")
|
||||||
res = sender.send_alert(msg, thread_id=str(t.id))
|
res = sender.send_alert(msg, thread_id=str(t.id))
|
||||||
logging.info(f" Thread {t.id}: WhatsApp response: {res}")
|
logging.info(f" Thread {t.id}: Slack response: {res}")
|
||||||
|
|
||||||
if res.get("status") == "success":
|
if res.get("status") == "success":
|
||||||
t.last_alert_level_sent = target_level
|
t.last_alert_level_sent = target_level
|
||||||
|
|||||||
+5
-3
@@ -226,7 +226,7 @@ def load_config() -> dict:
|
|||||||
"agency_domains": [],
|
"agency_domains": [],
|
||||||
"zoho_email": "",
|
"zoho_email": "",
|
||||||
"zoho_app_password": "",
|
"zoho_app_password": "",
|
||||||
"whatsapp_to": "",
|
"slack_webhook_url": "",
|
||||||
"auto_process": False,
|
"auto_process": False,
|
||||||
"auto_process_interval": 30,
|
"auto_process_interval": 30,
|
||||||
# Sync status
|
# Sync status
|
||||||
@@ -309,8 +309,10 @@ async def config_save(request: Request):
|
|||||||
cfg["zoho_app_password"] = (
|
cfg["zoho_app_password"] = (
|
||||||
form.get("zoho_app_password") or cfg.get("zoho_app_password", "")
|
form.get("zoho_app_password") or cfg.get("zoho_app_password", "")
|
||||||
).strip()
|
).strip()
|
||||||
# WhatsApp destination
|
# Slack webhook URL
|
||||||
cfg["whatsapp_to"] = (form.get("whatsapp_to") or cfg.get("whatsapp_to", "")).strip()
|
cfg["slack_webhook_url"] = (
|
||||||
|
form.get("slack_webhook_url") or cfg.get("slack_webhook_url", "")
|
||||||
|
).strip()
|
||||||
|
|
||||||
# Time frames: collect indexed rows
|
# Time frames: collect indexed rows
|
||||||
frames: list[dict] = []
|
frames: list[dict] = []
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class SlackSender:
|
||||||
|
def __init__(self, webhook_url: str | None = None):
|
||||||
|
env_webhook = (os.getenv("SLACK_WEBHOOK_URL") or "").strip()
|
||||||
|
self.webhook_url = webhook_url or env_webhook
|
||||||
|
|
||||||
|
# Log credential status (without exposing full webhook URL)
|
||||||
|
webhook_preview = (
|
||||||
|
self.webhook_url[:40] + "..."
|
||||||
|
if len(self.webhook_url) > 40
|
||||||
|
else self.webhook_url
|
||||||
|
)
|
||||||
|
logging.info(
|
||||||
|
f"Slack config: Webhook={'✓' if self.webhook_url else '✗'} "
|
||||||
|
f"({webhook_preview if self.webhook_url else 'missing'})"
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.webhook_url:
|
||||||
|
self.use_mock = False
|
||||||
|
logging.info("Slack sender initialized successfully")
|
||||||
|
else:
|
||||||
|
self.use_mock = True
|
||||||
|
logging.warning(
|
||||||
|
"Using mock Slack sender. Missing: SLACK_WEBHOOK_URL or webhook_url parameter"
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_alert(self, alert_message: str, thread_id: str = None) -> Dict[str, Any]:
|
||||||
|
"""Send alert message to Slack"""
|
||||||
|
if self.use_mock:
|
||||||
|
return self._mock_send(alert_message, thread_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Format message for Slack
|
||||||
|
formatted_message = self._format_message(alert_message, thread_id)
|
||||||
|
|
||||||
|
# Send to Slack via webhook
|
||||||
|
response = requests.post(
|
||||||
|
self.webhook_url,
|
||||||
|
json={"text": formatted_message},
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Slack webhooks return plain text "ok" on success, not JSON
|
||||||
|
try:
|
||||||
|
response_data = response.json()
|
||||||
|
logging.info(f"Slack response: {response_data}")
|
||||||
|
except ValueError:
|
||||||
|
# Response is plain text (e.g., "ok")
|
||||||
|
response_text = response.text.strip()
|
||||||
|
logging.info(f"Slack response: {response_text}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message_sid": response.headers.get(
|
||||||
|
"X-Slack-Request-Timestamp", "unknown"
|
||||||
|
),
|
||||||
|
"thread_id": thread_id,
|
||||||
|
"sent_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
logging.error(f"Slack send error: {error_msg}")
|
||||||
|
|
||||||
|
# Provide specific guidance based on error
|
||||||
|
if "401" in error_msg or "403" in error_msg:
|
||||||
|
logging.error(
|
||||||
|
"Authentication failed! Check your Slack webhook URL:\n"
|
||||||
|
" 1. Verify SLACK_WEBHOOK_URL in environment or config\n"
|
||||||
|
" 2. Make sure the webhook URL is valid and not revoked\n"
|
||||||
|
" 3. Check that the webhook is enabled in your Slack app settings\n"
|
||||||
|
" 4. Create a new webhook at https://api.slack.com/apps if needed"
|
||||||
|
)
|
||||||
|
elif "404" in error_msg:
|
||||||
|
logging.error(
|
||||||
|
"Invalid webhook URL: The endpoint was not found.\n"
|
||||||
|
" Make sure your webhook URL is correct and hasn't been revoked."
|
||||||
|
)
|
||||||
|
elif "timeout" in error_msg.lower():
|
||||||
|
logging.error("Request timeout: Slack may be temporarily unavailable.")
|
||||||
|
|
||||||
|
return {"status": "error", "error": error_msg, "thread_id": thread_id}
|
||||||
|
|
||||||
|
def _mock_send(self, alert_message: str, thread_id: str = None) -> Dict[str, Any]:
|
||||||
|
"""Mock Slack sending for testing"""
|
||||||
|
print("💬 [MOCK] Slack Alert Sent:")
|
||||||
|
print(f" Webhook: {self.webhook_url or 'not_configured'}")
|
||||||
|
print(f" Thread ID: {thread_id}")
|
||||||
|
print(f" Message: {alert_message[:100]}...")
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message_sid": "mock_sid_123",
|
||||||
|
"thread_id": thread_id,
|
||||||
|
"sent_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _format_message(self, alert_message: str, thread_id: str = None) -> str:
|
||||||
|
"""Format alert message for Slack"""
|
||||||
|
# Slack messages can be longer, but we'll keep it reasonable
|
||||||
|
# Thread ID is already included in the alert_message format
|
||||||
|
formatted = alert_message
|
||||||
|
|
||||||
|
# Slack has a 4000 character limit for messages
|
||||||
|
max_length = 4000
|
||||||
|
if len(formatted) > max_length:
|
||||||
|
formatted = formatted[: max_length - 3] + "..."
|
||||||
|
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
def send_bulk_alerts(self, alerts: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
|
"""Send multiple alerts to Slack"""
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for alert in alerts:
|
||||||
|
message = alert.get("message", "")
|
||||||
|
thread_id = alert.get("thread_id", "unknown")
|
||||||
|
|
||||||
|
result = self.send_alert(message, thread_id)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
# Add small delay between messages to avoid rate limits
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Test Slack sender
|
||||||
|
sender = SlackSender()
|
||||||
|
|
||||||
|
test_message = """
|
||||||
|
🚨 LEVEL 1 ALERT (24 Hours)
|
||||||
|
|
||||||
|
🟢 Urgency: LOW
|
||||||
|
📧 Thread ID: test_thread_123
|
||||||
|
|
||||||
|
📝 Summary:
|
||||||
|
Client inquiry about project status. Requires follow-up.
|
||||||
|
|
||||||
|
🎯 Action Required:
|
||||||
|
Respond to client question
|
||||||
|
|
||||||
|
⏰ Confidence: 70.0%
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
result = sender.send_alert(test_message, "test_thread_123")
|
||||||
|
print(f"Send result: {result}")
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
from twilio.base.exceptions import TwilioException
|
|
||||||
from twilio.rest import Client
|
|
||||||
|
|
||||||
|
|
||||||
class WhatsAppSender:
|
|
||||||
def __init__(self, to_number: str | None = None):
|
|
||||||
self.account_sid = (os.getenv("TWILIO_ACCOUNT_SID") or "").strip()
|
|
||||||
self.auth_token = (os.getenv("TWILIO_AUTH_TOKEN") or "").strip()
|
|
||||||
self.from_number = (os.getenv("TWILIO_WHATSAPP_NUMBER") or "").strip()
|
|
||||||
env_to = (os.getenv("WHATSAPP_TO_NUMBER") or "").strip()
|
|
||||||
self.to_number = to_number or env_to # Individual phone number
|
|
||||||
|
|
||||||
# Log credential status (without exposing full credentials)
|
|
||||||
logging.info(
|
|
||||||
f"Twilio config: SID={'✓' if self.account_sid else '✗'} "
|
|
||||||
f"({self.account_sid[:8] + '...' if self.account_sid else 'missing'}), "
|
|
||||||
f"Token={'✓' if self.auth_token else '✗'}, "
|
|
||||||
f"From={'✓' if self.from_number else '✗'} ({self.from_number}), "
|
|
||||||
f"To={'✓' if self.to_number else '✗'} ({self.to_number})"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.account_sid and self.auth_token and self.from_number and self.to_number:
|
|
||||||
try:
|
|
||||||
self.client = Client(self.account_sid, self.auth_token)
|
|
||||||
self.use_mock = False
|
|
||||||
logging.info("Twilio client initialized successfully")
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Twilio client failed to initialize: {e}")
|
|
||||||
self.use_mock = True
|
|
||||||
else:
|
|
||||||
self.use_mock = True
|
|
||||||
missing = []
|
|
||||||
if not self.account_sid:
|
|
||||||
missing.append("TWILIO_ACCOUNT_SID")
|
|
||||||
if not self.auth_token:
|
|
||||||
missing.append("TWILIO_AUTH_TOKEN")
|
|
||||||
if not self.from_number:
|
|
||||||
missing.append("TWILIO_WHATSAPP_NUMBER")
|
|
||||||
if not self.to_number:
|
|
||||||
missing.append("WHATSAPP_TO_NUMBER or to_number parameter")
|
|
||||||
logging.warning(
|
|
||||||
f"Using mock WhatsApp sender. Missing: {', '.join(missing)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def send_alert(self, alert_message: str, thread_id: str = None) -> Dict[str, Any]:
|
|
||||||
"""Send alert message to WhatsApp"""
|
|
||||||
if self.use_mock:
|
|
||||||
return self._mock_send(alert_message, thread_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Format message for WhatsApp
|
|
||||||
formatted_message = self._format_message(alert_message)
|
|
||||||
|
|
||||||
# Send to WhatsApp
|
|
||||||
message = self.client.messages.create(
|
|
||||||
from_=f"whatsapp:{self.from_number}",
|
|
||||||
body=formatted_message,
|
|
||||||
to=f"whatsapp:{self.to_number}",
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "success",
|
|
||||||
"message_sid": message.sid,
|
|
||||||
"thread_id": thread_id,
|
|
||||||
"sent_at": message.date_created,
|
|
||||||
}
|
|
||||||
|
|
||||||
except TwilioException as e:
|
|
||||||
error_msg = str(e)
|
|
||||||
logging.error(f"WhatsApp send error: {error_msg}")
|
|
||||||
|
|
||||||
# Provide specific guidance based on error
|
|
||||||
if "20003" in error_msg or "Authenticate" in error_msg:
|
|
||||||
logging.error(
|
|
||||||
"Authentication failed! Check your Twilio credentials:\n"
|
|
||||||
" 1. Verify TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN in .env file\n"
|
|
||||||
" 2. Make sure there are no extra spaces or quotes\n"
|
|
||||||
" 3. Confirm you're using the correct credentials (Live vs Test)\n"
|
|
||||||
" 4. Check https://console.twilio.com for correct values"
|
|
||||||
)
|
|
||||||
elif "21211" in error_msg:
|
|
||||||
logging.error(f"Invalid 'To' phone number: {self.to_number}")
|
|
||||||
elif "21408" in error_msg:
|
|
||||||
logging.error(
|
|
||||||
f"WhatsApp not enabled for number: {self.from_number}\n"
|
|
||||||
" Enable WhatsApp at https://console.twilio.com/us1/develop/sms/senders/whatsapp-senders"
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"status": "error", "error": error_msg, "thread_id": thread_id}
|
|
||||||
|
|
||||||
def _mock_send(self, alert_message: str, thread_id: str = None) -> Dict[str, Any]:
|
|
||||||
"""Mock WhatsApp sending for testing"""
|
|
||||||
print("📱 [MOCK] WhatsApp Alert Sent:")
|
|
||||||
print(f" To: {self.to_number or 'your_number'}")
|
|
||||||
print(f" Thread ID: {thread_id}")
|
|
||||||
print(f" Message: {alert_message[:100]}...")
|
|
||||||
return {
|
|
||||||
"status": "success",
|
|
||||||
"message_sid": "mock_sid_123",
|
|
||||||
"thread_id": thread_id,
|
|
||||||
"sent_at": "2024-01-15T10:00:00Z",
|
|
||||||
}
|
|
||||||
|
|
||||||
def _format_message(self, alert_message: str) -> str:
|
|
||||||
"""Format alert message for WhatsApp"""
|
|
||||||
# WhatsApp has character limits, so we might need to truncate
|
|
||||||
max_length = 1000
|
|
||||||
if len(alert_message) > max_length:
|
|
||||||
alert_message = alert_message[: max_length - 3] + "..."
|
|
||||||
|
|
||||||
return alert_message
|
|
||||||
|
|
||||||
def send_bulk_alerts(self, alerts: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
||||||
"""Send multiple alerts to WhatsApp"""
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for alert in alerts:
|
|
||||||
message = alert.get("message", "")
|
|
||||||
thread_id = alert.get("thread_id", "unknown")
|
|
||||||
|
|
||||||
result = self.send_alert(message, thread_id)
|
|
||||||
results.append(result)
|
|
||||||
|
|
||||||
# Add small delay between messages to avoid rate limits
|
|
||||||
import time
|
|
||||||
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Test WhatsApp sender
|
|
||||||
sender = WhatsAppSender()
|
|
||||||
|
|
||||||
test_message = """
|
|
||||||
🚨 LEVEL 1 ALERT (24 Hours)
|
|
||||||
|
|
||||||
🟢 Urgency: LOW
|
|
||||||
📧 Thread ID: test_thread_123
|
|
||||||
|
|
||||||
📝 Summary:
|
|
||||||
Client inquiry about project status. Requires follow-up.
|
|
||||||
|
|
||||||
🎯 Action Required:
|
|
||||||
Respond to client question
|
|
||||||
|
|
||||||
⏰ Confidence: 70.0%
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
result = sender.send_alert(test_message, "test_thread_123")
|
|
||||||
print(f"Send result: {result}")
|
|
||||||
@@ -47,12 +47,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-2">WhatsApp</h3>
|
<h3 class="mt-2">Slack</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<label>Send To (WhatsApp E.164, no spaces)<br>
|
<label>Webhook URL<br>
|
||||||
<input type="text" name="whatsapp_to" placeholder="+15551234567" value="{{ cfg.whatsapp_to or '' }}" />
|
<input type="url" name="slack_webhook_url" placeholder="https://hooks.slack.com/services/..." value="{{ cfg.slack_webhook_url or '' }}" />
|
||||||
</label>
|
</label>
|
||||||
|
<p class="muted" style="font-size: 0.9em; margin-top: 0.25em;">
|
||||||
|
Create a webhook at <a href="https://api.slack.com/apps" target="_blank">api.slack.com/apps</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test Twilio credentials and WhatsApp configuration
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
from twilio.rest import Client
|
|
||||||
|
|
||||||
# Load environment variables
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
|
|
||||||
def test_credentials():
|
|
||||||
print("=" * 60)
|
|
||||||
print("TWILIO CREDENTIALS TEST")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# Get credentials
|
|
||||||
account_sid = (os.getenv("TWILIO_ACCOUNT_SID") or "").strip()
|
|
||||||
auth_token = (os.getenv("TWILIO_AUTH_TOKEN") or "").strip()
|
|
||||||
from_number = (os.getenv("TWILIO_WHATSAPP_NUMBER") or "").strip()
|
|
||||||
to_number = (os.getenv("WHATSAPP_TO_NUMBER") or "").strip()
|
|
||||||
|
|
||||||
# Check if credentials exist
|
|
||||||
print("\n1. Checking environment variables:")
|
|
||||||
print(f" TWILIO_ACCOUNT_SID: {'✓ Found' if account_sid else '✗ Missing'}")
|
|
||||||
if account_sid:
|
|
||||||
print(f" Value: {account_sid[:8]}...{account_sid[-4:]}")
|
|
||||||
|
|
||||||
print(f" TWILIO_AUTH_TOKEN: {'✓ Found' if auth_token else '✗ Missing'}")
|
|
||||||
if auth_token:
|
|
||||||
print(f" Value: {'*' * len(auth_token[:4])}...{auth_token[-4:]}")
|
|
||||||
|
|
||||||
print(f" TWILIO_WHATSAPP_NUMBER: {'✓ Found' if from_number else '✗ Missing'}")
|
|
||||||
if from_number:
|
|
||||||
print(f" Value: {from_number}")
|
|
||||||
|
|
||||||
print(f" WHATSAPP_TO_NUMBER: {'✓ Found' if to_number else '✗ Missing'}")
|
|
||||||
if to_number:
|
|
||||||
print(f" Value: {to_number}")
|
|
||||||
|
|
||||||
# Test authentication
|
|
||||||
if not (account_sid and auth_token):
|
|
||||||
print("\n❌ Missing required credentials!")
|
|
||||||
print("\nPlease check your .env file and ensure:")
|
|
||||||
print(" - TWILIO_ACCOUNT_SID is set")
|
|
||||||
print(" - TWILIO_AUTH_TOKEN is set")
|
|
||||||
print(" - No extra spaces or quotes around values")
|
|
||||||
return False
|
|
||||||
|
|
||||||
print("\n2. Testing Twilio authentication...")
|
|
||||||
try:
|
|
||||||
client = Client(account_sid, auth_token)
|
|
||||||
|
|
||||||
# Try to fetch account info to verify auth
|
|
||||||
account = client.api.accounts(account_sid).fetch()
|
|
||||||
print(" ✓ Authentication successful!")
|
|
||||||
print(f" Account: {account.friendly_name}")
|
|
||||||
print(f" Status: {account.status}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(" ✗ Authentication failed!")
|
|
||||||
print(f" Error: {e}")
|
|
||||||
print("\n Troubleshooting:")
|
|
||||||
print(" 1. Go to https://console.twilio.com")
|
|
||||||
print(" 2. Navigate to Account > API keys & tokens")
|
|
||||||
print(" 3. Copy the Account SID and Auth Token")
|
|
||||||
print(" 4. Update your .env file with correct values")
|
|
||||||
print(" 5. Make sure there are NO quotes or spaces")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test WhatsApp sender configuration
|
|
||||||
print("\n3. Testing WhatsApp sender configuration...")
|
|
||||||
if not from_number:
|
|
||||||
print(" ✗ TWILIO_WHATSAPP_NUMBER not set")
|
|
||||||
print(" Please set up WhatsApp sender at:")
|
|
||||||
print(" https://console.twilio.com/us1/develop/sms/senders/whatsapp-senders")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not from_number.startswith("+"):
|
|
||||||
print(f" ⚠ Warning: Phone number should start with '+': {from_number}")
|
|
||||||
|
|
||||||
print(f" ✓ WhatsApp sender configured: {from_number}")
|
|
||||||
|
|
||||||
# Test recipient
|
|
||||||
print("\n4. Testing recipient configuration...")
|
|
||||||
if not to_number:
|
|
||||||
print(" ✗ WHATSAPP_TO_NUMBER not set")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if not to_number.startswith("+"):
|
|
||||||
print(f" ⚠ Warning: Phone number should start with '+': {to_number}")
|
|
||||||
|
|
||||||
print(f" ✓ Recipient configured: {to_number}")
|
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
|
||||||
print("✓ ALL CHECKS PASSED - Ready to send WhatsApp messages!")
|
|
||||||
print("=" * 60)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
success = test_credentials()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
print("\nYou can now send test messages with:")
|
|
||||||
print(
|
|
||||||
" python -c 'from src.whatsapp_sender import WhatsAppSender; "
|
|
||||||
'w = WhatsAppSender(); print(w.send_alert("Test message"))\''
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
print("\n❌ Please fix the issues above before sending WhatsApp messages")
|
|
||||||
Reference in New Issue
Block a user