universal-mcp-applications 0.1.22__py3-none-any.whl → 0.1.39rc8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of universal-mcp-applications might be problematic. Click here for more details.
- universal_mcp/applications/ahrefs/app.py +92 -238
- universal_mcp/applications/airtable/app.py +23 -122
- universal_mcp/applications/apollo/app.py +122 -475
- universal_mcp/applications/asana/app.py +605 -1755
- universal_mcp/applications/aws_s3/app.py +36 -103
- universal_mcp/applications/bill/app.py +644 -2055
- universal_mcp/applications/box/app.py +1246 -4159
- universal_mcp/applications/braze/app.py +410 -1476
- universal_mcp/applications/browser_use/README.md +15 -1
- universal_mcp/applications/browser_use/__init__.py +1 -0
- universal_mcp/applications/browser_use/app.py +94 -37
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +103 -242
- universal_mcp/applications/canva/app.py +75 -140
- universal_mcp/applications/clickup/app.py +331 -798
- universal_mcp/applications/coda/app.py +240 -520
- universal_mcp/applications/confluence/app.py +497 -1285
- universal_mcp/applications/contentful/app.py +36 -151
- universal_mcp/applications/crustdata/app.py +42 -121
- universal_mcp/applications/dialpad/app.py +451 -924
- universal_mcp/applications/digitalocean/app.py +2071 -6082
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +14 -64
- universal_mcp/applications/elevenlabs/app.py +9 -47
- universal_mcp/applications/exa/README.md +8 -4
- universal_mcp/applications/exa/app.py +408 -186
- universal_mcp/applications/falai/app.py +24 -101
- universal_mcp/applications/figma/app.py +91 -175
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +186 -163
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +92 -529
- universal_mcp/applications/fpl/utils/fixtures.py +15 -49
- universal_mcp/applications/fpl/utils/helper.py +25 -89
- universal_mcp/applications/fpl/utils/league_utils.py +20 -64
- universal_mcp/applications/ghost_content/app.py +66 -175
- universal_mcp/applications/github/app.py +28 -65
- universal_mcp/applications/gong/app.py +140 -300
- universal_mcp/applications/google_calendar/app.py +26 -78
- universal_mcp/applications/google_docs/app.py +324 -354
- universal_mcp/applications/google_drive/app.py +194 -793
- universal_mcp/applications/google_gemini/app.py +29 -64
- universal_mcp/applications/google_mail/README.md +1 -0
- universal_mcp/applications/google_mail/app.py +93 -214
- universal_mcp/applications/google_searchconsole/app.py +25 -58
- universal_mcp/applications/google_sheet/app.py +174 -623
- universal_mcp/applications/google_sheet/helper.py +26 -53
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/app.py +77 -155
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/README.md +1 -1
- universal_mcp/applications/hubspot/app.py +7508 -99
- universal_mcp/applications/jira/app.py +2419 -8334
- universal_mcp/applications/klaviyo/app.py +737 -1619
- universal_mcp/applications/linkedin/README.md +23 -4
- universal_mcp/applications/linkedin/app.py +861 -155
- universal_mcp/applications/mailchimp/app.py +696 -1851
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +333 -815
- universal_mcp/applications/ms_teams/app.py +85 -207
- universal_mcp/applications/neon/app.py +144 -250
- universal_mcp/applications/notion/app.py +36 -51
- universal_mcp/applications/onedrive/README.md +24 -0
- universal_mcp/applications/onedrive/__init__.py +1 -0
- universal_mcp/applications/onedrive/app.py +316 -0
- universal_mcp/applications/openai/app.py +42 -165
- universal_mcp/applications/outlook/README.md +22 -9
- universal_mcp/applications/outlook/app.py +606 -262
- universal_mcp/applications/perplexity/README.md +2 -1
- universal_mcp/applications/perplexity/app.py +162 -20
- universal_mcp/applications/pipedrive/app.py +1021 -3331
- universal_mcp/applications/posthog/app.py +272 -541
- universal_mcp/applications/reddit/app.py +88 -204
- universal_mcp/applications/resend/app.py +41 -107
- universal_mcp/applications/retell/app.py +23 -50
- universal_mcp/applications/rocketlane/app.py +250 -963
- universal_mcp/applications/scraper/README.md +7 -4
- universal_mcp/applications/scraper/app.py +245 -283
- universal_mcp/applications/semanticscholar/app.py +36 -78
- universal_mcp/applications/semrush/app.py +43 -77
- universal_mcp/applications/sendgrid/app.py +826 -1576
- universal_mcp/applications/sentry/app.py +444 -1079
- universal_mcp/applications/serpapi/app.py +40 -143
- universal_mcp/applications/sharepoint/README.md +16 -14
- universal_mcp/applications/sharepoint/app.py +245 -154
- universal_mcp/applications/shopify/app.py +1743 -4479
- universal_mcp/applications/shortcut/app.py +272 -534
- universal_mcp/applications/slack/app.py +58 -109
- universal_mcp/applications/spotify/app.py +206 -405
- universal_mcp/applications/supabase/app.py +174 -283
- universal_mcp/applications/tavily/app.py +2 -2
- universal_mcp/applications/trello/app.py +853 -2816
- universal_mcp/applications/twilio/app.py +14 -50
- universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
- universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
- universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
- universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
- universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
- universal_mcp/applications/whatsapp/app.py +35 -186
- universal_mcp/applications/whatsapp/audio.py +2 -6
- universal_mcp/applications/whatsapp/whatsapp.py +17 -51
- universal_mcp/applications/whatsapp_business/app.py +86 -299
- universal_mcp/applications/wrike/app.py +80 -153
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +120 -306
- universal_mcp/applications/zenquotes/app.py +4 -4
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/METADATA +4 -2
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/RECORD +113 -117
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/WHEEL +1 -1
- universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
- universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
- universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
- universal_mcp/applications/unipile/README.md +0 -28
- universal_mcp/applications/unipile/__init__.py +0 -1
- universal_mcp/applications/unipile/app.py +0 -1077
- {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
import
|
|
2
|
+
import asyncio
|
|
3
3
|
from email.message import EmailMessage
|
|
4
4
|
from typing import Any
|
|
5
|
-
|
|
6
5
|
from loguru import logger
|
|
7
6
|
from universal_mcp.applications.application import APIApplication
|
|
8
7
|
from universal_mcp.integrations import Integration
|
|
@@ -14,14 +13,7 @@ class GoogleMailApp(APIApplication):
|
|
|
14
13
|
self.base_api_url = "https://gmail.googleapis.com/gmail/v1/users/me"
|
|
15
14
|
self.base_url = "https://gmail.googleapis.com"
|
|
16
15
|
|
|
17
|
-
def send_email(
|
|
18
|
-
self,
|
|
19
|
-
to: str,
|
|
20
|
-
subject: str,
|
|
21
|
-
body: str,
|
|
22
|
-
body_type: str = "plain",
|
|
23
|
-
thread_id: str | None = None,
|
|
24
|
-
) -> dict[str, Any]:
|
|
16
|
+
async def send_email(self, to: str, subject: str, body: str, body_type: str = "plain", thread_id: str | None = None) -> dict[str, Any]:
|
|
25
17
|
"""
|
|
26
18
|
Composes and immediately sends an email message via the Gmail API. It can function as a reply within an existing conversation if a `thread_id` is provided. This action is distinct from `send_draft`, which sends a previously saved draft message, or `create_draft`, which only saves an email.
|
|
27
19
|
|
|
@@ -43,17 +35,12 @@ class GoogleMailApp(APIApplication):
|
|
|
43
35
|
Tags:
|
|
44
36
|
send, email, api, communication, important, thread, reply, openWorldHint
|
|
45
37
|
"""
|
|
46
|
-
|
|
47
38
|
url = f"{self.base_api_url}/messages/send"
|
|
48
39
|
raw_message = self._create_message(to, subject, body, body_type)
|
|
49
40
|
email_data = {"raw": raw_message}
|
|
50
|
-
|
|
51
|
-
# Add threadId to make it a proper reply if thread_id is provided
|
|
52
41
|
if thread_id:
|
|
53
42
|
email_data["threadId"] = thread_id
|
|
54
|
-
|
|
55
|
-
response = self._post(url, email_data)
|
|
56
|
-
|
|
43
|
+
response = await self._apost(url, email_data)
|
|
57
44
|
return self._handle_response(response)
|
|
58
45
|
|
|
59
46
|
def _create_message(self, to, subject, body, body_type="plain"):
|
|
@@ -69,13 +56,8 @@ class GoogleMailApp(APIApplication):
|
|
|
69
56
|
logger.error(f"Error creating message: {str(e)}")
|
|
70
57
|
raise
|
|
71
58
|
|
|
72
|
-
def create_draft(
|
|
73
|
-
self,
|
|
74
|
-
to: str,
|
|
75
|
-
subject: str,
|
|
76
|
-
body: str,
|
|
77
|
-
body_type: str = "plain",
|
|
78
|
-
thread_id: str | None = None,
|
|
59
|
+
async def create_draft(
|
|
60
|
+
self, to: str, subject: str, body: str, body_type: str = "plain", thread_id: str | None = None
|
|
79
61
|
) -> dict[str, Any]:
|
|
80
62
|
"""
|
|
81
63
|
Saves a new email draft in Gmail with a specified recipient, subject, and body. An optional thread ID can create the draft as a reply within an existing conversation, distinguishing it from `send_email`, which sends immediately.
|
|
@@ -98,24 +80,16 @@ class GoogleMailApp(APIApplication):
|
|
|
98
80
|
Tags:
|
|
99
81
|
create, email, draft, gmail, api, important, thread, reply, html
|
|
100
82
|
"""
|
|
101
|
-
|
|
102
83
|
url = f"{self.base_api_url}/drafts"
|
|
103
|
-
|
|
104
84
|
raw_message = self._create_message(to, subject, body, body_type)
|
|
105
|
-
|
|
106
85
|
draft_data = {"message": {"raw": raw_message}}
|
|
107
|
-
|
|
108
|
-
# Add threadId to make it a proper reply if thread_id is provided
|
|
109
86
|
if thread_id:
|
|
110
87
|
draft_data["message"]["threadId"] = thread_id
|
|
111
|
-
|
|
112
88
|
logger.info(f"Creating draft email to {to}")
|
|
113
|
-
|
|
114
|
-
response = self._post(url, draft_data)
|
|
115
|
-
|
|
89
|
+
response = await self._apost(url, draft_data)
|
|
116
90
|
return self._handle_response(response)
|
|
117
91
|
|
|
118
|
-
def send_draft(self, draft_id: str) -> dict[str, Any]:
|
|
92
|
+
async def send_draft(self, draft_id: str) -> dict[str, Any]:
|
|
119
93
|
"""
|
|
120
94
|
Sends a pre-existing Gmail draft identified by its unique ID. It posts to the `/drafts/send` endpoint, converting a saved draft into a sent message. This function acts on drafts from `create_draft` and differs from `send_email`, which composes and sends an email in one step.
|
|
121
95
|
|
|
@@ -133,18 +107,13 @@ class GoogleMailApp(APIApplication):
|
|
|
133
107
|
Tags:
|
|
134
108
|
send, email, api, communication, important, draft
|
|
135
109
|
"""
|
|
136
|
-
|
|
137
110
|
url = f"{self.base_api_url}/drafts/send"
|
|
138
|
-
|
|
139
111
|
draft_data = {"id": draft_id}
|
|
140
|
-
|
|
141
112
|
logger.info(f"Sending draft email with ID: {draft_id}")
|
|
142
|
-
|
|
143
|
-
response = self._post(url, draft_data)
|
|
144
|
-
|
|
113
|
+
response = await self._apost(url, draft_data)
|
|
145
114
|
return self._handle_response(response)
|
|
146
115
|
|
|
147
|
-
def get_draft(self, draft_id: str, format: str = "full") -> dict[str, Any]:
|
|
116
|
+
async def get_draft(self, draft_id: str, format: str = "full") -> dict[str, Any]:
|
|
148
117
|
"""
|
|
149
118
|
Retrieves a specific Gmail draft by its unique ID. This function allows specifying the output format (e.g., full, raw) to control the response detail. Unlike `list_drafts`, it fetches a single, known draft rather than a collection of multiple drafts.
|
|
150
119
|
|
|
@@ -163,24 +132,13 @@ class GoogleMailApp(APIApplication):
|
|
|
163
132
|
Tags:
|
|
164
133
|
retrieve, email, gmail, draft, api, format, important
|
|
165
134
|
"""
|
|
166
|
-
|
|
167
135
|
url = f"{self.base_api_url}/drafts/{draft_id}"
|
|
168
|
-
|
|
169
|
-
# Add format parameter as query param
|
|
170
136
|
params = {"format": format}
|
|
171
|
-
|
|
172
137
|
logger.info(f"Retrieving draft with ID: {draft_id}")
|
|
173
|
-
|
|
174
|
-
response = self._get(url, params=params)
|
|
175
|
-
|
|
138
|
+
response = await self._aget(url, params=params)
|
|
176
139
|
return self._handle_response(response)
|
|
177
140
|
|
|
178
|
-
def list_drafts(
|
|
179
|
-
self,
|
|
180
|
-
max_results: int = 20,
|
|
181
|
-
q: str | None = None,
|
|
182
|
-
include_spam_trash: bool = False,
|
|
183
|
-
) -> dict[str, Any]:
|
|
141
|
+
async def list_drafts(self, max_results: int = 20, q: str | None = None, include_spam_trash: bool = False) -> dict[str, Any]:
|
|
184
142
|
"""
|
|
185
143
|
Fetches a list of email drafts, allowing filtering by a search query and limiting results. It can optionally include drafts from spam and trash, returning a collection of draft objects. This is distinct from `get_draft`, which retrieves only a single, specific draft by its ID.
|
|
186
144
|
|
|
@@ -200,25 +158,17 @@ class GoogleMailApp(APIApplication):
|
|
|
200
158
|
Tags:
|
|
201
159
|
list, email, drafts, gmail, api, search, query, pagination, important
|
|
202
160
|
"""
|
|
203
|
-
|
|
204
161
|
url = f"{self.base_api_url}/drafts"
|
|
205
|
-
|
|
206
|
-
# Build query parameters
|
|
207
162
|
params: dict[str, Any] = {"maxResults": max_results}
|
|
208
|
-
|
|
209
163
|
if q:
|
|
210
164
|
params["q"] = q
|
|
211
|
-
|
|
212
165
|
if include_spam_trash:
|
|
213
166
|
params["includeSpamTrash"] = "true"
|
|
214
|
-
|
|
215
167
|
logger.info(f"Retrieving drafts list with params: {params}")
|
|
216
|
-
|
|
217
|
-
response = self._get(url, params=params)
|
|
218
|
-
|
|
168
|
+
response = await self._aget(url, params=params)
|
|
219
169
|
return self._handle_response(response)
|
|
220
170
|
|
|
221
|
-
def get_message_details(self, message_id: str) -> dict[str, Any]:
|
|
171
|
+
async def get_message_details(self, message_id: str) -> dict[str, Any]:
|
|
222
172
|
"""
|
|
223
173
|
Retrieves a specific email from Gmail by its ID. It parses the API response to extract and format key details—including sender, subject, body, and attachments—into a structured dictionary. This function provides detailed data for a single message, distinguishing it from `list_messages` which fetches multiple messages.
|
|
224
174
|
|
|
@@ -226,40 +176,42 @@ class GoogleMailApp(APIApplication):
|
|
|
226
176
|
message_id: The unique identifier of the Gmail message to retrieve
|
|
227
177
|
|
|
228
178
|
Returns:
|
|
229
|
-
A dictionary containing the
|
|
230
|
-
|
|
179
|
+
A dictionary containing the message details with the following keys:
|
|
180
|
+
- message_id (str): The unique ID of the email.
|
|
181
|
+
- from (str): The sender's email address or "Unknown sender" if unavailable.
|
|
182
|
+
- to (str): The recipient(s) email address(es) or "Unknown recipient" if unavailable.
|
|
183
|
+
- date (str): The date the email was sent or "Unknown date" if unavailable.
|
|
184
|
+
- subject (str): The subject of the email or "No subject" if unavailable.
|
|
185
|
+
- body (str): The plain text content of the email or a preview/snippet.
|
|
186
|
+
- thread_id (str | None): The ID of the email thread this message belongs to.
|
|
187
|
+
- attachments (list[dict]): A list of attachments, each represented as a dictionary
|
|
188
|
+
with metadata (e.g., filename, MIME type, attachment ID).
|
|
189
|
+
|
|
231
190
|
Tags:
|
|
232
191
|
retrieve, email, format, api, gmail, message, important, body, content, attachments
|
|
233
192
|
"""
|
|
234
193
|
url = f"{self.base_api_url}/messages/{message_id}"
|
|
235
|
-
response = self.
|
|
194
|
+
response = await self._aget(url)
|
|
236
195
|
raw_data = self._handle_response(response)
|
|
237
|
-
|
|
238
|
-
# Extract headers
|
|
239
196
|
headers = {}
|
|
240
197
|
for header in raw_data.get("payload", {}).get("headers", []):
|
|
241
198
|
name = header.get("name", "")
|
|
242
199
|
value = header.get("value", "")
|
|
243
200
|
headers[name] = value
|
|
244
|
-
|
|
245
|
-
# Extract body content
|
|
246
201
|
body_content = self._extract_email_body(raw_data.get("payload", {}))
|
|
247
202
|
if not body_content:
|
|
248
203
|
if "snippet" in raw_data:
|
|
249
204
|
body_content = f"Preview: {raw_data['snippet']}"
|
|
250
205
|
else:
|
|
251
206
|
body_content = "No content available"
|
|
252
|
-
|
|
253
|
-
# Extract attachments
|
|
254
207
|
attachments = self._extract_attachments(raw_data.get("payload", {}))
|
|
255
|
-
|
|
256
208
|
return {
|
|
257
209
|
"message_id": message_id,
|
|
258
|
-
"
|
|
210
|
+
"from": headers.get("From", "Unknown sender"),
|
|
259
211
|
"to": headers.get("To", "Unknown recipient"),
|
|
260
212
|
"date": headers.get("Date", "Unknown date"),
|
|
261
213
|
"subject": headers.get("Subject", "No subject"),
|
|
262
|
-
"
|
|
214
|
+
"body": body_content,
|
|
263
215
|
"thread_id": raw_data.get("threadId"),
|
|
264
216
|
"attachments": attachments,
|
|
265
217
|
}
|
|
@@ -275,45 +227,30 @@ class GoogleMailApp(APIApplication):
|
|
|
275
227
|
str: The email body content (plain text preferred, HTML as fallback)
|
|
276
228
|
"""
|
|
277
229
|
try:
|
|
278
|
-
# Handle single part message
|
|
279
230
|
if payload.get("body") and payload.get("body", {}).get("data"):
|
|
280
231
|
return self._decode_base64(payload["body"]["data"])
|
|
281
|
-
|
|
282
|
-
# Handle multipart message
|
|
283
232
|
parts = payload.get("parts", [])
|
|
284
233
|
if not parts:
|
|
285
234
|
return ""
|
|
286
|
-
|
|
287
235
|
plain_text_body = ""
|
|
288
236
|
html_body = ""
|
|
289
|
-
|
|
290
237
|
for part in parts:
|
|
291
238
|
mime_type = part.get("mimeType", "")
|
|
292
|
-
|
|
293
|
-
# Extract plain text
|
|
294
239
|
if mime_type == "text/plain":
|
|
295
240
|
if part.get("body") and part.get("body", {}).get("data"):
|
|
296
241
|
plain_text_body = self._decode_base64(part["body"]["data"])
|
|
297
|
-
|
|
298
|
-
# Extract HTML content
|
|
299
242
|
elif mime_type == "text/html":
|
|
300
243
|
if part.get("body") and part.get("body", {}).get("data"):
|
|
301
244
|
html_body = self._decode_base64(part["body"]["data"])
|
|
302
|
-
|
|
303
|
-
# Handle nested multipart (recursive)
|
|
304
245
|
elif mime_type.startswith("multipart/") and part.get("parts"):
|
|
305
246
|
nested_body = self._extract_email_body(part)
|
|
306
|
-
if nested_body and not plain_text_body:
|
|
247
|
+
if nested_body and (not plain_text_body):
|
|
307
248
|
plain_text_body = nested_body
|
|
308
|
-
|
|
309
|
-
# Prefer plain text, fallback to HTML
|
|
310
249
|
if plain_text_body:
|
|
311
250
|
return plain_text_body
|
|
312
251
|
elif html_body:
|
|
313
252
|
return f"[HTML Content]\n{html_body}"
|
|
314
|
-
|
|
315
253
|
return ""
|
|
316
|
-
|
|
317
254
|
except Exception as e:
|
|
318
255
|
logger.error(f"Error extracting email body: {str(e)}")
|
|
319
256
|
return ""
|
|
@@ -329,7 +266,6 @@ class GoogleMailApp(APIApplication):
|
|
|
329
266
|
list: List of attachment dictionaries with attachment_id, filename, mime_type, and size
|
|
330
267
|
"""
|
|
331
268
|
attachments = []
|
|
332
|
-
|
|
333
269
|
try:
|
|
334
270
|
if payload.get("filename") and payload.get("body", {}).get("attachmentId"):
|
|
335
271
|
attachments.append(
|
|
@@ -340,7 +276,6 @@ class GoogleMailApp(APIApplication):
|
|
|
340
276
|
"size": payload.get("body", {}).get("size", 0),
|
|
341
277
|
}
|
|
342
278
|
)
|
|
343
|
-
|
|
344
279
|
parts = payload.get("parts", [])
|
|
345
280
|
for part in parts:
|
|
346
281
|
if part.get("filename") and part.get("body", {}).get("attachmentId"):
|
|
@@ -352,14 +287,11 @@ class GoogleMailApp(APIApplication):
|
|
|
352
287
|
"size": part.get("body", {}).get("size", 0),
|
|
353
288
|
}
|
|
354
289
|
)
|
|
355
|
-
|
|
356
290
|
elif part.get("parts"):
|
|
357
291
|
nested_attachments = self._extract_attachments(part)
|
|
358
292
|
attachments.extend(nested_attachments)
|
|
359
|
-
|
|
360
293
|
except Exception as e:
|
|
361
294
|
logger.error(f"Error extracting attachments: {str(e)}")
|
|
362
|
-
|
|
363
295
|
return attachments
|
|
364
296
|
|
|
365
297
|
def _decode_base64(self, data):
|
|
@@ -373,19 +305,14 @@ class GoogleMailApp(APIApplication):
|
|
|
373
305
|
str: Decoded string content
|
|
374
306
|
"""
|
|
375
307
|
try:
|
|
376
|
-
# Gmail API uses URL-safe base64 encoding
|
|
377
308
|
decoded_bytes = base64.urlsafe_b64decode(data)
|
|
378
309
|
return decoded_bytes.decode("utf-8")
|
|
379
310
|
except Exception as e:
|
|
380
311
|
logger.error(f"Error decoding base64 data: {str(e)}")
|
|
381
312
|
return f"[Unable to decode content: {str(e)}]"
|
|
382
313
|
|
|
383
|
-
def list_messages(
|
|
384
|
-
self,
|
|
385
|
-
max_results: int = 10,
|
|
386
|
-
q: str | None = None,
|
|
387
|
-
include_spam_trash: bool = False,
|
|
388
|
-
page_token: str | None = None,
|
|
314
|
+
async def list_messages(
|
|
315
|
+
self, max_results: int = 10, q: str | None = None, include_spam_trash: bool = False, page_token: str | None = None
|
|
389
316
|
) -> dict[str, Any]:
|
|
390
317
|
"""
|
|
391
318
|
Fetches a paginated list of detailed email messages using optional search queries. It concurrently retrieves full content (sender, subject, body) for each message, returning the results and a pagination token. This differs from `get_message_details`, which fetches only a single message.
|
|
@@ -412,7 +339,17 @@ class GoogleMailApp(APIApplication):
|
|
|
412
339
|
include_spam_trash: Boolean flag to include messages from spam and trash folders (default False)
|
|
413
340
|
|
|
414
341
|
Returns:
|
|
415
|
-
A dictionary
|
|
342
|
+
A dictionary with the following keys:
|
|
343
|
+
- messages (list[dict]): A list of detailed message dictionaries. Each dictionary contains:
|
|
344
|
+
- message_id (str): Unique ID of the email.
|
|
345
|
+
- from (str): Sender's email address or "Unknown sender" if unavailable.
|
|
346
|
+
- to (str): Recipient(s) email address(es) or "Unknown recipient" if unavailable.
|
|
347
|
+
- date (str): Date the email was sent or "Unknown date" if unavailable.
|
|
348
|
+
- subject (str): Subject of the email or "No subject" if unavailable.
|
|
349
|
+
- body (str): Plain text content of the email or a preview/snippet.
|
|
350
|
+
- thread_id (str | None): ID of the email thread this message belongs to.
|
|
351
|
+
- attachments (list[dict]): List of attachments with metadata (e.g., filename, MIME type, attachment ID).
|
|
352
|
+
- next_page_token (str | None): Token to retrieve the next page of messages; None if there are no further pages.
|
|
416
353
|
|
|
417
354
|
Raises:
|
|
418
355
|
NotAuthorizedError: When the Gmail API authentication is invalid or missing
|
|
@@ -423,54 +360,30 @@ class GoogleMailApp(APIApplication):
|
|
|
423
360
|
list, messages, gmail, search, query, pagination, important
|
|
424
361
|
"""
|
|
425
362
|
url = f"{self.base_api_url}/messages?format=metadata"
|
|
426
|
-
|
|
427
|
-
# Build query parameters
|
|
428
363
|
params: dict[str, Any] = {"maxResults": max_results}
|
|
429
|
-
|
|
430
364
|
if q:
|
|
431
365
|
params["q"] = q
|
|
432
|
-
|
|
433
366
|
if include_spam_trash:
|
|
434
367
|
params["includeSpamTrash"] = "true"
|
|
435
|
-
|
|
436
368
|
if page_token:
|
|
437
369
|
params["pageToken"] = page_token
|
|
438
|
-
|
|
439
370
|
logger.info(f"Retrieving messages list with params: {params}")
|
|
440
|
-
|
|
441
|
-
response = self._get(url, params=params)
|
|
371
|
+
response = await self._aget(url, params=params)
|
|
442
372
|
data = self._handle_response(response)
|
|
443
|
-
|
|
444
|
-
# Extract message IDs
|
|
445
373
|
messages = data.get("messages", [])
|
|
446
374
|
message_ids = [msg.get("id") for msg in messages if msg.get("id")]
|
|
447
|
-
|
|
448
|
-
# Use ThreadPoolExecutor to get detailed information for each message in parallel
|
|
449
375
|
detailed_messages = []
|
|
450
376
|
if message_ids:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
try:
|
|
462
|
-
result = future.result()
|
|
463
|
-
detailed_messages.append(result)
|
|
464
|
-
except Exception as e:
|
|
465
|
-
logger.error(f"Error retrieving message {message_id}: {str(e)}")
|
|
466
|
-
# Skip failed messages rather than including error strings
|
|
467
|
-
|
|
468
|
-
return {
|
|
469
|
-
"messages": detailed_messages,
|
|
470
|
-
"next_page_token": data.get("nextPageToken"),
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
def get_email_thread(self, thread_id: str) -> dict[str, Any]:
|
|
377
|
+
tasks = [self.get_message_details(message_id) for message_id in message_ids]
|
|
378
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
379
|
+
for i, result in enumerate(results):
|
|
380
|
+
if isinstance(result, Exception):
|
|
381
|
+
logger.error(f"Error retrieving message {message_ids[i]}: {str(result)}")
|
|
382
|
+
else:
|
|
383
|
+
detailed_messages.append(result)
|
|
384
|
+
return {"messages": detailed_messages, "next_page_token": data.get("nextPageToken")}
|
|
385
|
+
|
|
386
|
+
async def get_email_thread(self, thread_id: str) -> dict[str, Any]:
|
|
474
387
|
"""
|
|
475
388
|
Fetches a complete email conversation by its unique thread ID. Unlike `get_message_details`, which retrieves a single message, this returns all messages and metadata for the entire thread, providing the full context of the conversation.
|
|
476
389
|
|
|
@@ -490,10 +403,10 @@ class GoogleMailApp(APIApplication):
|
|
|
490
403
|
"""
|
|
491
404
|
url = f"{self.base_api_url}/threads/{thread_id}"
|
|
492
405
|
logger.info(f"Retrieving thread {thread_id}")
|
|
493
|
-
response = self.
|
|
406
|
+
response = await self._aget(url)
|
|
494
407
|
return self._handle_response(response)
|
|
495
408
|
|
|
496
|
-
def list_labels(self) -> dict[str, Any]:
|
|
409
|
+
async def list_labels(self) -> dict[str, Any]:
|
|
497
410
|
"""
|
|
498
411
|
Fetches a complete list of all available labels from the user's Gmail account via the API. It retrieves both system-defined (e.g., INBOX) and user-created labels, returning their names and IDs, complementing management functions like `create_label` and `update_label`.
|
|
499
412
|
|
|
@@ -510,16 +423,12 @@ class GoogleMailApp(APIApplication):
|
|
|
510
423
|
Tags:
|
|
511
424
|
list, gmail, labels, fetch, organize, important, management
|
|
512
425
|
"""
|
|
513
|
-
|
|
514
426
|
url = f"{self.base_api_url}/labels"
|
|
515
|
-
|
|
516
427
|
logger.info("Retrieving Gmail labels")
|
|
517
|
-
|
|
518
|
-
response = self._get(url)
|
|
519
|
-
|
|
428
|
+
response = await self._aget(url)
|
|
520
429
|
return self._handle_response(response)
|
|
521
430
|
|
|
522
|
-
def create_label(self, name: str) -> dict[str, Any]:
|
|
431
|
+
async def create_label(self, name: str) -> dict[str, Any]:
|
|
523
432
|
"""
|
|
524
433
|
Creates a new Gmail label with a specified name, hardcoding its visibility to ensure it appears in both label and message lists. This function complements `update_label` and `delete_label` by adding new organizational tags to the user's account via the API.
|
|
525
434
|
|
|
@@ -536,23 +445,13 @@ class GoogleMailApp(APIApplication):
|
|
|
536
445
|
Tags:
|
|
537
446
|
create, label, gmail, management, important
|
|
538
447
|
"""
|
|
539
|
-
|
|
540
448
|
url = f"{self.base_api_url}/labels"
|
|
541
|
-
|
|
542
|
-
# Create the label data with just the essential fields
|
|
543
|
-
label_data = {
|
|
544
|
-
"name": name,
|
|
545
|
-
"labelListVisibility": "labelShow", # Show in label list
|
|
546
|
-
"messageListVisibility": "show", # Show in message list
|
|
547
|
-
}
|
|
548
|
-
|
|
449
|
+
label_data = {"name": name, "labelListVisibility": "labelShow", "messageListVisibility": "show"}
|
|
549
450
|
logger.info(f"Creating new Gmail label: {name}")
|
|
550
|
-
|
|
551
|
-
response = self._post(url, label_data)
|
|
552
|
-
|
|
451
|
+
response = await self._apost(url, label_data)
|
|
553
452
|
return self._handle_response(response)
|
|
554
453
|
|
|
555
|
-
def get_profile(self) -> dict[str, Any]:
|
|
454
|
+
async def get_profile(self) -> dict[str, Any]:
|
|
556
455
|
"""
|
|
557
456
|
Retrieves the authenticated user's Gmail profile from the API. The profile includes the user's email address, total message and thread counts, and the mailbox's history ID, offering a high-level summary of the account's state.
|
|
558
457
|
|
|
@@ -569,15 +468,12 @@ class GoogleMailApp(APIApplication):
|
|
|
569
468
|
Tags:
|
|
570
469
|
fetch, profile, gmail, user-info, api-request, important
|
|
571
470
|
"""
|
|
572
|
-
|
|
573
471
|
url = f"{self.base_api_url}/profile"
|
|
574
|
-
|
|
575
472
|
logger.info("Retrieving Gmail user profile")
|
|
576
|
-
|
|
577
|
-
response = self._get(url)
|
|
473
|
+
response = await self._aget(url)
|
|
578
474
|
return self._handle_response(response)
|
|
579
475
|
|
|
580
|
-
def update_draft(
|
|
476
|
+
async def update_draft(
|
|
581
477
|
self,
|
|
582
478
|
userId,
|
|
583
479
|
id,
|
|
@@ -670,10 +566,7 @@ class GoogleMailApp(APIApplication):
|
|
|
670
566
|
raise ValueError("Missing required parameter 'userId'")
|
|
671
567
|
if id is None:
|
|
672
568
|
raise ValueError("Missing required parameter 'id'")
|
|
673
|
-
request_body = {
|
|
674
|
-
"id": id,
|
|
675
|
-
"message": message,
|
|
676
|
-
}
|
|
569
|
+
request_body = {"id": id, "message": message}
|
|
677
570
|
request_body = {k: v for k, v in request_body.items() if v is not None}
|
|
678
571
|
url = f"{self.base_url}/gmail/v1/users/{userId}/drafts/{id}"
|
|
679
572
|
query_params = {
|
|
@@ -693,11 +586,10 @@ class GoogleMailApp(APIApplication):
|
|
|
693
586
|
]
|
|
694
587
|
if v is not None
|
|
695
588
|
}
|
|
696
|
-
response = self.
|
|
697
|
-
|
|
698
|
-
return response.json()
|
|
589
|
+
response = await self._aput(url, data=request_body, params=query_params)
|
|
590
|
+
return self._handle_response(response)
|
|
699
591
|
|
|
700
|
-
def trash_message(
|
|
592
|
+
async def trash_message(
|
|
701
593
|
self,
|
|
702
594
|
userId,
|
|
703
595
|
id,
|
|
@@ -759,11 +651,10 @@ class GoogleMailApp(APIApplication):
|
|
|
759
651
|
]
|
|
760
652
|
if v is not None
|
|
761
653
|
}
|
|
762
|
-
response = self.
|
|
763
|
-
|
|
764
|
-
return response.json()
|
|
654
|
+
response = await self._apost(url, data={}, params=query_params)
|
|
655
|
+
return self._handle_response(response)
|
|
765
656
|
|
|
766
|
-
def untrash_message(
|
|
657
|
+
async def untrash_message(
|
|
767
658
|
self,
|
|
768
659
|
userId,
|
|
769
660
|
id,
|
|
@@ -825,11 +716,10 @@ class GoogleMailApp(APIApplication):
|
|
|
825
716
|
]
|
|
826
717
|
if v is not None
|
|
827
718
|
}
|
|
828
|
-
response = self.
|
|
829
|
-
|
|
830
|
-
return response.json()
|
|
719
|
+
response = await self._apost(url, data={}, params=query_params)
|
|
720
|
+
return self._handle_response(response)
|
|
831
721
|
|
|
832
|
-
def get_attachment(
|
|
722
|
+
async def get_attachment(
|
|
833
723
|
self,
|
|
834
724
|
userId,
|
|
835
725
|
messageId,
|
|
@@ -895,11 +785,10 @@ class GoogleMailApp(APIApplication):
|
|
|
895
785
|
]
|
|
896
786
|
if v is not None
|
|
897
787
|
}
|
|
898
|
-
response = self.
|
|
899
|
-
|
|
900
|
-
return response.json()
|
|
788
|
+
response = await self._aget(url, params=query_params)
|
|
789
|
+
return self._handle_response(response)
|
|
901
790
|
|
|
902
|
-
def update_label(
|
|
791
|
+
async def update_label(
|
|
903
792
|
self,
|
|
904
793
|
userId,
|
|
905
794
|
id,
|
|
@@ -1010,11 +899,10 @@ class GoogleMailApp(APIApplication):
|
|
|
1010
899
|
]
|
|
1011
900
|
if v is not None
|
|
1012
901
|
}
|
|
1013
|
-
response = self.
|
|
1014
|
-
|
|
1015
|
-
return response.json()
|
|
902
|
+
response = await self._aput(url, data=request_body, params=query_params)
|
|
903
|
+
return self._handle_response(response)
|
|
1016
904
|
|
|
1017
|
-
def delete_label(
|
|
905
|
+
async def delete_label(
|
|
1018
906
|
self,
|
|
1019
907
|
userId,
|
|
1020
908
|
id,
|
|
@@ -1076,11 +964,10 @@ class GoogleMailApp(APIApplication):
|
|
|
1076
964
|
]
|
|
1077
965
|
if v is not None
|
|
1078
966
|
}
|
|
1079
|
-
response = self.
|
|
1080
|
-
|
|
1081
|
-
return response.json()
|
|
967
|
+
response = await self._adelete(url, params=query_params)
|
|
968
|
+
return self._handle_response(response)
|
|
1082
969
|
|
|
1083
|
-
def get_filter(
|
|
970
|
+
async def get_filter(
|
|
1084
971
|
self,
|
|
1085
972
|
userId,
|
|
1086
973
|
id,
|
|
@@ -1142,11 +1029,10 @@ class GoogleMailApp(APIApplication):
|
|
|
1142
1029
|
]
|
|
1143
1030
|
if v is not None
|
|
1144
1031
|
}
|
|
1145
|
-
response = self.
|
|
1146
|
-
|
|
1147
|
-
return response.json()
|
|
1032
|
+
response = await self._aget(url, params=query_params)
|
|
1033
|
+
return self._handle_response(response)
|
|
1148
1034
|
|
|
1149
|
-
def delete_filter(
|
|
1035
|
+
async def delete_filter(
|
|
1150
1036
|
self,
|
|
1151
1037
|
userId,
|
|
1152
1038
|
id,
|
|
@@ -1208,11 +1094,10 @@ class GoogleMailApp(APIApplication):
|
|
|
1208
1094
|
]
|
|
1209
1095
|
if v is not None
|
|
1210
1096
|
}
|
|
1211
|
-
response = self.
|
|
1212
|
-
|
|
1213
|
-
return response.json()
|
|
1097
|
+
response = await self._adelete(url, params=query_params)
|
|
1098
|
+
return self._handle_response(response)
|
|
1214
1099
|
|
|
1215
|
-
def get_all_filters(
|
|
1100
|
+
async def get_all_filters(
|
|
1216
1101
|
self,
|
|
1217
1102
|
userId,
|
|
1218
1103
|
access_token=None,
|
|
@@ -1270,11 +1155,10 @@ class GoogleMailApp(APIApplication):
|
|
|
1270
1155
|
]
|
|
1271
1156
|
if v is not None
|
|
1272
1157
|
}
|
|
1273
|
-
response = self.
|
|
1274
|
-
|
|
1275
|
-
return response.json()
|
|
1158
|
+
response = await self._aget(url, params=query_params)
|
|
1159
|
+
return self._handle_response(response)
|
|
1276
1160
|
|
|
1277
|
-
def create_filter(
|
|
1161
|
+
async def create_filter(
|
|
1278
1162
|
self,
|
|
1279
1163
|
userId,
|
|
1280
1164
|
access_token=None,
|
|
@@ -1348,11 +1232,7 @@ class GoogleMailApp(APIApplication):
|
|
|
1348
1232
|
"""
|
|
1349
1233
|
if userId is None:
|
|
1350
1234
|
raise ValueError("Missing required parameter 'userId'")
|
|
1351
|
-
request_body = {
|
|
1352
|
-
"action": action,
|
|
1353
|
-
"criteria": criteria,
|
|
1354
|
-
"id": id,
|
|
1355
|
-
}
|
|
1235
|
+
request_body = {"action": action, "criteria": criteria, "id": id}
|
|
1356
1236
|
request_body = {k: v for k, v in request_body.items() if v is not None}
|
|
1357
1237
|
url = f"{self.base_url}/gmail/v1/users/{userId}/settings/filters"
|
|
1358
1238
|
query_params = {
|
|
@@ -1372,9 +1252,8 @@ class GoogleMailApp(APIApplication):
|
|
|
1372
1252
|
]
|
|
1373
1253
|
if v is not None
|
|
1374
1254
|
}
|
|
1375
|
-
response = self.
|
|
1376
|
-
|
|
1377
|
-
return response.json()
|
|
1255
|
+
response = await self._apost(url, data=request_body, params=query_params)
|
|
1256
|
+
return self._handle_response(response)
|
|
1378
1257
|
|
|
1379
1258
|
def list_tools(self):
|
|
1380
1259
|
return [
|
|
@@ -1385,10 +1264,10 @@ class GoogleMailApp(APIApplication):
|
|
|
1385
1264
|
self.list_drafts,
|
|
1386
1265
|
self.get_message_details,
|
|
1387
1266
|
self.list_messages,
|
|
1267
|
+
self.get_email_thread,
|
|
1388
1268
|
self.list_labels,
|
|
1389
1269
|
self.create_label,
|
|
1390
1270
|
self.get_profile,
|
|
1391
|
-
# Auto Generated from openapi spec
|
|
1392
1271
|
self.update_draft,
|
|
1393
1272
|
self.trash_message,
|
|
1394
1273
|
self.untrash_message,
|