Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import pandas as pd | |
| import requests | |
| import mimetypes | |
| import csv | |
| import os | |
| import io | |
| import time | |
| import schedule | |
| import threading | |
| from datetime import datetime | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| # Setup | |
| log_dir = "/tmp/logs" | |
| os.makedirs(log_dir, exist_ok=True) | |
| 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") | |
| except Exception as e: | |
| return None | |
| 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}" | |
| 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_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() | |
| def schedule_campaign(access_token, phone_number_id, template_name, language_code, header_type, api_url, | |
| csv_file, media_file, existing_media_id, media_link, body_texts, button_sub_types, | |
| button_indexes, button_param_types, button_param_values, schedule_date, schedule_time): | |
| if not csv_file: | |
| return "β Please upload a CSV file." | |
| # Save and parse CSV | |
| csv_path = f"/tmp/{csv_file.name}" | |
| with open(csv_path, "wb") as f: | |
| f.write(csv_file.read()) | |
| phones = [] | |
| 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) | |
| if not phones: | |
| return "β No valid phone numbers found." | |
| # Upload media if needed | |
| media_id = existing_media_id.strip() | |
| 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.read()) | |
| media_id = upload_media(media_path, access_token, phone_number_id) | |
| if not media_id: | |
| return "β Media upload failed." | |
| 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}]}) | |
| if body_texts: | |
| components.append({ | |
| "type": "body", | |
| "parameters": [{"type": "text", "text": t.strip()} for t in body_texts.splitlines() if t.strip()] | |
| }) | |
| 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 # Silently fail for malformed input | |
| scheduled_datetime = datetime.combine(schedule_date, schedule_time) | |
| def job(): | |
| if datetime.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(job) | |
| def run_schedule_loop(): | |
| while True: | |
| schedule.run_pending() | |
| time.sleep(1) | |
| threading.Thread(target=run_schedule_loop, daemon=True).start() | |
| return f"β Campaign scheduled for {scheduled_datetime.strftime('%Y-%m-%d %H:%M:%S')}." | |
| # Gradio UI | |
| demo = gr.Interface( | |
| fn=schedule_campaign, | |
| inputs=[ | |
| gr.Textbox(label="Access Token", type="password"), | |
| gr.Textbox(label="Phone Number ID"), | |
| gr.Textbox(label="Template Name"), | |
| gr.Textbox(label="Language Code", value="en"), | |
| gr.Dropdown(["image", "video"], label="Header Type"), | |
| gr.Textbox(label="WhatsApp API URL"), | |
| gr.File(label="CSV File (Phone Numbers)", file_types=[".csv"]), | |
| gr.File(label="Media File (Optional)", file_types=[".jpg", ".jpeg", ".png", ".mp4"]), | |
| gr.Textbox(label="Existing Media ID (Optional)", value=""), | |
| gr.Textbox(label="Media Link (Optional)", value=""), | |
| gr.Textbox(label="Body Texts (Each line = one parameter)", lines=4), | |
| gr.Textbox(label="Button Sub Types (comma-separated)", value=""), | |
| gr.Textbox(label="Button Indexes (comma-separated)", value=""), | |
| gr.Textbox(label="Button Param Types (comma-separated)", value=""), | |
| gr.Textbox(label="Button Param Values (comma-separated)", value=""), | |
| gr.components.Date(label="Schedule Date"), | |
| gr.components.Time(label="Schedule Time"), | |
| ], | |
| outputs=gr.Textbox(label="Status"), | |
| title="Temple Dekho WhatsApp Message Scheduler", | |
| description="Schedule WhatsApp template messages with optional media, buttons, and CSV contact upload." | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |