111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
import asyncio
|
|
from typing import Any
|
|
|
|
from googleapiclient.discovery import build
|
|
|
|
from app.google.auth import GoogleAuthError, GoogleAuthManager
|
|
from app.tools.base import Tool
|
|
|
|
|
|
class GmailTool(Tool):
|
|
name = "gmail"
|
|
description = "List and search Gmail messages for the connected Google account."
|
|
|
|
def __init__(self, auth_manager: GoogleAuthManager) -> None:
|
|
self.auth_manager = auth_manager
|
|
|
|
def parameters_schema(self) -> dict[str, Any]:
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"query": {
|
|
"type": "string",
|
|
"description": "Optional Gmail search query such as from:someone newer_than:7d.",
|
|
},
|
|
"max_results": {
|
|
"type": "integer",
|
|
"description": "Maximum number of messages to return, from 1 to 20.",
|
|
"minimum": 1,
|
|
"maximum": 20,
|
|
},
|
|
"label_ids": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "Optional Gmail label filters. Defaults to INBOX.",
|
|
},
|
|
},
|
|
"additionalProperties": False,
|
|
}
|
|
|
|
async def run(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
query = str(payload.get("query", "")).strip()
|
|
max_results = max(1, min(20, int(payload.get("max_results", 10) or 10)))
|
|
label_ids = payload.get("label_ids")
|
|
if not isinstance(label_ids, list) or not label_ids:
|
|
label_ids = ["INBOX"]
|
|
|
|
try:
|
|
creds = await self.auth_manager.get_credentials()
|
|
except GoogleAuthError as exc:
|
|
return {"tool": self.name, "status": "error", "message": str(exc)}
|
|
|
|
return await asyncio.to_thread(
|
|
self._list_messages,
|
|
creds,
|
|
query,
|
|
max_results,
|
|
[str(label) for label in label_ids],
|
|
)
|
|
|
|
def _list_messages(
|
|
self,
|
|
credentials: Any,
|
|
query: str,
|
|
max_results: int,
|
|
label_ids: list[str],
|
|
) -> dict[str, Any]:
|
|
service = build("gmail", "v1", credentials=credentials, cache_discovery=False)
|
|
response = (
|
|
service.users()
|
|
.messages()
|
|
.list(userId="me", q=query or None, labelIds=label_ids, maxResults=max_results)
|
|
.execute()
|
|
)
|
|
message_refs = response.get("messages", [])
|
|
messages = []
|
|
for item in message_refs:
|
|
detail = (
|
|
service.users()
|
|
.messages()
|
|
.get(
|
|
userId="me",
|
|
id=item["id"],
|
|
format="metadata",
|
|
metadataHeaders=["From", "Subject", "Date"],
|
|
)
|
|
.execute()
|
|
)
|
|
headers = {
|
|
header.get("name", "").lower(): header.get("value", "")
|
|
for header in detail.get("payload", {}).get("headers", [])
|
|
}
|
|
messages.append(
|
|
{
|
|
"id": detail.get("id", ""),
|
|
"thread_id": detail.get("threadId", ""),
|
|
"from": headers.get("from", ""),
|
|
"subject": headers.get("subject", "(no subject)"),
|
|
"date": headers.get("date", ""),
|
|
"snippet": detail.get("snippet", ""),
|
|
"label_ids": detail.get("labelIds", []),
|
|
}
|
|
)
|
|
|
|
return {
|
|
"tool": self.name,
|
|
"status": "ok",
|
|
"query": query,
|
|
"count": len(messages),
|
|
"messages": messages,
|
|
}
|