Rishit commited on
Commit
eefe88d
Β·
verified Β·
1 Parent(s): 5bfde29

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -0
app.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import csv
4
+ import time
5
+ import mimetypes
6
+ import os
7
+ import tempfile
8
+ from datetime import datetime
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
10
+ import threading
11
+ import schedule
12
+
13
+ log_dir = tempfile.mkdtemp()
14
+
15
+ def upload_media(file_path, access_token, phone_number_id):
16
+ mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
17
+ with open(file_path, "rb") as f:
18
+ files = {
19
+ 'file': (os.path.basename(file_path), f, mime_type),
20
+ 'type': (None, mime_type),
21
+ 'messaging_product': (None, 'whatsapp')
22
+ }
23
+ headers = {
24
+ 'Authorization': f'Bearer {access_token}'
25
+ }
26
+ url = f"https://graph.facebook.com/v19.0/{phone_number_id}/media"
27
+ try:
28
+ res = requests.post(url, headers=headers, files=files)
29
+ res.raise_for_status()
30
+ return res.json().get("id")
31
+ except Exception:
32
+ return None
33
+
34
+ def send_message(phone, payload, headers, retries=3, backoff_intervals=[5, 15, 30]):
35
+ for attempt in range(retries):
36
+ try:
37
+ response = requests.post(payload['url'], json=payload['data'], headers=headers)
38
+ if response.status_code == 200:
39
+ return f"{phone} β†’ βœ… {response.text}"
40
+ elif response.status_code == 400 and 'Service temporarily unavailable' in response.text:
41
+ if attempt < len(backoff_intervals):
42
+ time.sleep(backoff_intervals[attempt])
43
+ continue
44
+ return f"{phone} β†’ ❌ {response.text}"
45
+ except Exception as e:
46
+ if attempt < len(backoff_intervals):
47
+ time.sleep(backoff_intervals[attempt])
48
+ continue
49
+ return f"{phone} β†’ ❌ Error: {str(e)}"
50
+
51
+ def execute_campaign(params):
52
+ log_file = os.path.join(log_dir, f"log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt")
53
+ with open(log_file, 'w') as logfile:
54
+ with ThreadPoolExecutor(max_workers=10) as executor:
55
+ futures = {
56
+ executor.submit(send_message, phone, {
57
+ "url": params['api_url'],
58
+ "data": {
59
+ "messaging_product": "whatsapp",
60
+ "to": phone,
61
+ "type": "template",
62
+ "template": {
63
+ "name": params['template_name'],
64
+ "language": {"code": params['language_code']},
65
+ "components": params['components']
66
+ }
67
+ }
68
+ }, params['headers']): phone for phone in params['phones']
69
+ }
70
+
71
+ for future in as_completed(futures):
72
+ result = future.result()
73
+ logfile.write(result + "\n")
74
+ logfile.flush()
75
+
76
+ def schedule_campaign(
77
+ access_token, phone_number_id, template_name, language_code, header_type,
78
+ api_url, csv_file, media_file, existing_media_id, media_link,
79
+ schedule_date, schedule_time, body_texts,
80
+ button_sub_types, button_indexes, button_param_types, button_param_values
81
+ ):
82
+
83
+ if csv_file is None:
84
+ return "❌ CSV file is required."
85
+
86
+ phones = []
87
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as tmp_csv:
88
+ tmp_csv.write(csv_file.read())
89
+ tmp_csv_path = tmp_csv.name
90
+
91
+ with open(tmp_csv_path, "r", encoding="utf-8-sig") as f:
92
+ reader = csv.reader(f)
93
+ next(reader, None)
94
+ for row in reader:
95
+ if not row or not row[0].strip():
96
+ continue
97
+ num = row[0].strip()
98
+ if num.isdigit():
99
+ if len(num) == 10:
100
+ phones.append("91" + num)
101
+ elif len(num) == 12 and num.startswith("91"):
102
+ phones.append(num)
103
+
104
+ if not phones:
105
+ return "❌ No valid phone numbers found."
106
+
107
+ media_id = existing_media_id.strip() if existing_media_id else None
108
+ if media_file and not media_id:
109
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(media_file.name)[1]) as tmp_media:
110
+ tmp_media.write(media_file.read())
111
+ media_path = tmp_media.name
112
+ media_id = upload_media(media_path, access_token, phone_number_id)
113
+ if not media_id:
114
+ return "❌ Media upload failed."
115
+
116
+ headers = {
117
+ "Authorization": f"Bearer {access_token}",
118
+ "Content-Type": "application/json"
119
+ }
120
+
121
+ components = []
122
+ if media_id:
123
+ header_obj = {"id": media_id}
124
+ if media_link:
125
+ header_obj["link"] = media_link
126
+ components.append({"type": "header", "parameters": [{header_type: header_obj, "type": header_type}]})
127
+
128
+ components.append({"type": "body", "parameters": [{"type": "text", "text": t} for t in body_texts.splitlines() if t]})
129
+
130
+ try:
131
+ buttons = []
132
+ for stype, idx, ptype, pval in zip(
133
+ button_sub_types.split(","),
134
+ button_indexes.split(","),
135
+ button_param_types.split(","),
136
+ button_param_values.split(",")
137
+ ):
138
+ buttons.append({
139
+ "type": "button",
140
+ "sub_type": stype.strip(),
141
+ "index": int(idx.strip()),
142
+ "parameters": [{"type": ptype.strip(), ptype.strip(): pval.strip()}]
143
+ })
144
+ components += buttons
145
+ except:
146
+ pass
147
+
148
+ scheduled_datetime = datetime.strptime(f"{schedule_date} {schedule_time}", "%Y-%m-%d %H:%M:%S")
149
+
150
+ def job():
151
+ now = datetime.now()
152
+ if now >= scheduled_datetime:
153
+ schedule.clear()
154
+ execute_campaign({
155
+ "phones": phones,
156
+ "api_url": api_url,
157
+ "template_name": template_name,
158
+ "language_code": language_code,
159
+ "components": components,
160
+ "headers": headers
161
+ })
162
+
163
+ schedule.every(1).minutes.do(job)
164
+
165
+ def scheduler_loop():
166
+ while True:
167
+ schedule.run_pending()
168
+ time.sleep(1)
169
+
170
+ threading.Thread(target=scheduler_loop, daemon=True).start()
171
+
172
+ return f"βœ… Campaign scheduled for {scheduled_datetime.strftime('%Y-%m-%d %H:%M:%S')}."
173
+
174
+ # --- Gradio Interface ---
175
+
176
+ demo = gr.Interface(
177
+ fn=schedule_campaign,
178
+ inputs=[
179
+ gr.Textbox(label="Access Token", type="password"),
180
+ gr.Textbox(label="Phone Number ID"),
181
+ gr.Textbox(label="Template Name"),
182
+ gr.Textbox(label="Language Code", value="en"),
183
+ gr.Dropdown(["image", "video"], label="Header Type"),
184
+
185
+ gr.Textbox(label="WhatsApp API URL"),
186
+ gr.File(label="CSV File with Phone Numbers", file_types=[".csv"]),
187
+ gr.File(label="(Optional) Media File", file_types=[".jpg", ".jpeg", ".png", ".mp4"]),
188
+ gr.Textbox(label="(Optional) Existing Media ID"),
189
+ gr.Textbox(label="(Optional) Media Link"),
190
+
191
+ gr.Textbox(label="Schedule Date (YYYY-MM-DD)"),
192
+ gr.Textbox(label="Schedule Time (HH:MM:SS)"),
193
+
194
+ gr.Textbox(label="Body Text Parameters (one per line)", lines=5),
195
+ gr.Textbox(label="Button Sub Types (comma-separated)"),
196
+ gr.Textbox(label="Button Indexes (comma-separated)"),
197
+ gr.Textbox(label="Button Param Types (comma-separated)"),
198
+ gr.Textbox(label="Button Param Values (comma-separated)")
199
+ ],
200
+ outputs="text",
201
+ title="Temple Dekho WhatsApp Promo Scheduler"
202
+ )
203
+
204
+ if __name__ == "__main__":
205
+ demo.launch()