| from fastapi import FastAPI, Request |
| from pydantic import BaseModel |
| import requests |
| import logging |
| from datetime import datetime |
|
|
| |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') |
|
|
| app = FastAPI() |
|
|
| |
| CLICKUP_URL_BASE = "https://api.clickup.com/api/v2" |
|
|
| |
| WHATSAPP_URL = "https://7105.api.greenapi.com/waInstance7105265861/sendMessage/f1e39ce29d1f4040a20f0718547a384ae2e0afb3d9884727ad" |
|
|
| |
| ACCESS_TOKEN = "2144425825_36f2249dc27c5aca075ac5442b1bbcdf01c3a29b9e41b86bda46a6cf651acd0f" |
|
|
| |
| web_app_url = "https://script.google.com/macros/s/AKfycbx9-oUXV896jM0HbQSz4h61Crf_UYHM8LJMbxXux4PHwf38zqjaJjIJe9O4UyT1u6s/exec" |
|
|
| |
| headers = { |
| "Authorization": ACCESS_TOKEN, |
| "Content-Type": "application/json" |
| } |
|
|
| |
| whatsapp_headers = { |
| "Content-Type": "application/json" |
| } |
|
|
| class TaskData(BaseModel): |
| task_name: str |
| task_type: str |
| campaign_name: str |
| platforms: list[str] |
| assignees: list[int] |
| due_date: int |
|
|
| @app.post("/singletask") |
| async def create_task(request: Request): |
| data = await request.json() |
| logging.info(f"Received task data: {data}") |
|
|
| |
| 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(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}") |
| |
| |
| description_text = "" |
| task_type_lower = task_type.lower() |
| |
| |
| template_ids = { |
| "content": "t-8698wwx4z", |
| "creative": "t-8698wwx4z", |
| "ads": "t-8698wwx4z" |
| } |
| custom_field_id = "64b6898b-eaee-4dd1-b819-a5b142226f69" |
| team_id = "9012303718" |
| |
| |
| 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: |
| |
| 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}") |
|
|
| |
| update_payload = { |
| "assignees": { |
| "add": [int(uid) for uid in assignee_ids], |
| "rem": [] |
| }, |
| "status": status |
| } |
| |
| |
| 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_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_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) |
| |
| |
| 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}") |
| |
| |
|
|
| 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"} |
| |
| |
| 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 |
| } |
|
|
| |
| 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}") |
|
|
| |
| |
| new_task_id = clickup_data.get("id") |
| space_id = clickup_data.get("space", {}).get("id") |
| assignee_ids = [str(uid) for uid in assignee_ids] |
| |
| |
| 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}"} |
| |
| |
|
|
| |
| 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 |
| } |
| |
| |
| response = requests.get(url, headers=headers) |
|
|
| |
| if response.status_code == 200: |
| |
| |
| 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") |
| |
| |
| 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": |
| |
| 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}") |
| |
| params = { |
| "mode": "notify-roles", |
| "assigneeId": assignee_ids[0], |
| "spaceId": space_id |
| } |
| response = requests.get(web_app_url, params=params) |
| |
| if response.ok: |
| notify_map = response.json() |
| print("✅ Notify Map:", notify_map) |
| |
| |
| 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) |
| |
| |
| 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) |
| |
| |
| 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": |
|
|
| |
| task_details = get_task_details(task_id) |
| print(f"Task details: {task_details}") |
| |
| |
| assignee_ids = [str(assignee['id']) for assignee in task_details.get('assignees', [])] |
| print("Assignee IDs:", assignee_ids) |
| |
| |
| space_id = task_details.get("space", {}).get("id") |
| |
| |
| |
| params = { |
| "mode": "notify", |
| "spaceId": space_id, |
| } |
| |
| |
| for assignee_id in assignee_ids: |
| params.setdefault("assignees", []).append(assignee_id) |
|
|
| |
| task_name = task_details.get("name", "Unnamed Task") |
| |
| |
| assignee_id_to_name = { |
| str(assignee.get("id")): assignee.get("username") |
| for assignee in task_details.get("assignees", []) |
| } |
| |
| |
| print("📝 Task Name:", task_name) |
|
|
| task_url = task_details.get("url", "") |
| print("🔗 Task URL:", task_url) |
| |
| |
| 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" |
| |
| |
| response = requests.get(web_app_url, params=params) |
| |
| |
| if response.ok: |
| notify_map = response.json() |
| print("✅ Notify Map:", notify_map) |
| |
| |
| user_dict = notify_map.get("users", {}) |
| manager_entries = notify_map.get("managers", []) |
| |
| |
| user_numbers = list(user_dict.values()) |
| |
| |
| 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) |
| |
| |
| 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"} |