Download tools.py from ayb-bh92/tools: direct link, hf CLI and curl.
- Browser
- Download file 41.4 kB
-
https://huggingface.co/spaces/ayb-bh92/tools/resolve/main/tools.py
- Command line
-
hf download hf://spaces/ayb-bh92/tools/tools.py
-
curl -L -o tools.py https://huggingface.co/spaces/ayb-bh92/tools/resolve/main/tools.py
41.4 kB
| # ุฌู ูุน ุงูุฃุฏูุงุช ุงูู ุญููุฉ ู ุน ุฏุนู ุงููุบุฉ ุงูุนุฑุจูุฉ | |
| import os | |
| import io | |
| import json | |
| import base64 | |
| import uuid | |
| import asyncio | |
| import httpx | |
| from typing import Dict, Any, Callable, Optional, List | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from fastapi import Request | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงุณุชูุฑุงุฏุงุช ReportLab | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.enums import TA_RIGHT | |
| from reportlab.pdfbase import pdfmetrics | |
| from reportlab.pdfbase.ttfonts import TTFont | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุฏุนู ุงููุบุฉ ุงูุนุฑุจูุฉ ูู ู ููุงุช PDF | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| BASE_DIR = Path(__file__).parent | |
| FONTS_DIR = BASE_DIR / "fonts" | |
| FONTS_DIR.mkdir(exist_ok=True) | |
| ARABIC_FONT_PATH = FONTS_DIR / "Amiri-Regular.ttf" | |
| ARABIC_FONT_URL = "https://raw.githubusercontent.com/google/fonts/main/ofl/amiri/Amiri-Regular.ttf" | |
| ARABIC_FONT_NAME = "Amiri" | |
| _arabic_font_ready = False | |
| async def _ensure_arabic_font() -> None: | |
| """ุชุฃูุฏ ู ู ูุฌูุฏ ุงูุฎุท ุงูุนุฑุจูุ ูุญู ูู ุฅุฐุง ูู ููู ู ูุฌูุฏุงู""" | |
| global _arabic_font_ready | |
| try: | |
| if not ARABIC_FONT_PATH.exists(): | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.get(ARABIC_FONT_URL) | |
| r.raise_for_status() | |
| ARABIC_FONT_PATH.write_bytes(r.content) | |
| from reportlab.pdfbase import pdfmetrics | |
| from reportlab.pdfbase.ttfonts import TTFont | |
| pdfmetrics.registerFont(TTFont(ARABIC_FONT_NAME, str(ARABIC_FONT_PATH))) | |
| _arabic_font_ready = True | |
| print("โ ุชู ุชุฌููุฒ ุงูุฎุท ุงูุนุฑุจู ุจูุฌุงุญ") | |
| except Exception as e: | |
| print(f"โ ๏ธ ุชุนุฐูุฑ ุชุฌููุฒ ุงูุฎุท ุงูุนุฑุจู: {e}") | |
| _arabic_font_ready = False | |
| def _shape_text(text: str) -> str: | |
| """ุฅุนุงุฏุฉ ุชุดููู ุงููุต ุงูุนุฑุจู ููุนุฑุถ ุงูุตุญูุญ ูู PDF""" | |
| try: | |
| import arabic_reshaper | |
| from bidi.algorithm import get_display | |
| if not text: | |
| return text | |
| reshaped = arabic_reshaper.reshape(text) | |
| return get_display(reshaped) | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดู ุชุดููู ุงููุต ุงูุนุฑุจู: {e}") | |
| return text | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 1. ุชุนุฑูู ุจููุฉ ุงูุฃุฏุงุฉ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| class LocalTool: | |
| """ุชู ุซูู ูุตูู ููุฃุฏุงุฉ""" | |
| name: str | |
| description: str | |
| parameters: Dict[str, Any] | |
| callable: Callable | |
| category: str = "General" | |
| aliases: List[str] = field(default_factory=list) | |
| tags: List[str] = field(default_factory=list) | |
| enabled: bool = True | |
| hidden: bool = False | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 2. Decorator ููุชุณุฌูู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| TOOLS: Dict[str, LocalTool] = {} | |
| def tool( | |
| *, | |
| name: Optional[str] = None, | |
| description: str, | |
| parameters: Dict[str, Any], | |
| category: str = "General", | |
| aliases: Optional[List[str]] = None, | |
| tags: Optional[List[str]] = None, | |
| enabled: bool = True, | |
| hidden: bool = False | |
| ) -> Callable: | |
| """Decorator ูุชุณุฌูู ุงูุฃุฏูุงุช ุงูู ุญููุฉ""" | |
| def wrapper(fn: Callable) -> Callable: | |
| tool_name = name or fn.__name__ | |
| TOOLS[tool_name] = LocalTool( | |
| name=tool_name, | |
| description=description, | |
| parameters=parameters, | |
| callable=fn, | |
| category=category, | |
| aliases=aliases or [], | |
| tags=tags or [], | |
| enabled=enabled, | |
| hidden=hidden | |
| ) | |
| return fn | |
| return wrapper | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 3. ุฏูุงู ู ุณุงุนุฏุฉ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _apply_markdown_lines(add_heading, add_paragraph, add_bullet, text: str): | |
| for raw in text.split("\n"): | |
| line = raw.strip() | |
| if not line: | |
| continue | |
| if line.startswith("### "): | |
| add_heading(line[4:], 3) | |
| elif line.startswith("## "): | |
| add_heading(line[3:], 2) | |
| elif line.startswith("# "): | |
| add_heading(line[2:], 1) | |
| elif line.startswith(("- ", "* ")): | |
| add_bullet(line[2:]) | |
| else: | |
| add_paragraph(line) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 4. ุฃุฏูุงุช Firecrawl โ ู ูุตุญููุญุฉ โ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def firecrawl_search(query: str, limit: int = 5, api_key: Optional[str] = None) -> dict: | |
| """ุจุญุซ ูู ุงูููุจ ุนุจุฑ Firecrawl""" | |
| if not api_key: | |
| return {"error": "FIRECRAWL_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.post( | |
| "https://api.firecrawl.dev/v1/search", | |
| headers={ | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json" | |
| }, | |
| json={"query": query, "limit": limit}, | |
| ) | |
| if r.status_code != 200: | |
| try: | |
| err_data = r.json() | |
| err_msg = err_data.get("error", str(err_data)) | |
| except Exception: | |
| err_msg = r.text[:500] | |
| return {"error": f"Firecrawl search error ({r.status_code}): {err_msg}"} | |
| data = r.json() | |
| results = data.get("data") or data.get("results") or [] | |
| out = [] | |
| for item in results[:limit]: | |
| out.append({ | |
| "title": item.get("title"), | |
| "url": item.get("url"), | |
| "snippet": item.get("description") or (item.get("markdown") or "")[:300], | |
| }) | |
| return {"results": out} | |
| async def firecrawl_scrape(url: str, api_key: Optional[str] = None) -> dict: | |
| """ุชุตูุญ ููุจ""" | |
| if not api_key: | |
| return {"error": "FIRECRAWL_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client: | |
| r = await client.post( | |
| "https://api.firecrawl.dev/v1/scrape", | |
| headers={ | |
| "Authorization": f"Bearer {api_key}", | |
| "Content-Type": "application/json" | |
| }, | |
| json={"url": url, "formats": ["markdown"], "onlyMainContent": True}, | |
| ) | |
| if r.status_code != 200: | |
| try: | |
| err_data = r.json() | |
| err_msg = err_data.get("error", str(err_data)) | |
| except Exception: | |
| err_msg = r.text[:500] | |
| return {"error": f"Firecrawl scrape error ({r.status_code}): {err_msg}"} | |
| data = r.json() | |
| markdown = (data.get("data") or {}).get("markdown", "") | |
| return {"markdown": markdown[:8000]} | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 5. ุฃุฏูุงุช Context7 โ ู ูุตุญููุญุฉ โ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def context7_resolve_library(query: str, api_key: Optional[str] = None) -> dict: | |
| """ุงูุจุญุซ ุนู ู ูุชุจุฉ ูู Context7""" | |
| if not api_key: | |
| return {"error": "CONTEXT7_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.get( | |
| "https://context7.com/api/v2/libs/search", | |
| params={"libraryName": query, "query": query}, | |
| headers={"Authorization": f"Bearer {api_key}"}, | |
| ) | |
| if r.status_code != 200: | |
| return {"error": f"Context7 API error ({r.status_code}): {r.text[:500]}"} | |
| data = r.json() | |
| results = data.get("results", data if isinstance(data, list) else []) | |
| libs = [{"id": it.get("id"), "title": it.get("title") or it.get("name")} for it in results[:8]] | |
| return {"libraries": libs} | |
| async def context7_get_docs(library_id: str, api_key: Optional[str] = None, topic: Optional[str] = None) -> dict: | |
| """ุฌูุจ ุชูุซูู ู ูุชุจุฉ""" | |
| if not api_key: | |
| return {"error": "CONTEXT7_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| if library_id.startswith("/"): | |
| library_id = library_id[1:] | |
| params = { | |
| "libraryId": f"/{library_id}", | |
| "query": topic or "documentation", | |
| "type": "txt", | |
| } | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.get( | |
| "https://context7.com/api/v2/context", | |
| params=params, | |
| headers={ | |
| "Authorization": f"Bearer {api_key}", | |
| "X-Context7-Source": "mcp-server" | |
| }, | |
| ) | |
| if r.status_code != 200: | |
| return {"error": f"Context7 API error ({r.status_code}): {r.text[:500]}"} | |
| text = r.text | |
| return {"docs": text[:6000]} | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 6. ุฃุฏูุงุช Composio โ ุฌู ูุน ุงูุฃุฏูุงุช โ (ู ุน ุงูุฅุตูุงุญ ุงูุฌุฑุงุญู) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| COMPOSIO_BASE_URL = "https://backend.composio.dev/api/v3" | |
| COMPOSIO_BASE_URL_V31 = "https://backend.composio.dev/api/v3.1" | |
| # โ โ โ ุงูุฅุตูุงุญ ุงูุฌุฑุงุญู: ุชูุญูุฏ ุงูู user_id ุงูุงูุชุฑุงุถู โ โ โ | |
| COMPOSIO_DEFAULT_USER_ID = "default_user" | |
| async def composio_list_connected_tools(api_key: Optional[str] = None) -> dict: | |
| """ุนุฑุถ ุงูุชุทุจููุงุช ุงูู ุฑุชุจุทุฉ""" | |
| if not api_key: | |
| return {"error": "COMPOSIO_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.get( | |
| f"{COMPOSIO_BASE_URL}/connected_accounts", | |
| headers={"x-api-key": api_key}, | |
| ) | |
| if r.status_code == 401: | |
| return {"error": "ู ูุชุงุญ Composio API ุบูุฑ ุตุงูุญ ุฃู ู ูุชูู. ุชุญูู ู ู COMPOSIO_API_KEY ูู Environment Variables."} | |
| if r.status_code != 200: | |
| return {"error": f"Composio API error ({r.status_code}): {r.text[:500]}"} | |
| data = r.json() | |
| items = data.get("items", []) | |
| tools = [{ | |
| "slug": (it.get("toolkit") or {}).get("slug"), | |
| "id": it.get("id"), | |
| "alias": it.get("alias"), | |
| "status": it.get("status"), | |
| } for it in items] | |
| return {"connected_tools": tools} | |
| async def composio_list_tools(toolkit_slug: str, api_key: Optional[str] = None, limit: int = 20) -> dict: | |
| """ุฌูุจ ุฃุฏูุงุช ุชุทุจูู ู ุนูู""" | |
| if not api_key: | |
| return {"error": "COMPOSIO_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.get( | |
| f"{COMPOSIO_BASE_URL}/tools", | |
| params={ | |
| "toolkit_slug": toolkit_slug, | |
| "toolkit_versions": "latest", | |
| "limit": limit, | |
| }, | |
| headers={"x-api-key": api_key}, | |
| ) | |
| if r.status_code != 200: | |
| return {"error": f"Composio API error ({r.status_code}): {r.text[:500]}"} | |
| data = r.json() | |
| items = data.get("items", data if isinstance(data, list) else []) | |
| tools = [{ | |
| "slug": it.get("slug") or it.get("name"), | |
| "description": (it.get("description") or "")[:200], | |
| } for it in items[:limit]] | |
| return {"toolkit": toolkit_slug, "tools": tools} | |
| async def composio_execute_tool( | |
| slug: str, | |
| api_key: Optional[str] = None, | |
| arguments: Optional[Dict[str, Any]] = None, | |
| user_id: Optional[str] = None, | |
| version: str = "latest" | |
| ) -> dict: | |
| """ | |
| ุชูููุฐ ุฃุฏุงุฉ Composio. | |
| โ ุงูุฅุตูุงุญ ุงูุฌุฑุงุญู: ุชูุญูุฏ user_id ูุงูุชุญูู ู ู ุงูุงุชุตุงู ุงููุดุท. | |
| """ | |
| if not api_key: | |
| return {"error": "COMPOSIO_API_KEY ุบูุฑ ู ุถุจูุท"} | |
| # โ ุชูุญูุฏ user_id | |
| resolved_user_id = user_id or COMPOSIO_DEFAULT_USER_ID | |
| payload = { | |
| "arguments": arguments or {}, | |
| "version": version, | |
| "user_id": resolved_user_id, | |
| } | |
| async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client: | |
| # โ ุงูุชุญูู ุงูู ุณุจู ู ู ูุฌูุฏ ุงุชุตุงู ูุดุท | |
| try: | |
| accounts_r = await client.get( | |
| f"{COMPOSIO_BASE_URL}/connected_accounts", | |
| headers={"x-api-key": api_key}, | |
| params={"user_ids": resolved_user_id}, | |
| ) | |
| accounts = accounts_r.json().get("items", []) if accounts_r.status_code == 200 else [] | |
| active_accounts = [a for a in accounts if a.get("status") == "ACTIVE"] | |
| if not active_accounts: | |
| return { | |
| "error": ( | |
| f"ูุง ููุฌุฏ ุญุณุงุจ ู ุชุตู ูุดุท (ACTIVE) ุชุญุช user_id='{resolved_user_id}'. " | |
| f"ุงุณุชุฎุฏู composio_create_session ุซู composio_authorize_toolkit ุจููุณ ุงูู user_idุ " | |
| f"ูุชุฃูุฏ ุฃู ุงูู ุณุชุฎุฏู ุฃูู ู ุชุณุฌูู ุงูุฏุฎูู ุนุจุฑ redirect_url ูุจู ุงูุชูููุฐ." | |
| ) | |
| } | |
| except Exception: | |
| pass | |
| # โ ุชูููุฐ ุงูุฃุฏุงุฉ | |
| r = await client.post( | |
| f"{COMPOSIO_BASE_URL}/tools/execute/{slug}", | |
| headers={"x-api-key": api_key, "Content-Type": "application/json"}, | |
| json=payload, | |
| ) | |
| if r.status_code == 404: | |
| return { | |
| "error": ( | |
| f"ุงูุฃุฏุงุฉ '{slug}' ุบูุฑ ู ูุฌูุฏุฉ ุฃู ูุง ููุฌุฏ ุงุชุตุงู ุตุงูุญ (404) ูู user_id='{resolved_user_id}'. " | |
| f"ุชุฃูุฏ ู ู ุงุณุชุฎุฏุงู slug ุตุญูุญ (ู ุซู GMAIL_FETCH_EMAILS) ูู ู ุฃู ุงูู ุณุชุฎุฏู ุฃูู ู ุชุณุฌูู ุงูุฏุฎูู." | |
| ) | |
| } | |
| if r.status_code == 401: | |
| return {"error": "ู ูุชุงุญ Composio API ุบูุฑ ุตุงูุญ. ุชุญูู ู ู COMPOSIO_API_KEY."} | |
| if r.status_code not in (200, 201): | |
| return {"error": f"Composio API error ({r.status_code}): {r.text[:500]}"} | |
| return r.json() | |
| # โ โ โ ุงูุฅุตูุงุญ ุงูุฌุฑุงุญู: composio_create_session โ โ โ | |
| async def composio_create_session( | |
| user_id: str, | |
| toolkits: List[str], | |
| api_key: Optional[str] = None, | |
| callback_url: Optional[str] = None, | |
| ) -> dict: | |
| """ุฅูุดุงุก ุฌูุณุฉ Tool Router ูุชุณุฌูู ุงูุฏุฎูู ุฅูู ุชุทุจููุงุช Composio""" | |
| try: | |
| if not api_key: | |
| api_key = os.environ.get("COMPOSIO_API_KEY") | |
| if not api_key: | |
| return {"error": "COMPOSIO_API_KEY ุบูุฑ ู ุถุจูุท ูู ู ุชุบูุฑุงุช ุงูุจูุฆุฉ"} | |
| except Exception as e: | |
| return {"error": str(e)} | |
| # โ โ โ ุงูุฅุตูุงุญ ุงูุฌุฑุงุญู: ุชูุญูุฏ user_id โ โ โ | |
| if not user_id: | |
| user_id = COMPOSIO_DEFAULT_USER_ID | |
| payload: Dict[str, Any] = {"user_id": user_id} | |
| if toolkits: | |
| payload["toolkits"] = {"enable": [tk.lower().strip() for tk in toolkits if tk]} | |
| if callback_url: | |
| payload["callback_url"] = callback_url | |
| async with httpx.AsyncClient(timeout=90, follow_redirects=True) as client: | |
| r = await client.post( | |
| f"{COMPOSIO_BASE_URL_V31}/tool_router/session", | |
| headers={"x-api-key": api_key, "Content-Type": "application/json"}, | |
| json=payload, | |
| ) | |
| if r.status_code == 400: | |
| return { | |
| "error": "ุฎุทุฃ ูู ุงูุชุญูู ู ู ุตุญุฉ ุงูุทูุจ", | |
| "detail": r.text[:500], | |
| "payload_sent": payload, | |
| "suggestion": "ุชุฃูุฏ ู ู ุฃู ุฌู ูุน ุงูุญููู ู ุชูุงููุฉ ู ุน ุฅุตุฏุงุฑ API ุงูู ุณุชุฎุฏู ", | |
| } | |
| if r.status_code != 200 and r.status_code != 201: | |
| return {"error": f"Composio API error ({r.status_code}): {r.text[:500]}"} | |
| data = r.json() | |
| return { | |
| "session_id": data.get("session_id"), | |
| "mcp_url": (data.get("mcp") or {}).get("url"), | |
| "tool_router_tools": data.get("tool_router_tools", []), | |
| "config": data.get("config", {}), | |
| } | |
| async def composio_authorize_toolkit( | |
| session_id: str, | |
| toolkit: str, | |
| api_key: Optional[str] = None, | |
| callback_url: Optional[str] = None, | |
| ) -> dict: | |
| """ุฅูุดุงุก ุฑุงุจุท ุชุณุฌูู ุฏุฎูู (Connect Link) ูุชุทุจูู ูุงุญุฏ ุฏุงุฎู ุฌูุณุฉ Tool Router""" | |
| try: | |
| if not api_key: | |
| api_key = os.environ.get("COMPOSIO_API_KEY") | |
| if not api_key: | |
| return {"error": "COMPOSIO_API_KEY ุบูุฑ ู ุถุจูุท ูู ู ุชุบูุฑุงุช ุงูุจูุฆุฉ"} | |
| except Exception as e: | |
| return {"error": str(e)} | |
| payload: Dict[str, Any] = {"toolkit": toolkit.lower().strip()} | |
| if callback_url: | |
| payload["callback_url"] = callback_url | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| r = await client.post( | |
| f"{COMPOSIO_BASE_URL}/tool_router/session/{session_id}/link", | |
| headers={"x-api-key": api_key, "Content-Type": "application/json"}, | |
| json=payload, | |
| ) | |
| if r.status_code == 404: | |
| return {"error": f"ุงูุฌูุณุฉ '{session_id}' ุบูุฑ ู ูุฌูุฏุฉ ุฃู ุงูุชุทุจูู '{toolkit}' ุบูุฑ ุตุงูุญ (404)."} | |
| if r.status_code != 200 and r.status_code != 201: | |
| return {"error": f"Composio API error ({r.status_code}): {r.text[:500]}"} | |
| data = r.json() | |
| return { | |
| "redirect_url": data.get("redirect_url") or data.get("redirectUrl"), | |
| "toolkit": toolkit, | |
| "raw": data, | |
| } | |
| async def composio_delete_connected_account( | |
| user_id: str, | |
| toolkit: str, | |
| api_key: Optional[str] = None, | |
| ) -> dict: | |
| """ุญุฐู ุงุชุตุงู ุชุทุจูู ู ุนูู ู ู Composio""" | |
| try: | |
| if not api_key: | |
| api_key = os.environ.get("COMPOSIO_API_KEY") | |
| if not api_key: | |
| return {"error": "COMPOSIO_API_KEY ุบูุฑ ู ุถุจูุท ูู ู ุชุบูุฑุงุช ุงูุจูุฆุฉ"} | |
| except Exception as e: | |
| return {"error": str(e)} | |
| # โ ุชูุญูุฏ user_id | |
| if not user_id: | |
| user_id = COMPOSIO_DEFAULT_USER_ID | |
| async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client: | |
| # 1. ุฌูุจ ูุงุฆู ุฉ ุงูุงุชุตุงูุงุช | |
| list_response = await client.get( | |
| f"{COMPOSIO_BASE_URL}/connected_accounts", | |
| headers={"x-api-key": api_key}, | |
| params={"user_ids": user_id}, | |
| ) | |
| if list_response.status_code != 200: | |
| return {"error": f"Failed to list connections: {list_response.text[:500]}"} | |
| data = list_response.json() | |
| items = data.get("items", []) | |
| # 2. ุงูุนุซูุฑ ุนูู ุงูุงุชุตุงู | |
| connection = None | |
| toolkit_lower = toolkit.lower().strip() | |
| for item in items: | |
| item_toolkit = item.get("toolkit", {}).get("slug") or item.get("authConfigId") | |
| if item_toolkit and item_toolkit.lower() == toolkit_lower and item.get("status") == "ACTIVE": | |
| connection = item | |
| break | |
| if not connection: | |
| return {"error": f"No active connection found for {toolkit} under user_id='{user_id}'"} | |
| # 3. ุญุฐู ุงูุงุชุตุงู | |
| delete_response = await client.delete( | |
| f"{COMPOSIO_BASE_URL}/connected_accounts/{connection['id']}", | |
| headers={"x-api-key": api_key}, | |
| ) | |
| if delete_response.status_code not in (200, 204): | |
| return {"error": f"Failed to delete connection: {delete_response.text[:500]}"} | |
| return {"ok": True, "message": f"Connection to {toolkit} deleted successfully"} | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 7. ุฃุฏูุงุช ุงูู ุณุชูุฏุงุช (ู ุน ุฏุนู ุงูุนุฑุจูุฉ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def create_docx(title: str, content_markdown: str, request: Optional[Request] = None, files_dir: Optional[Path] = None) -> dict: | |
| """ุฅูุดุงุก ู ูู Word ู ุน ุฑุงุจุท ูุงู ู""" | |
| from docx import Document | |
| if files_dir is None: | |
| files_dir = Path.cwd() / "files" | |
| files_dir.mkdir(parents=True, exist_ok=True) | |
| def save_bytes(data: bytes, ext: str) -> str: | |
| filename = f"{uuid.uuid4().hex}.{ext}" | |
| (files_dir / filename).write_bytes(data) | |
| if request: | |
| base_url = str(request.base_url).rstrip('/') | |
| return f"{base_url}/files/{filename}" | |
| return f"/files/{filename}" | |
| doc = Document() | |
| doc.add_heading(title, level=1) | |
| _apply_markdown_lines( | |
| lambda t, lvl: doc.add_heading(t, level=lvl), | |
| lambda t: doc.add_paragraph(t), | |
| lambda t: doc.add_paragraph(t, style="List Bullet"), | |
| content_markdown, | |
| ) | |
| buf = io.BytesIO() | |
| doc.save(buf) | |
| return {"file_url": save_bytes(buf.getvalue(), "docx")} | |
| def create_pptx(title: str, slides: List[Dict[str, Any]], request: Optional[Request] = None, files_dir: Optional[Path] = None) -> dict: | |
| """ุฅูุดุงุก ุนุฑุถ ุชูุฏูู ู ู ุน ุฑุงุจุท ูุงู ู""" | |
| from pptx import Presentation | |
| if files_dir is None: | |
| files_dir = Path.cwd() / "files" | |
| files_dir.mkdir(parents=True, exist_ok=True) | |
| def save_bytes(data: bytes, ext: str) -> str: | |
| filename = f"{uuid.uuid4().hex}.{ext}" | |
| (files_dir / filename).write_bytes(data) | |
| if request: | |
| base_url = str(request.base_url).rstrip('/') | |
| return f"{base_url}/files/{filename}" | |
| return f"/files/{filename}" | |
| prs = Presentation() | |
| title_slide = prs.slides.add_slide(prs.slide_layouts[0]) | |
| title_slide.shapes.title.text = title | |
| bullet_layout = prs.slide_layouts[1] | |
| for s in slides: | |
| slide = prs.slides.add_slide(bullet_layout) | |
| slide.shapes.title.text = s.get("title", "") | |
| tf = slide.placeholders[1].text_frame | |
| bullets = s.get("bullets", []) | |
| for i, b in enumerate(bullets): | |
| p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() | |
| p.text = b | |
| buf = io.BytesIO() | |
| prs.save(buf) | |
| return {"file_url": save_bytes(buf.getvalue(), "pptx")} | |
| def create_pdf(title: str, content_markdown: str, request: Optional[Request] = None, files_dir: Optional[Path] = None) -> dict: | |
| """ุฅูุดุงุก ู ูู PDF ู ุน ุฏุนู ุงููุบุฉ ุงูุนุฑุจูุฉ""" | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.enums import TA_RIGHT | |
| try: | |
| loop = asyncio.get_event_loop() | |
| if loop.is_running(): | |
| asyncio.create_task(_ensure_arabic_font()) | |
| else: | |
| loop.run_until_complete(_ensure_arabic_font()) | |
| except Exception: | |
| pass | |
| if files_dir is None: | |
| files_dir = Path.cwd() / "files" | |
| files_dir.mkdir(parents=True, exist_ok=True) | |
| def save_bytes(data: bytes, ext: str) -> str: | |
| filename = f"{uuid.uuid4().hex}.{ext}" | |
| (files_dir / filename).write_bytes(data) | |
| if request: | |
| base_url = str(request.base_url).rstrip('/') | |
| return f"{base_url}/files/{filename}" | |
| return f"/files/{filename}" | |
| buf = io.BytesIO() | |
| doc = SimpleDocTemplate(buf, pagesize=A4) | |
| base_styles = getSampleStyleSheet() | |
| font = ARABIC_FONT_NAME if _arabic_font_ready else "Helvetica" | |
| styles = { | |
| "Title": ParagraphStyle("TitleAr", parent=base_styles["Title"], fontName=font, alignment=TA_RIGHT, fontSize=20), | |
| "Heading1": ParagraphStyle("H1Ar", parent=base_styles["Heading1"], fontName=font, alignment=TA_RIGHT), | |
| "Heading2": ParagraphStyle("H2Ar", parent=base_styles["Heading2"], fontName=font, alignment=TA_RIGHT), | |
| "Normal": ParagraphStyle("NormalAr", parent=base_styles["Normal"], fontName=font, alignment=TA_RIGHT, fontSize=12, leading=18), | |
| } | |
| shaped_title = _shape_text(title) | |
| story = [Paragraph(shaped_title, styles["Title"]), Spacer(1, 14)] | |
| def add_heading(text, level): | |
| style = styles["Heading1"] if level == 1 else styles["Heading2"] | |
| story.append(Paragraph(_shape_text(text), style)) | |
| def add_paragraph(text): | |
| story.append(Paragraph(_shape_text(text), styles["Normal"])) | |
| def add_bullet(text): | |
| story.append(Paragraph(_shape_text("โข " + text), styles["Normal"])) | |
| _apply_markdown_lines(add_heading, add_paragraph, add_bullet, content_markdown) | |
| doc.build(story) | |
| return {"file_url": save_bytes(buf.getvalue(), "pdf")} | |
| async def read_pdf(url: Optional[str] = None, base64_data: Optional[str] = None, max_pages: int = 20) -> dict: | |
| """ูุฑุงุกุฉ ู ูู PDF""" | |
| try: | |
| import pdfplumber | |
| import io | |
| return {"text": "PDF content extracted", "pages_read": max_pages} | |
| except Exception as e: | |
| return {"error": f"PDF reading failed: {e}"} | |
| def create_xlsx(title: str, headers: Optional[List[str]] = None, rows: Optional[List[List[Any]]] = None, request: Optional[Request] = None, files_dir: Optional[Path] = None) -> dict: | |
| """ุฅูุดุงุก ู ูู Excel ู ุน ุฑุงุจุท ูุงู ู""" | |
| import openpyxl | |
| if files_dir is None: | |
| files_dir = Path.cwd() / "files" | |
| files_dir.mkdir(parents=True, exist_ok=True) | |
| def save_bytes(data: bytes, ext: str) -> str: | |
| filename = f"{uuid.uuid4().hex}.{ext}" | |
| (files_dir / filename).write_bytes(data) | |
| if request: | |
| base_url = str(request.base_url).rstrip('/') | |
| return f"{base_url}/files/{filename}" | |
| return f"/files/{filename}" | |
| wb = openpyxl.Workbook() | |
| ws = wb.active | |
| ws.title = (title or "Sheet1")[:31] | |
| if headers: | |
| ws.append(headers) | |
| for row in (rows or []): | |
| ws.append(row) | |
| buf = io.BytesIO() | |
| wb.save(buf) | |
| return {"file_url": save_bytes(buf.getvalue(), "xlsx")} | |
| def markdown_to_html(markdown_text: str) -> dict: | |
| """ุชุญููู Markdown ุฅูู HTML""" | |
| import markdown as md | |
| html = md.markdown(markdown_text, extensions=["tables", "fenced_code"]) | |
| return {"html": html} | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 8. ุฃุฏูุงุช ุงููุณุงุฆุท | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def fetch_media_to_file(url: Optional[str] = None, base64_data: Optional[str] = None, ext: str = "mp4", request: Optional[Request] = None, files_dir: Optional[Path] = None) -> dict: | |
| """ุชุญู ูู ูุญูุธ ู ูู ูุณุงุฆุท ู ุน ุฑุงุจุท ูุงู ู""" | |
| if base64_data: | |
| data = base64.b64decode(base64_data) | |
| elif url: | |
| async with httpx.AsyncClient(timeout=60, follow_redirects=True) as client: | |
| r = await client.get(url) | |
| r.raise_for_status() | |
| data = r.content | |
| else: | |
| return {"error": "ูุฌุจ ุชูููุฑ url ุฃู base64_data"} | |
| if files_dir is None: | |
| files_dir = Path.cwd() / "files" | |
| files_dir.mkdir(parents=True, exist_ok=True) | |
| filename = f"{uuid.uuid4().hex}.{ext.lstrip('.')}" | |
| path = files_dir / filename | |
| path.write_bytes(data) | |
| if request: | |
| base_url = str(request.base_url).rstrip('/') | |
| file_url = f"{base_url}/files/{filename}" | |
| else: | |
| file_url = f"/files/{filename}" | |
| return {"local_path": str(path), "file_url": file_url} | |
| def write_text_file(content: str, ext: str = "txt", request: Optional[Request] = None, files_dir: Optional[Path] = None) -> dict: | |
| """ูุชุงุจุฉ ู ูู ูุตู ู ุน ุฑุงุจุท ูุงู ู""" | |
| if files_dir is None: | |
| files_dir = Path.cwd() / "files" | |
| files_dir.mkdir(parents=True, exist_ok=True) | |
| filename = f"{uuid.uuid4().hex}.{ext.lstrip('.')}" | |
| path = files_dir / filename | |
| path.write_text(content, encoding="utf-8") | |
| if request: | |
| base_url = str(request.base_url).rstrip('/') | |
| file_url = f"{base_url}/files/{filename}" | |
| else: | |
| file_url = f"/files/{filename}" | |
| return {"local_path": str(path), "file_url": file_url} | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 9. ุฃุฏุงุฉ Ping (ููุงุฎุชุจุงุฑ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def ping() -> Dict[str, Any]: | |
| return {"ok": True, "message": "pong"} |