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