whatsapp_message_promotion / src /streamlit_app.py
Rishit's picture
Update src/streamlit_app.py
b81669e verified
# import streamlit as st
# import requests
# import csv
# import io
# import time
# import mimetypes
# from datetime import datetime
# st.set_page_config(page_title="Temple Dekho WhatsApp Promo Sender", layout="wide")
# st.title("πŸ“² Temple Dekho WhatsApp Promotional Message Sender")
# # --- Helper Function ---
# def upload_media(file, access_token, phone_number_id):
# mime_type = mimetypes.guess_type(file.name)[0] or "application/octet-stream"
# files = {
# 'file': (file.name, file.getvalue(), mime_type),
# 'type': (None, mime_type),
# 'messaging_product': (None, 'whatsapp') # βœ… Required by WhatsApp API
# }
# headers = {
# 'Authorization': f'Bearer {access_token}'
# }
# url = f"https://graph.facebook.com/v19.0/{phone_number_id}/media"
# try:
# res = requests.post(url, headers=headers, files=files)
# res.raise_for_status()
# return res.json().get("id"), res.status_code, res.text
# except Exception as e:
# return None, 500, str(e)
# # --- Input Fields ---
# with st.form("promo_form"):
# col1, col2 = st.columns(2)
# with col1:
# access_token = st.text_input("Access Token", type="password")
# phone_number_id = st.text_input("Phone Number ID")
# template_name = st.text_input("Template Name")
# language_code = st.text_input("Language Code", value="en")
# header_type = st.selectbox("Header Type", ["image", "video"])
# with col2:
# api_url = st.text_input("WhatsApp API URL")
# csv_file = st.file_uploader("CSV File with Phone Numbers", type=["csv"])
# media_file = st.file_uploader("(Optional) Image or Video File", type=["jpg", "jpeg", "png", "mp4"])
# existing_media_id = st.text_input("(Optional) Media ID")
# media_link = st.text_input("(Optional) Media Link")
# st.markdown("**Body Texts (Each line will be used as a template body parameter):**")
# body_texts = st.text_area("Body Text Parameters", height=100).splitlines()
# st.markdown("**Buttons Info (Match list lengths):**")
# button_sub_types = st.text_input("Button Sub Types (comma-separated)")
# button_indexes = st.text_input("Button Indexes (comma-separated)")
# button_param_types = st.text_input("Button Param Types (comma-separated)")
# button_param_values = st.text_input("Button Param Values (comma-separated)")
# submitted = st.form_submit_button("πŸš€ Send Promotional Messages")
# # --- Send Flow ---
# if submitted:
# if not csv_file:
# st.error("CSV file is required.")
# else:
# media_id = existing_media_id.strip() if existing_media_id.strip() else None
# if media_file and not media_id:
# st.info("Uploading media to WhatsApp...")
# media_id, status_code, response = upload_media(media_file, access_token, phone_number_id)
# if not media_id:
# st.error(f"❌ Media upload failed. Status: {status_code}\nResponse: {response}")
# else:
# st.success(f"βœ… Media uploaded successfully! Media ID: {media_id}")
# if not media_id:
# st.warning("⚠️ No media_id provided or uploaded. Template may fail if media is required.")
# csv_file.seek(0)
# decoded = csv_file.read().decode('utf-8').strip()
# rows = csv.reader(io.StringIO(decoded))
# next(rows, None) # Skip header
# phones = []
# try:
# for row in rows:
# if not row or not row[0].strip():
# continue # Skip empty lines
# num = row[0].strip()
# if num.isdigit():
# if len(num) == 10:
# phones.append("91" + num)
# elif len(num) == 12 and num.startswith("91"):
# phones.append(num)
# st.success(f"πŸ“± {len(phones)} valid phone numbers loaded.")
# except Exception as e:
# st.error(f"CSV parsing failed: {e}")
# # Build payload
# hdr = {
# "Authorization": f"Bearer {access_token}",
# "Content-Type": "application/json"
# }
# components = []
# if media_id:
# header_obj = {"id": media_id}
# if media_link:
# header_obj["link"] = media_link
# components.append({"type": "header", "parameters": [{header_type: header_obj, "type": header_type}]})
# components.append({"type": "body", "parameters": [{"type": "text", "text": t} for t in body_texts if t]})
# buttons = []
# try:
# for stype, idx, ptype, pval in zip(
# button_sub_types.split(","),
# button_indexes.split(","),
# button_param_types.split(","),
# button_param_values.split(",")
# ):
# buttons.append({
# "type": "button",
# "sub_type": stype.strip(),
# "index": int(idx.strip()),
# "parameters": [{"type": ptype.strip(), ptype.strip(): pval.strip()}]
# })
# except Exception as e:
# st.warning(f"⚠️ Button parameters may be misaligned: {e}")
# components += buttons
# st.info("Sending messages...")
# log_area = st.empty()
# full_log = ""
# for i, phone in enumerate(phones):
# payload = {
# "messaging_product": "whatsapp",
# "to": phone,
# "type": "template",
# "template": {
# "name": template_name,
# "language": {"code": language_code},
# "components": components
# }
# }
# try:
# r = requests.post(api_url, json=payload, headers=hdr)
# msg = f"{phone} β†’ Status: {r.status_code} β†’ {r.text}\n"
# full_log += msg
# log_area.text_area("πŸ“‹ Message Dispatch Log", value=full_log, height=400)
# except Exception as e:
# msg = f"{phone} β†’ Error: {str(e)}\n"
# full_log += msg
# log_area.text_area("πŸ“‹ Message Dispatch Log", value=full_log, height=400)
# time.sleep(0.05)
# st.success("βœ… All messages processed.")
import streamlit as st
import requests
import csv
import io
import time
import mimetypes
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed
import os
import threading
import schedule
st.set_page_config(page_title="Temple Dekho WhatsApp Promo Scheduler", layout="wide")
st.title("πŸ“… Temple Dekho WhatsApp Message Scheduler")
# Logs directory
log_dir = "/tmp/logs"
os.makedirs(log_dir, exist_ok=True)
# --- Helper Functions ---
def upload_media(file_path, access_token, phone_number_id):
mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
with open(file_path, "rb") as f:
files = {
'file': (os.path.basename(file_path), f, mime_type),
'type': (None, mime_type),
'messaging_product': (None, 'whatsapp')
}
headers = {
'Authorization': f'Bearer {access_token}'
}
url = f"https://graph.facebook.com/v19.0/{phone_number_id}/media"
try:
res = requests.post(url, headers=headers, files=files)
res.raise_for_status()
return res.json().get("id"), res.status_code, res.text
except Exception as e:
return None, 500, str(e)
def send_message(phone, payload, headers, retries=3, backoff_intervals=[5, 15, 30]):
for attempt in range(retries):
try:
response = requests.post(payload['url'], json=payload['data'], headers=headers)
if response.status_code == 200:
return f"{phone} β†’ Status: {response.status_code} β†’ {response.text}"
elif response.status_code == 400 and 'Service temporarily unavailable' in response.text:
if attempt < len(backoff_intervals):
time.sleep(backoff_intervals[attempt])
continue
return f"{phone} β†’ Status: {response.status_code} β†’ {response.text}"
except Exception as e:
if attempt < len(backoff_intervals):
time.sleep(backoff_intervals[attempt])
continue
return f"{phone} β†’ Error after {retries} retries: {str(e)}"
def execute_campaign(params):
log_filename = f"templedekho_whatsapp_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
log_filepath = os.path.join(log_dir, log_filename)
with open(log_filepath, 'w') as logfile:
with ThreadPoolExecutor(max_workers=10) as executor:
future_to_phone = {
executor.submit(
send_message,
phone,
{
"url": params['api_url'],
"data": {
"messaging_product": "whatsapp",
"to": phone,
"type": "template",
"template": {
"name": params['template_name'],
"language": {"code": params['language_code']},
"components": params['components']
}
}
},
params['headers']
): phone for phone in params['phones']
}
for future in as_completed(future_to_phone):
result = future.result()
logfile.write(result + "\n")
logfile.flush()
# --- UI Form ---
with st.form("schedule_form"):
st.subheader("πŸ—“οΈ Schedule WhatsApp Campaign")
col1, col2 = st.columns(2)
with col1:
access_token = st.text_input("Access Token", type="password")
phone_number_id = st.text_input("Phone Number ID")
template_name = st.text_input("Template Name")
language_code = st.text_input("Language Code", value="en")
header_type = st.selectbox("Header Type", ["image", "video"])
schedule_date = st.date_input("Schedule Date")
with col2:
api_url = st.text_input("WhatsApp API URL")
csv_file = st.file_uploader("CSV File with Phone Numbers", type=["csv"])
media_file = st.file_uploader("(Optional) Image or Video File", type=["jpg", "jpeg", "png", "mp4"])
existing_media_id = st.text_input("(Optional) Media ID")
media_link = st.text_input("(Optional) Media Link")
schedule_time = st.time_input("Schedule Time")
st.markdown("**Body Texts (Each line will be used as a template body parameter):**")
body_texts = st.text_area("Body Text Parameters", height=100).splitlines()
st.markdown("**Buttons Info (Match list lengths):**")
button_sub_types = st.text_input("Button Sub Types (comma-separated)")
button_indexes = st.text_input("Button Indexes (comma-separated)")
button_param_types = st.text_input("Button Param Types (comma-separated)")
button_param_values = st.text_input("Button Param Values (comma-separated)")
submitted = st.form_submit_button("⏰ Schedule Message Dispatch")
# --- Scheduler Setup ---
if submitted:
if not csv_file:
st.error("CSV file is required.")
else:
# Save CSV to /tmp
csv_path = f"/tmp/{csv_file.name}"
with open(csv_path, "wb") as f:
f.write(csv_file.getbuffer())
media_id = existing_media_id.strip() if existing_media_id.strip() else None
# Save media file to /tmp
if media_file and not media_id:
media_path = f"/tmp/{media_file.name}"
with open(media_path, "wb") as f:
f.write(media_file.getbuffer())
media_id, status_code, response = upload_media(media_path, access_token, phone_number_id)
if not media_id:
st.error(f"Media upload failed. Status: {status_code}\nResponse: {response}")
st.stop()
# Process CSV
phones = []
try:
with open(csv_path, "r", encoding="utf-8-sig") as f:
reader = csv.reader(f)
next(reader, None)
for row in reader:
if not row or not row[0].strip():
continue
num = row[0].strip()
if num.isdigit():
if len(num) == 10:
phones.append("91" + num)
elif len(num) == 12 and num.startswith("91"):
phones.append(num)
except Exception as e:
st.error(f"Error reading CSV: {e}")
st.stop()
if not phones:
st.error("No valid phone numbers found in CSV.")
st.stop()
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
components = []
if media_id:
header_obj = {"id": media_id}
if media_link:
header_obj["link"] = media_link
components.append({"type": "header", "parameters": [{header_type: header_obj, "type": header_type}]})
components.append({"type": "body", "parameters": [{"type": "text", "text": t} for t in body_texts if t]})
try:
buttons = []
for stype, idx, ptype, pval in zip(
button_sub_types.split(","),
button_indexes.split(","),
button_param_types.split(","),
button_param_values.split(",")
):
buttons.append({
"type": "button",
"sub_type": stype.strip(),
"index": int(idx.strip()),
"parameters": [{"type": ptype.strip(), ptype.strip(): pval.strip()}]
})
components += buttons
except:
pass # ignore if button input is malformed
scheduled_datetime = datetime.combine(schedule_date, schedule_time)
def schedule_job():
now = datetime.now()
if now >= scheduled_datetime:
schedule.clear()
execute_campaign({
"phones": phones,
"api_url": api_url,
"template_name": template_name,
"language_code": language_code,
"components": components,
"headers": headers
})
schedule.every(1).minutes.do(schedule_job)
def run_scheduler():
while True:
schedule.run_pending()
time.sleep(1)
threading.Thread(target=run_scheduler, daemon=True).start()
st.success(f"βœ… Campaign scheduled for {scheduled_datetime.strftime('%Y-%m-%d %H:%M:%S')}. UI can now be closed.")