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"}