Mr-Help's picture
Update app.py
bbec878 verified
from fastapi import FastAPI, Request
from pydantic import BaseModel
import requests
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
app = FastAPI()
# ClickUp API URL for creating a task
CLICKUP_URL_BASE = "https://api.clickup.com/api/v2"
# WhatsApp API URL
WHATSAPP_URL = "https://7105.api.greenapi.com/waInstance7105265861/sendMessage/f1e39ce29d1f4040a20f0718547a384ae2e0afb3d9884727ad"
# Your access token
ACCESS_TOKEN = "2144425825_36f2249dc27c5aca075ac5442b1bbcdf01c3a29b9e41b86bda46a6cf651acd0f"
# Google Apps Script Data handler
web_app_url = "https://script.google.com/macros/s/AKfycbx9-oUXV896jM0HbQSz4h61Crf_UYHM8LJMbxXux4PHwf38zqjaJjIJe9O4UyT1u6s/exec"
# Headers for ClickUp authorization
headers = {
"Authorization": ACCESS_TOKEN,
"Content-Type": "application/json"
}
# Headers for WhatsApp API
whatsapp_headers = {
"Content-Type": "application/json"
}
class TaskData(BaseModel):
task_name: str
task_type: str # "Reel" or "Design"
campaign_name: str
platforms: list[str]
assignees: list[int] # List of user IDs
due_date: int # Unix timestamp in milliseconds
@app.post("/singletask")
async def create_task(request: Request):
data = await request.json()
logging.info(f"Received task data: {data}")
# Extract and assign to variables
team = data.get('team', '')
task_type = data.get('taskType', '')
task_title = data.get('taskTitle', '')
assignees = data.get('assignees', '')
platforms = data.get('platforms', [])
deadline = data.get('deadline', '')
goal = data.get('goal', '')
description = data.get('description', '')
creative_type = data.get('creativeType', '')
ad_content = data.get('adContent', '')
posting_content = data.get('postingContent', '')
attachment_link = data.get('attachmentLink', '')
start_date = data.get('startDate', '')
end_date = data.get('endDate', '')
# Print extracted values for debugging
print(f"Team: {team}")
print(f"Task Type: {task_type}")
print(f"Task Title: {task_title}")
print(f"Assignees: {assignees}")
print(f"Platforms: {platforms}")
print(f"Deadline: {deadline}")
print(f"Goal: {goal}")
print(f"Description: {description}")
print(f"Creative Type: {creative_type}")
print(f"Ad Content: {ad_content}")
print(f"Posting Content: {posting_content}")
print(f"Attachment Link: {attachment_link}")
print(f"Start Date: {start_date}")
print(f"End Date: {end_date}")
params = {
"mode": "extended",
"taskType": task_type,
"company": team,
"assignees": ','.join(assignees)
}
response = requests.get(web_app_url, params=params)
try:
response_data = response.json()
except ValueError:
print("ERROR: Failed to decode JSON from web app response")
return {"error": "Invalid response from Google Apps Script"}
print(f"Web App Response: {response_data}")
list_id = response_data.get("listId")
print(f"List ID: {list_id}")
assignee_ids = response_data.get("assigneeIds", [])
print(f"Assignee IDs: {assignee_ids}")
assignee_numbers = response_data.get("assigneeNumbers", [])
manager_numbers = response_data.get("managerNumbers", [])
print(f"Assignee Numbers: {assignee_numbers}")
print(f"Manager Numbers: {manager_numbers}")
# Create ClickUp task according to task type
description_text = ""
task_type_lower = task_type.lower()
# Template and field setup
template_ids = {
"content": "t-8698wwx4z",
"creative": "t-8698wwx4z",
"ads": "t-8698wwx4z"
}
custom_field_id = "64b6898b-eaee-4dd1-b819-a5b142226f69"
team_id = "9012303718"
# Shared description builder
if task_type_lower == "content":
status = "backlog"
description_text = goal.strip()
if platforms:
description_text += "\n\nPlatforms: " + ", ".join(platforms)
elif task_type_lower == "creative":
status = "to do"
description_text = creative_type.strip()
if platforms:
description_text += "\n\nPlatforms: " + ", ".join(platforms)
elif task_type_lower == "ads":
status = "ready"
parts = []
if ad_content:
parts.append(ad_content.strip())
if attachment_link:
parts.append(f"Attachment: {attachment_link.strip()}")
if platforms:
parts.append("Platforms: " + ", ".join(platforms))
description_text = "\n\n".join(parts)
if task_type_lower in template_ids:
# Create from template
template_id = template_ids[task_type_lower]
template_url = f"{CLICKUP_URL_BASE}/list/{list_id}/taskTemplate/{template_id}"
template_payload = {
"name": task_title
}
response = requests.post(template_url, headers=headers, json=template_payload)
print("Template Creation Status:", response.status_code)
print(f"Template Creation response {response.json()}")
if response.ok:
new_task = response.json()
new_task_id = new_task.get("id")
space_id = new_task.get("task", {}).get("space", {}).get("id")
print("✅ Task created from template:", new_task_id)
print(f"Space id is {space_id}")
# ➕ Prepare the update payload to add the assignee and duedate to the created task from template
update_payload = {
"assignees": {
"add": [int(uid) for uid in assignee_ids],
"rem": [] # optionally remove others if needed
},
"status": status
}
# ➕ Add due date if provided
if deadline:
try:
due_timestamp = int(datetime.strptime(deadline, "%Y-%m-%d").timestamp() * 1000)
update_payload["due_date"] = due_timestamp
print(f"📅 Due Date (timestamp): {due_timestamp}")
except ValueError:
print("❌ Invalid deadline format. Skipping due_date.")
print(f"Update payload is {update_payload}\n")
# 🔁 Update the task with assignees and due date
update_url = f"{CLICKUP_URL_BASE}/task/{new_task_id}"
update_response = requests.put(update_url, headers=headers, json=update_payload)
print("🔧 Update Status:", update_response.status_code)
print("🧾 Update Response:", update_response.text)
# Update custom field (used as description)
update_field_url = f"{CLICKUP_URL_BASE}/task/{new_task_id}/field/{custom_field_id}?custom_task_ids=true&team_id={team_id}"
field_payload = {"value": description_text}
field_update = requests.post(update_field_url, headers=headers, json=field_payload)
print("📥 Field Update Status:", field_update.status_code)
print("📥 Field Update Response:", field_update.text)
# ✅ Notify each assignee (same logic used in standard tasks)
for assignee_id in assignee_ids:
params = {
"mode": "notify-assigned",
"assigneeId": assignee_id,
"spaceId": space_id
}
response = requests.get(web_app_url, params=params)
print(f"Assignee to notify {response.json()}")
if response.ok:
phone = response.json().get("phone")
if phone and phone != "not-found":
chat_id = f"{phone}@c.us"
task_url = f"https://app.clickup.com/t/{new_task_id}"
task_name = task_title
if deadline:
due_str = datetime.utcfromtimestamp(due_timestamp / 1000).strftime('%Y-%m-%d')
msg = (
f"📌 *تم تعيين مهمة جديدة لك!*\n\n"
f"📝 *اسم المهمة:* {task_name}\n"
f"📅 *تاريخ التسليم:* {due_str}\n"
f"🔗 *رابط المهمة:* {task_url}"
)
else:
msg = (
f"📌 *تم تعيين مهمة جديدة لك!*\n\n"
f"📝 *اسم المهمة:* {task_name}\n"
f"🔗 *رابط المهمة:* {task_url}"
)
send_whatsapp_notification(chat_id, msg)
else:
logging.warning(f"❌ Couldn't fetch phone number for assignee {assignee_id}")
# ✅ Then return the response
return {
"status": "Template-based task created",
"task_id": new_task_id
}
else:
print("❌ Failed to create task from template")
return {"error": "Template task creation failed"}
# If not using template, fallback to normal creation
elif task_type_lower in ["strategy", "posting", "ads report"]:
if task_type_lower == "strategy":
status = "to do"
description_text = description
elif task_type_lower == "posting":
status = "ready for posting"
parts = []
if posting_content:
parts.append(posting_content.strip())
if platforms:
parts.append("Platforms: " + ", ".join(platforms))
if attachment_link:
parts.append(f"Attachment: {attachment_link.strip()}")
description_text = "\n\n".join(parts)
elif task_type_lower == "ads report":
status = "ready"
platforms_text = ", ".join(platforms) if platforms else ""
description_text = f"Prepare an ads report for {platforms_text}"
if start_date and end_date:
description_text += f" from {start_date} to {end_date}"
payload = {
"name": task_title,
"description": description_text,
"assignees": [int(uid) for uid in assignee_ids],
"status": status
}
# Handle due date
if deadline:
try:
due_timestamp = int(datetime.strptime(deadline, "%Y-%m-%d").timestamp() * 1000)
payload["due_date"] = due_timestamp
print(f"Due Date (timestamp): {due_timestamp}")
except ValueError:
print("Invalid deadline format. Skipping due_date.")
print(f"Task payload is {payload}\n")
create_url = f"{CLICKUP_URL_BASE}/list/{list_id}/task"
clickup_response = requests.post(create_url, headers=headers, json=payload)
clickup_data = clickup_response.json()
print(f"ClickUp Response: {clickup_response.status_code}, {clickup_data}")
# After task creation and response
new_task_id = clickup_data.get("id")
space_id = clickup_data.get("space", {}).get("id")
assignee_ids = [str(uid) for uid in assignee_ids] # already known
# Notify each assignee
for assignee_id in assignee_ids:
params = {
"mode": "notify-assigned",
"assigneeId": assignee_id,
"spaceId": space_id
}
response = requests.get(web_app_url, params=params)
print(f"Assingnee to notify {response.json()}")
if response.ok:
phone = response.json().get("phone")
if phone and phone != "not-found":
chat_id = f"{phone}@c.us"
task_url = f"https://app.clickup.com/t/{new_task_id}"
task_name = payload["name"]
due_date = payload.get("due_date")
if due_date:
due_str = datetime.utcfromtimestamp(due_date / 1000).strftime('%Y-%m-%d')
msg = (
f"📌 *تم تعيين مهمة جديدة لك!*\n\n"
f"📝 *اسم المهمة:* {task_name}\n"
f"📅 *تاريخ التسليم:* {due_str}\n"
f"🔗 *رابط المهمة:* {task_url}"
)
else:
msg = (
f"📌 *تم تعيين مهمة جديدة لك!*\n\n"
f"📝 *اسم المهمة:* {task_name}\n"
f"🔗 *رابط المهمة:* {task_url}"
)
send_whatsapp_notification(chat_id, msg)
return {"status": "Standard task created", "clickup": clickup_data}
else:
logging.warning(f"❌ Couldn't fetch phone number for assignee {assignee_id}")
else:
return {"error": f"Unsupported task type: {task_type}"}
# Function to get task name by task ID
def get_task_name(task_id):
"""Fetch task details from ClickUp API using task_id."""
url = f"https://api.clickup.com/api/v2/task/{task_id}"
headers = {
"Authorization": ACCESS_TOKEN
}
# Make the API request to ClickUp
response = requests.get(url, headers=headers)
# Check if the request was successful (status code 200)
if response.status_code == 200:
# Parse and return the task details
task_name = response.json()["name"]
return task_name
else:
task_name = "Task name wasn't not found"
return task_name
def get_task_details(task_id):
url = f"https://api.clickup.com/api/v2/task/{task_id}"
headers = {"Authorization": ACCESS_TOKEN}
response = requests.get(url, headers=headers)
if response.status_code == 200:
task_data = response.json()
else:
print(f"Error: {response.status_code}, {response.text}")
task_data = "invalid data"
return task_data
def send_whatsapp_notification(chat_id: str, message: str):
payload = {
"chatId": chat_id,
"message": message
}
try:
response = requests.post(WHATSAPP_URL, json=payload, headers=whatsapp_headers)
response.raise_for_status()
logging.info(f"WhatsApp sent to {chat_id} - {response.status_code}: {response.text}")
except requests.RequestException as e:
logging.error(f"WhatsApp send failed for {chat_id}: {e}")
@app.post("/updates")
async def task_update(request: Request):
data = await request.json()
logging.info(f"Received task update from ClickUp: {data}")
event_type = data.get("event")
task_id = data.get("task_id", "Unknown ID")
# Get task details
task_name = get_task_name(task_id)
task_link = f"https://app.clickup.com/t/{task_id}"
if event_type == "taskUpdated":
history_items = data.get("history_items", [])
for item in history_items:
if item.get("field") == "status":
after_status = item.get("after", {}).get("status")
action_timestamp = item.get("date", 0)
if not after_status:
logging.warning(f"Task {task_id} update ignored: No status change detected.")
continue
action_date_human = datetime.utcfromtimestamp(int(action_timestamp) / 1000).strftime('%Y-%m-%d %H:%M:%S') if action_timestamp else "Unknown Date"
logging.info(f"Task: {task_name}, New Status: {after_status}, Action Date: {action_date_human}")
if after_status.lower() == "ready for review":
# Step 1: Get task details
task_details = get_task_details(task_id)
print(f"Task details: {task_details}")
task_name = task_details.get("name", "Unnamed Task")
task_url = task_details.get("url", "")
space_id = task_details.get("space", {}).get("id")
assignees = task_details.get("assignees", [])
assignee_ids = [str(assignee['id']) for assignee in assignees]
assignee_id_to_name = {str(assignee['id']): assignee['username'] for assignee in assignees}
assignee_names_str = "، ".join(assignee_id_to_name.values())
print(f"Assignees: {assignee_ids}")
# Step 2: Call WebApp for notify-roles
params = {
"mode": "notify-roles",
"assigneeId": assignee_ids[0], # Assume main assignee
"spaceId": space_id
}
response = requests.get(web_app_url, params=params)
if response.ok:
notify_map = response.json()
print("✅ Notify Map:", notify_map)
# 1️⃣ Notify the user (confirmation)
user_dict = notify_map.get("users", {})
for user_id, number in user_dict.items():
chat_id = f"{number}@c.us"
user_message = (
f"✅ *تم نقل المهمة للمراجعة بنجاح!*\n\n"
f"📌 *المهمة:* {task_name}\n"
f"📅 *التاريخ:* {action_date_human}\n"
f"🔗 *رابط المهمة:* {task_url}\n\n"
f"شكراً لك على استكمال المهمة."
)
send_whatsapp_notification(chat_id, user_message)
# 2️⃣ Notify the team roles
notify_users = notify_map.get("notifyUsers", [])
for user in notify_users:
name = user.get("name", "عضو الفريق")
number = user.get("phone")
if number:
chat_id = f"{number}@c.us"
notify_message = (
f"📣 *تنبيه للفريق:*\n\n"
f"📌 *{assignee_names_str}* قام بنقل المهمة التالية للمراجعة:\n"
f"*{task_name}*\n"
f"🔗 {task_url}\n\n"
f"يرجى مراجعة المهمة وإضافة ملاحظاتك."
)
send_whatsapp_notification(chat_id, notify_message)
# 3️⃣ Notify the managers
managers = notify_map.get("managers", [])
for manager in managers:
manager_name = manager.get("name", "مدير")
number = manager.get("phone")
if number:
chat_id = f"{number}@c.us"
manager_message = (
f"👤 *إشعار للمدير:*\n\n"
f"📌 *{assignee_names_str}* قام بنقل المهمة للمراجعة:\n"
f"*{task_name}*\n"
f"🔗 {task_url}\n\n"
f"يمكنك إلقاء نظرة وإضافة ملاحظات إن وجدت."
)
send_whatsapp_notification(chat_id, manager_message)
else:
logging.error(f"❌ Failed to fetch notify-roles data: {response.status_code}")
print("Raw response:", response.text)
elif event_type == "taskTagUpdated":
history_items = data.get("history_items", [])
if history_items:
for history_item in history_items:
if "after" in history_item:
for tag in history_item["after"]:
tag_name = tag.get("name")
if tag_name and tag_name.lower() == "missed due date":
# Step 1: Prepare data
task_details = get_task_details(task_id)
print(f"Task details: {task_details}")
# ✅ Extract assignee **IDs**
assignee_ids = [str(assignee['id']) for assignee in task_details.get('assignees', [])]
print("Assignee IDs:", assignee_ids)
# ✅ Extract space ID
space_id = task_details.get("space", {}).get("id")
# Step 2: Prepare the request to Apps Script Web App
params = {
"mode": "notify",
"spaceId": space_id,
}
# Append assignees[] multiple times for each ID
for assignee_id in assignee_ids:
params.setdefault("assignees", []).append(assignee_id)
# ✅ Get task name
task_name = task_details.get("name", "Unnamed Task")
# ✅ Create a mapping of assignee ID to name
assignee_id_to_name = {
str(assignee.get("id")): assignee.get("username")
for assignee in task_details.get("assignees", [])
}
# ✅ Print the values
print("📝 Task Name:", task_name)
task_url = task_details.get("url", "")
print("🔗 Task URL:", task_url)
# Get due date
due_date_timestamp = task_details.get("due_date")
due_date = datetime.utcfromtimestamp(int(due_date_timestamp) / 1000).strftime('%Y-%m-%d') if due_date_timestamp else "Unknown Due Date"
# Step 3: Make the request
response = requests.get(web_app_url, params=params)
# Step 4: Handle the response
if response.ok:
notify_map = response.json()
print("✅ Notify Map:", notify_map)
# ✅ Extract user and manager numbers
user_dict = notify_map.get("users", {})
manager_entries = notify_map.get("managers", [])
# ✅ Get unique user numbers
user_numbers = list(user_dict.values())
# ✅ Get assignee names from user_dict keys (user IDs)
user_names = [
assignee_id_to_name.get(user_id, f"User {user_id}")
for user_id in user_dict.keys()
]
assignee_name_str = "، ".join(user_names)
print("📞 User Numbers:", user_numbers)
print("👥 Assignee Name(s) for Manager Message:", assignee_name_str)
# ✅ Arabic WhatsApp messages
user_message = (
f"⚠️ *تنبيه بتأخر المهمة!*\n\n"
f"📌 *المهمة:* {task_name}\n"
f"📅 *تاريخ التسليم:* {due_date}\n"
f"🔗 *رابط المهمة:* {task_url}\n\n"
f"يرجى اتخاذ الإجراء اللازم فوراً."
)
for num in user_numbers:
chat_id = f"{num}@c.us"
send_whatsapp_notification(chat_id, user_message)
for manager in manager_entries:
manager_name = manager.get("name", "مدير غير معروف")
manager_number = manager.get("number")
if manager_number:
chat_id = f"{manager_number}@c.us"
personalized_message = (
f"📣 *تنبيه مهم:*\n\n"
f"📌 مرحباً {manager_name}\n"
f"📌 *الموظف:* {assignee_name_str}\n"
f"❌ *تأخر في مهمة:* {task_name}\n"
f"📅 *تاريخ التسليم:* {due_date}\n"
f"🔗 *رابط المهمة:* {task_url}\n\n"
f"يرجى المتابعة مع الفريق."
)
send_whatsapp_notification(chat_id, personalized_message)
else:
print("❌ Failed to fetch notify data:", response.status_code)
print("Raw response:", response.text)
logging.info(f"Missed Due Date Tag Added for Task: {task_name}, Due Date: {due_date}")
return {"status": "Update received"}