Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +55 -40
src/streamlit_app.py
CHANGED
|
@@ -161,7 +161,6 @@
|
|
| 161 |
|
| 162 |
|
| 163 |
|
| 164 |
-
|
| 165 |
import streamlit as st
|
| 166 |
import requests
|
| 167 |
import csv
|
|
@@ -173,34 +172,34 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
| 173 |
import os
|
| 174 |
import threading
|
| 175 |
import schedule
|
| 176 |
-
import os
|
| 177 |
|
| 178 |
st.set_page_config(page_title="Temple Dekho WhatsApp Promo Scheduler", layout="wide")
|
| 179 |
st.title("π
Temple Dekho WhatsApp Message Scheduler")
|
| 180 |
|
| 181 |
-
#
|
| 182 |
log_dir = "/tmp/logs"
|
| 183 |
os.makedirs(log_dir, exist_ok=True)
|
| 184 |
|
| 185 |
# --- Helper Functions ---
|
| 186 |
-
def upload_media(
|
| 187 |
-
mime_type = mimetypes.guess_type(
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
|
|
|
| 204 |
|
| 205 |
def send_message(phone, payload, headers, retries=3, backoff_intervals=[5, 15, 30]):
|
| 206 |
for attempt in range(retries):
|
|
@@ -251,7 +250,7 @@ def execute_campaign(params):
|
|
| 251 |
logfile.write(result + "\n")
|
| 252 |
logfile.flush()
|
| 253 |
|
| 254 |
-
# --- UI Form
|
| 255 |
with st.form("schedule_form"):
|
| 256 |
st.subheader("ποΈ Schedule WhatsApp Campaign")
|
| 257 |
col1, col2 = st.columns(2)
|
|
@@ -288,27 +287,45 @@ if submitted:
|
|
| 288 |
if not csv_file:
|
| 289 |
st.error("CSV file is required.")
|
| 290 |
else:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
media_id = existing_media_id.strip() if existing_media_id.strip() else None
|
|
|
|
|
|
|
| 292 |
if media_file and not media_id:
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
| 294 |
if not media_id:
|
| 295 |
st.error(f"Media upload failed. Status: {status_code}\nResponse: {response}")
|
|
|
|
| 296 |
|
| 297 |
-
|
| 298 |
-
decoded = csv_file.read().decode('utf-8').strip()
|
| 299 |
-
rows = csv.reader(io.StringIO(decoded))
|
| 300 |
-
next(rows, None)
|
| 301 |
-
|
| 302 |
phones = []
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
|
| 313 |
headers = {
|
| 314 |
"Authorization": f"Bearer {access_token}",
|
|
@@ -340,7 +357,7 @@ if submitted:
|
|
| 340 |
})
|
| 341 |
components += buttons
|
| 342 |
except:
|
| 343 |
-
pass
|
| 344 |
|
| 345 |
scheduled_datetime = datetime.combine(schedule_date, schedule_time)
|
| 346 |
|
|
@@ -367,5 +384,3 @@ if submitted:
|
|
| 367 |
threading.Thread(target=run_scheduler, daemon=True).start()
|
| 368 |
|
| 369 |
st.success(f"β
Campaign scheduled for {scheduled_datetime.strftime('%Y-%m-%d %H:%M:%S')}. UI can now be closed.")
|
| 370 |
-
|
| 371 |
-
|
|
|
|
| 161 |
|
| 162 |
|
| 163 |
|
|
|
|
| 164 |
import streamlit as st
|
| 165 |
import requests
|
| 166 |
import csv
|
|
|
|
| 172 |
import os
|
| 173 |
import threading
|
| 174 |
import schedule
|
|
|
|
| 175 |
|
| 176 |
st.set_page_config(page_title="Temple Dekho WhatsApp Promo Scheduler", layout="wide")
|
| 177 |
st.title("π
Temple Dekho WhatsApp Message Scheduler")
|
| 178 |
|
| 179 |
+
# Logs directory
|
| 180 |
log_dir = "/tmp/logs"
|
| 181 |
os.makedirs(log_dir, exist_ok=True)
|
| 182 |
|
| 183 |
# --- Helper Functions ---
|
| 184 |
+
def upload_media(file_path, access_token, phone_number_id):
|
| 185 |
+
mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
| 186 |
+
with open(file_path, "rb") as f:
|
| 187 |
+
files = {
|
| 188 |
+
'file': (os.path.basename(file_path), f, mime_type),
|
| 189 |
+
'type': (None, mime_type),
|
| 190 |
+
'messaging_product': (None, 'whatsapp')
|
| 191 |
+
}
|
| 192 |
+
headers = {
|
| 193 |
+
'Authorization': f'Bearer {access_token}'
|
| 194 |
+
}
|
| 195 |
+
url = f"https://graph.facebook.com/v19.0/{phone_number_id}/media"
|
| 196 |
+
|
| 197 |
+
try:
|
| 198 |
+
res = requests.post(url, headers=headers, files=files)
|
| 199 |
+
res.raise_for_status()
|
| 200 |
+
return res.json().get("id"), res.status_code, res.text
|
| 201 |
+
except Exception as e:
|
| 202 |
+
return None, 500, str(e)
|
| 203 |
|
| 204 |
def send_message(phone, payload, headers, retries=3, backoff_intervals=[5, 15, 30]):
|
| 205 |
for attempt in range(retries):
|
|
|
|
| 250 |
logfile.write(result + "\n")
|
| 251 |
logfile.flush()
|
| 252 |
|
| 253 |
+
# --- UI Form ---
|
| 254 |
with st.form("schedule_form"):
|
| 255 |
st.subheader("ποΈ Schedule WhatsApp Campaign")
|
| 256 |
col1, col2 = st.columns(2)
|
|
|
|
| 287 |
if not csv_file:
|
| 288 |
st.error("CSV file is required.")
|
| 289 |
else:
|
| 290 |
+
# Save CSV to /tmp
|
| 291 |
+
csv_path = f"/tmp/{csv_file.name}"
|
| 292 |
+
with open(csv_path, "wb") as f:
|
| 293 |
+
f.write(csv_file.getbuffer())
|
| 294 |
+
|
| 295 |
media_id = existing_media_id.strip() if existing_media_id.strip() else None
|
| 296 |
+
|
| 297 |
+
# Save media file to /tmp
|
| 298 |
if media_file and not media_id:
|
| 299 |
+
media_path = f"/tmp/{media_file.name}"
|
| 300 |
+
with open(media_path, "wb") as f:
|
| 301 |
+
f.write(media_file.getbuffer())
|
| 302 |
+
media_id, status_code, response = upload_media(media_path, access_token, phone_number_id)
|
| 303 |
if not media_id:
|
| 304 |
st.error(f"Media upload failed. Status: {status_code}\nResponse: {response}")
|
| 305 |
+
st.stop()
|
| 306 |
|
| 307 |
+
# Process CSV
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
phones = []
|
| 309 |
+
try:
|
| 310 |
+
with open(csv_path, "r", encoding="utf-8-sig") as f:
|
| 311 |
+
reader = csv.reader(f)
|
| 312 |
+
next(reader, None)
|
| 313 |
+
for row in reader:
|
| 314 |
+
if not row or not row[0].strip():
|
| 315 |
+
continue
|
| 316 |
+
num = row[0].strip()
|
| 317 |
+
if num.isdigit():
|
| 318 |
+
if len(num) == 10:
|
| 319 |
+
phones.append("91" + num)
|
| 320 |
+
elif len(num) == 12 and num.startswith("91"):
|
| 321 |
+
phones.append(num)
|
| 322 |
+
except Exception as e:
|
| 323 |
+
st.error(f"Error reading CSV: {e}")
|
| 324 |
+
st.stop()
|
| 325 |
+
|
| 326 |
+
if not phones:
|
| 327 |
+
st.error("No valid phone numbers found in CSV.")
|
| 328 |
+
st.stop()
|
| 329 |
|
| 330 |
headers = {
|
| 331 |
"Authorization": f"Bearer {access_token}",
|
|
|
|
| 357 |
})
|
| 358 |
components += buttons
|
| 359 |
except:
|
| 360 |
+
pass # ignore if button input is malformed
|
| 361 |
|
| 362 |
scheduled_datetime = datetime.combine(schedule_date, schedule_time)
|
| 363 |
|
|
|
|
| 384 |
threading.Thread(target=run_scheduler, daemon=True).start()
|
| 385 |
|
| 386 |
st.success(f"β
Campaign scheduled for {scheduled_datetime.strftime('%Y-%m-%d %H:%M:%S')}. UI can now be closed.")
|
|
|
|
|
|