Spaces:
Sleeping
Sleeping
| # 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.") | |