universal-mcp-applications 0.1.30rc1__py3-none-any.whl → 0.1.36rc1__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.

Files changed (106) hide show
  1. universal_mcp/applications/ahrefs/app.py +52 -198
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +111 -464
  4. universal_mcp/applications/asana/app.py +417 -1567
  5. universal_mcp/applications/aws_s3/app.py +33 -100
  6. universal_mcp/applications/bill/app.py +546 -1957
  7. universal_mcp/applications/box/app.py +1068 -3981
  8. universal_mcp/applications/braze/app.py +364 -1430
  9. universal_mcp/applications/browser_use/app.py +2 -8
  10. universal_mcp/applications/cal_com_v2/app.py +207 -625
  11. universal_mcp/applications/calendly/app.py +61 -200
  12. universal_mcp/applications/canva/app.py +45 -110
  13. universal_mcp/applications/clickup/app.py +207 -674
  14. universal_mcp/applications/coda/app.py +146 -426
  15. universal_mcp/applications/confluence/app.py +310 -1098
  16. universal_mcp/applications/contentful/app.py +36 -151
  17. universal_mcp/applications/crustdata/app.py +28 -107
  18. universal_mcp/applications/dialpad/app.py +283 -756
  19. universal_mcp/applications/digitalocean/app.py +1766 -5777
  20. universal_mcp/applications/domain_checker/app.py +3 -54
  21. universal_mcp/applications/e2b/app.py +14 -64
  22. universal_mcp/applications/elevenlabs/app.py +9 -47
  23. universal_mcp/applications/exa/app.py +6 -17
  24. universal_mcp/applications/falai/app.py +23 -100
  25. universal_mcp/applications/figma/app.py +53 -137
  26. universal_mcp/applications/file_system/app.py +2 -13
  27. universal_mcp/applications/firecrawl/app.py +51 -152
  28. universal_mcp/applications/fireflies/app.py +59 -281
  29. universal_mcp/applications/fpl/app.py +91 -528
  30. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  31. universal_mcp/applications/fpl/utils/helper.py +25 -89
  32. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  33. universal_mcp/applications/ghost_content/app.py +52 -161
  34. universal_mcp/applications/github/app.py +19 -56
  35. universal_mcp/applications/gong/app.py +88 -248
  36. universal_mcp/applications/google_calendar/app.py +16 -68
  37. universal_mcp/applications/google_docs/app.py +88 -188
  38. universal_mcp/applications/google_drive/app.py +140 -462
  39. universal_mcp/applications/google_gemini/app.py +12 -64
  40. universal_mcp/applications/google_mail/app.py +28 -157
  41. universal_mcp/applications/google_searchconsole/app.py +15 -48
  42. universal_mcp/applications/google_sheet/app.py +101 -578
  43. universal_mcp/applications/google_sheet/helper.py +10 -37
  44. universal_mcp/applications/hashnode/app.py +57 -269
  45. universal_mcp/applications/heygen/app.py +44 -122
  46. universal_mcp/applications/http_tools/app.py +10 -32
  47. universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
  48. universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
  49. universal_mcp/applications/hubspot/app.py +23 -87
  50. universal_mcp/applications/jira/app.py +2071 -7986
  51. universal_mcp/applications/klaviyo/app.py +494 -1376
  52. universal_mcp/applications/linkedin/README.md +23 -4
  53. universal_mcp/applications/linkedin/app.py +392 -212
  54. universal_mcp/applications/mailchimp/app.py +450 -1605
  55. universal_mcp/applications/markitdown/app.py +8 -20
  56. universal_mcp/applications/miro/app.py +217 -699
  57. universal_mcp/applications/ms_teams/app.py +64 -186
  58. universal_mcp/applications/neon/app.py +86 -192
  59. universal_mcp/applications/notion/app.py +21 -36
  60. universal_mcp/applications/onedrive/app.py +14 -36
  61. universal_mcp/applications/openai/app.py +42 -165
  62. universal_mcp/applications/outlook/app.py +16 -76
  63. universal_mcp/applications/perplexity/app.py +4 -19
  64. universal_mcp/applications/pipedrive/app.py +832 -3142
  65. universal_mcp/applications/posthog/app.py +163 -432
  66. universal_mcp/applications/reddit/app.py +40 -139
  67. universal_mcp/applications/resend/app.py +41 -107
  68. universal_mcp/applications/retell/app.py +14 -41
  69. universal_mcp/applications/rocketlane/app.py +221 -934
  70. universal_mcp/applications/scraper/README.md +7 -4
  71. universal_mcp/applications/scraper/app.py +280 -93
  72. universal_mcp/applications/semanticscholar/app.py +22 -64
  73. universal_mcp/applications/semrush/app.py +43 -77
  74. universal_mcp/applications/sendgrid/app.py +512 -1262
  75. universal_mcp/applications/sentry/app.py +271 -906
  76. universal_mcp/applications/serpapi/app.py +40 -143
  77. universal_mcp/applications/sharepoint/app.py +15 -37
  78. universal_mcp/applications/shopify/app.py +1551 -4287
  79. universal_mcp/applications/shortcut/app.py +155 -417
  80. universal_mcp/applications/slack/app.py +50 -101
  81. universal_mcp/applications/spotify/app.py +126 -325
  82. universal_mcp/applications/supabase/app.py +104 -213
  83. universal_mcp/applications/tavily/app.py +1 -1
  84. universal_mcp/applications/trello/app.py +693 -2656
  85. universal_mcp/applications/twilio/app.py +14 -50
  86. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  87. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  88. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  89. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  90. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  91. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  92. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  93. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  94. universal_mcp/applications/whatsapp/app.py +35 -186
  95. universal_mcp/applications/whatsapp/audio.py +2 -6
  96. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  97. universal_mcp/applications/whatsapp_business/app.py +70 -283
  98. universal_mcp/applications/wrike/app.py +45 -118
  99. universal_mcp/applications/yahoo_finance/app.py +19 -65
  100. universal_mcp/applications/youtube/app.py +75 -261
  101. universal_mcp/applications/zenquotes/app.py +2 -2
  102. {universal_mcp_applications-0.1.30rc1.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.30rc1.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/RECORD +105 -106
  104. universal_mcp/applications/scraper/scraper_testers.py +0 -17
  105. {universal_mcp_applications-0.1.30rc1.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/WHEEL +0 -0
  106. {universal_mcp_applications-0.1.30rc1.dist-info → universal_mcp_applications-0.1.36rc1.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,6 @@ import os
4
4
  import uuid
5
5
  import wave
6
6
  from typing import Annotated
7
-
8
7
  from google import genai
9
8
  from google.genai import types
10
9
  from PIL import Image
@@ -22,21 +21,13 @@ class GoogleGeminiApp(APIApplication):
22
21
  if self._genai_client is not None:
23
22
  return self._genai_client
24
23
  credentials = self.integration.get_credentials()
25
- api_key = (
26
- credentials.get("api_key")
27
- or credentials.get("API_KEY")
28
- or credentials.get("apiKey")
29
- )
24
+ api_key = credentials.get("api_key") or credentials.get("API_KEY") or credentials.get("apiKey")
30
25
  if not api_key:
31
26
  raise ValueError("API key not found in integration credentials")
32
27
  self._genai_client = genai.Client(api_key=api_key)
33
28
  return self._genai_client
34
29
 
35
- async def generate_text(
36
- self,
37
- prompt: Annotated[str, "The prompt to generate text from"],
38
- model: str = "gemini-2.5-flash",
39
- ) -> str:
30
+ async def generate_text(self, prompt: Annotated[str, "The prompt to generate text from"], model: str = "gemini-2.5-flash") -> str:
40
31
  """Generates text using the Google Gemini model based on a given prompt.
41
32
  This tool is suitable for various natural language processing tasks such as content generation, summarization, translation, and question answering.
42
33
 
@@ -54,15 +45,13 @@ class GoogleGeminiApp(APIApplication):
54
45
  Tags:
55
46
  text, generate, llm, important
56
47
  """
57
- response = self.genai_client.models.generate_content(
58
- contents=prompt, model=model
59
- )
48
+ response = self.genai_client.models.generate_content(contents=prompt, model=model)
60
49
  return response.text
61
50
 
62
51
  async def generate_image(
63
52
  self,
64
53
  prompt: Annotated[str, "The prompt to generate image from"],
65
- images: Annotated[list[str], "The reference image URLs"] | None = None,
54
+ images: Annotated[list[str], "The reference image URLs"] | None = None,
66
55
  model: str = "gemini-2.5-flash-image-preview",
67
56
  ) -> dict:
68
57
  """
@@ -78,7 +67,7 @@ class GoogleGeminiApp(APIApplication):
78
67
  Returns:
79
68
  dict: A dictionary containing:
80
69
  - 'type' (str): Always "image".
81
- - 'data' (str): The base64 encoded image data.
70
+ - 'data' (str): The base64 encoded image data. Either upload or save this and then render the file path/link in markdown to the user. DO NOT use a data url.
82
71
  - 'mime_type' (str): The MIME type of the image (e.g., "image/png").
83
72
  - 'file_name' (str): A suggested file name for the generated image.
84
73
  - 'text' (str): Any accompanying text generated by the model.
@@ -91,7 +80,6 @@ class GoogleGeminiApp(APIApplication):
91
80
  Tags:
92
81
  image, generate, vision, important
93
82
  """
94
- # The Gemini API is synchronous, so run in a thread
95
83
  contents = [prompt]
96
84
  if images:
97
85
  for image in images:
@@ -104,35 +92,20 @@ class GoogleGeminiApp(APIApplication):
104
92
  else:
105
93
  image = Image.open(image)
106
94
  contents.append(image)
107
- response = self.genai_client.models.generate_content(
108
- model=model,
109
- contents=contents,
110
- )
95
+ response = self.genai_client.models.generate_content(model=model, contents=contents)
111
96
  candidate = response.candidates[0]
112
97
  text = ""
113
98
  for part in candidate.content.parts:
114
99
  if part.text is not None:
115
100
  text += part.text
116
101
  elif part.inline_data is not None:
117
- # Return the raw image bytes
118
102
  image_bytes = part.inline_data.data
119
-
120
103
  img_base64 = base64.b64encode(image_bytes).decode("utf-8")
121
-
122
104
  file_name = f"{uuid.uuid4()}.png"
123
-
124
- return {
125
- "type": "image",
126
- "data": img_base64,
127
- "mime_type": "image/png",
128
- "file_name": file_name,
129
- "text": text,
130
- }
105
+ return {"type": "image", "data": img_base64, "mime_type": "image/png", "file_name": file_name, "text": text}
131
106
 
132
107
  async def generate_audio(
133
- self,
134
- prompt: Annotated[str, "The prompt to generate audio from"],
135
- model: str = "gemini-2.5-flash-preview-tts",
108
+ self, prompt: Annotated[str, "The prompt to generate audio from"], model: str = "gemini-2.5-flash-preview-tts"
136
109
  ) -> str:
137
110
  """Generates audio from a given text prompt using the Google Gemini model's Text-to-Speech (TTS) capabilities.
138
111
  This tool is useful for converting text into spoken audio, which can be used for voiceovers, accessibility features, or interactive applications.
@@ -156,7 +129,6 @@ class GoogleGeminiApp(APIApplication):
156
129
  audio, generate, tts, speech, important
157
130
  """
158
131
 
159
- # Set up the wave file to save the output:
160
132
  def wave_file(filename, pcm, channels=1, rate=24000, sample_width=2):
161
133
  with wave.open(filename, "wb") as wf:
162
134
  wf.setnchannels(channels)
@@ -170,52 +142,28 @@ class GoogleGeminiApp(APIApplication):
170
142
  config=types.GenerateContentConfig(
171
143
  response_modalities=["AUDIO"],
172
144
  speech_config=types.SpeechConfig(
173
- voice_config=types.VoiceConfig(
174
- prebuilt_voice_config=types.PrebuiltVoiceConfig(
175
- voice_name="Kore",
176
- )
177
- )
145
+ voice_config=types.VoiceConfig(prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Kore"))
178
146
  ),
179
147
  ),
180
148
  )
181
-
182
149
  data = response.candidates[0].content.parts[0].inline_data.data
183
-
184
150
  file_name = f"{uuid.uuid4()}.wav"
185
151
  wave_file(file_name, data)
186
-
187
- # read the file
188
152
  with open(file_name, "rb") as f:
189
153
  data = f.read()
190
-
191
- # delete the file
192
154
  os.remove(file_name)
193
-
194
- # Convert to base64
195
155
  import base64
196
156
 
197
157
  audio_base64 = base64.b64encode(data).decode("utf-8")
198
-
199
- return {
200
- "type": "audio",
201
- "data": audio_base64,
202
- "mime_type": "audio/wav",
203
- "file_name": file_name,
204
- }
158
+ return {"type": "audio", "data": audio_base64, "mime_type": "audio/wav", "file_name": file_name}
205
159
 
206
160
  def list_tools(self):
207
- return [
208
- self.generate_text,
209
- self.generate_image,
210
- self.generate_audio,
211
- ]
161
+ return [self.generate_text, self.generate_image, self.generate_audio]
212
162
 
213
163
 
214
164
  async def test_google_gemini():
215
165
  app = GoogleGeminiApp()
216
- await app.generate_image(
217
- "A beautiful women potrait with red green hair color"
218
- )
166
+ await app.generate_image("A beautiful women potrait with red green hair color")
219
167
 
220
168
 
221
169
  if __name__ == "__main__":
@@ -2,7 +2,6 @@ import base64
2
2
  import concurrent.futures
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
43
  response = self._post(url, email_data)
56
-
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
89
  response = self._post(url, draft_data)
115
-
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
113
  response = self._post(url, draft_data)
144
-
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
138
  response = self._get(url, params=params)
175
-
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
168
  response = self._get(url, params=params)
218
-
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
 
@@ -234,25 +184,18 @@ class GoogleMailApp(APIApplication):
234
184
  url = f"{self.base_api_url}/messages/{message_id}"
235
185
  response = self._get(url)
236
186
  raw_data = self._handle_response(response)
237
-
238
- # Extract headers
239
187
  headers = {}
240
188
  for header in raw_data.get("payload", {}).get("headers", []):
241
189
  name = header.get("name", "")
242
190
  value = header.get("value", "")
243
191
  headers[name] = value
244
-
245
- # Extract body content
246
192
  body_content = self._extract_email_body(raw_data.get("payload", {}))
247
193
  if not body_content:
248
194
  if "snippet" in raw_data:
249
195
  body_content = f"Preview: {raw_data['snippet']}"
250
196
  else:
251
197
  body_content = "No content available"
252
-
253
- # Extract attachments
254
198
  attachments = self._extract_attachments(raw_data.get("payload", {}))
255
-
256
199
  return {
257
200
  "message_id": message_id,
258
201
  "from_addr": headers.get("From", "Unknown sender"),
@@ -275,45 +218,30 @@ class GoogleMailApp(APIApplication):
275
218
  str: The email body content (plain text preferred, HTML as fallback)
276
219
  """
277
220
  try:
278
- # Handle single part message
279
221
  if payload.get("body") and payload.get("body", {}).get("data"):
280
222
  return self._decode_base64(payload["body"]["data"])
281
-
282
- # Handle multipart message
283
223
  parts = payload.get("parts", [])
284
224
  if not parts:
285
225
  return ""
286
-
287
226
  plain_text_body = ""
288
227
  html_body = ""
289
-
290
228
  for part in parts:
291
229
  mime_type = part.get("mimeType", "")
292
-
293
- # Extract plain text
294
230
  if mime_type == "text/plain":
295
231
  if part.get("body") and part.get("body", {}).get("data"):
296
232
  plain_text_body = self._decode_base64(part["body"]["data"])
297
-
298
- # Extract HTML content
299
233
  elif mime_type == "text/html":
300
234
  if part.get("body") and part.get("body", {}).get("data"):
301
235
  html_body = self._decode_base64(part["body"]["data"])
302
-
303
- # Handle nested multipart (recursive)
304
236
  elif mime_type.startswith("multipart/") and part.get("parts"):
305
237
  nested_body = self._extract_email_body(part)
306
- if nested_body and not plain_text_body:
238
+ if nested_body and (not plain_text_body):
307
239
  plain_text_body = nested_body
308
-
309
- # Prefer plain text, fallback to HTML
310
240
  if plain_text_body:
311
241
  return plain_text_body
312
242
  elif html_body:
313
243
  return f"[HTML Content]\n{html_body}"
314
-
315
244
  return ""
316
-
317
245
  except Exception as e:
318
246
  logger.error(f"Error extracting email body: {str(e)}")
319
247
  return ""
@@ -329,7 +257,6 @@ class GoogleMailApp(APIApplication):
329
257
  list: List of attachment dictionaries with attachment_id, filename, mime_type, and size
330
258
  """
331
259
  attachments = []
332
-
333
260
  try:
334
261
  if payload.get("filename") and payload.get("body", {}).get("attachmentId"):
335
262
  attachments.append(
@@ -340,7 +267,6 @@ class GoogleMailApp(APIApplication):
340
267
  "size": payload.get("body", {}).get("size", 0),
341
268
  }
342
269
  )
343
-
344
270
  parts = payload.get("parts", [])
345
271
  for part in parts:
346
272
  if part.get("filename") and part.get("body", {}).get("attachmentId"):
@@ -352,14 +278,11 @@ class GoogleMailApp(APIApplication):
352
278
  "size": part.get("body", {}).get("size", 0),
353
279
  }
354
280
  )
355
-
356
281
  elif part.get("parts"):
357
282
  nested_attachments = self._extract_attachments(part)
358
283
  attachments.extend(nested_attachments)
359
-
360
284
  except Exception as e:
361
285
  logger.error(f"Error extracting attachments: {str(e)}")
362
-
363
286
  return attachments
364
287
 
365
288
  def _decode_base64(self, data):
@@ -373,19 +296,14 @@ class GoogleMailApp(APIApplication):
373
296
  str: Decoded string content
374
297
  """
375
298
  try:
376
- # Gmail API uses URL-safe base64 encoding
377
299
  decoded_bytes = base64.urlsafe_b64decode(data)
378
300
  return decoded_bytes.decode("utf-8")
379
301
  except Exception as e:
380
302
  logger.error(f"Error decoding base64 data: {str(e)}")
381
303
  return f"[Unable to decode content: {str(e)}]"
382
304
 
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,
305
+ async def list_messages(
306
+ self, max_results: int = 10, q: str | None = None, include_spam_trash: bool = False, page_token: str | None = None
389
307
  ) -> dict[str, Any]:
390
308
  """
391
309
  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.
@@ -423,39 +341,22 @@ class GoogleMailApp(APIApplication):
423
341
  list, messages, gmail, search, query, pagination, important
424
342
  """
425
343
  url = f"{self.base_api_url}/messages?format=metadata"
426
-
427
- # Build query parameters
428
344
  params: dict[str, Any] = {"maxResults": max_results}
429
-
430
345
  if q:
431
346
  params["q"] = q
432
-
433
347
  if include_spam_trash:
434
348
  params["includeSpamTrash"] = "true"
435
-
436
349
  if page_token:
437
350
  params["pageToken"] = page_token
438
-
439
351
  logger.info(f"Retrieving messages list with params: {params}")
440
-
441
352
  response = self._get(url, params=params)
442
353
  data = self._handle_response(response)
443
-
444
- # Extract message IDs
445
354
  messages = data.get("messages", [])
446
355
  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
356
  detailed_messages = []
450
357
  if message_ids:
451
358
  with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
452
- # Submit all get_message_details calls
453
- future_to_message_id = {
454
- executor.submit(self.get_message_details, message_id): message_id
455
- for message_id in message_ids
456
- }
457
-
458
- # Collect results as they complete
359
+ future_to_message_id = {executor.submit(self.get_message_details, message_id): message_id for message_id in message_ids}
459
360
  for future in concurrent.futures.as_completed(future_to_message_id):
460
361
  message_id = future_to_message_id[future]
461
362
  try:
@@ -463,12 +364,7 @@ class GoogleMailApp(APIApplication):
463
364
  detailed_messages.append(result)
464
365
  except Exception as e:
465
366
  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
- }
367
+ return {"messages": detailed_messages, "next_page_token": data.get("nextPageToken")}
472
368
 
473
369
  def get_email_thread(self, thread_id: str) -> dict[str, Any]:
474
370
  """
@@ -493,7 +389,7 @@ class GoogleMailApp(APIApplication):
493
389
  response = self._get(url)
494
390
  return self._handle_response(response)
495
391
 
496
- def list_labels(self) -> dict[str, Any]:
392
+ async def list_labels(self) -> dict[str, Any]:
497
393
  """
498
394
  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
395
 
@@ -510,16 +406,12 @@ class GoogleMailApp(APIApplication):
510
406
  Tags:
511
407
  list, gmail, labels, fetch, organize, important, management
512
408
  """
513
-
514
409
  url = f"{self.base_api_url}/labels"
515
-
516
410
  logger.info("Retrieving Gmail labels")
517
-
518
411
  response = self._get(url)
519
-
520
412
  return self._handle_response(response)
521
413
 
522
- def create_label(self, name: str) -> dict[str, Any]:
414
+ async def create_label(self, name: str) -> dict[str, Any]:
523
415
  """
524
416
  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
417
 
@@ -536,23 +428,13 @@ class GoogleMailApp(APIApplication):
536
428
  Tags:
537
429
  create, label, gmail, management, important
538
430
  """
539
-
540
431
  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
-
432
+ label_data = {"name": name, "labelListVisibility": "labelShow", "messageListVisibility": "show"}
549
433
  logger.info(f"Creating new Gmail label: {name}")
550
-
551
434
  response = self._post(url, label_data)
552
-
553
435
  return self._handle_response(response)
554
436
 
555
- def get_profile(self) -> dict[str, Any]:
437
+ async def get_profile(self) -> dict[str, Any]:
556
438
  """
557
439
  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
440
 
@@ -569,15 +451,12 @@ class GoogleMailApp(APIApplication):
569
451
  Tags:
570
452
  fetch, profile, gmail, user-info, api-request, important
571
453
  """
572
-
573
454
  url = f"{self.base_api_url}/profile"
574
-
575
455
  logger.info("Retrieving Gmail user profile")
576
-
577
456
  response = self._get(url)
578
457
  return self._handle_response(response)
579
458
 
580
- def update_draft(
459
+ async def update_draft(
581
460
  self,
582
461
  userId,
583
462
  id,
@@ -670,10 +549,7 @@ class GoogleMailApp(APIApplication):
670
549
  raise ValueError("Missing required parameter 'userId'")
671
550
  if id is None:
672
551
  raise ValueError("Missing required parameter 'id'")
673
- request_body = {
674
- "id": id,
675
- "message": message,
676
- }
552
+ request_body = {"id": id, "message": message}
677
553
  request_body = {k: v for k, v in request_body.items() if v is not None}
678
554
  url = f"{self.base_url}/gmail/v1/users/{userId}/drafts/{id}"
679
555
  query_params = {
@@ -697,7 +573,7 @@ class GoogleMailApp(APIApplication):
697
573
  response.raise_for_status()
698
574
  return response.json()
699
575
 
700
- def trash_message(
576
+ async def trash_message(
701
577
  self,
702
578
  userId,
703
579
  id,
@@ -763,7 +639,7 @@ class GoogleMailApp(APIApplication):
763
639
  response.raise_for_status()
764
640
  return response.json()
765
641
 
766
- def untrash_message(
642
+ async def untrash_message(
767
643
  self,
768
644
  userId,
769
645
  id,
@@ -829,7 +705,7 @@ class GoogleMailApp(APIApplication):
829
705
  response.raise_for_status()
830
706
  return response.json()
831
707
 
832
- def get_attachment(
708
+ async def get_attachment(
833
709
  self,
834
710
  userId,
835
711
  messageId,
@@ -899,7 +775,7 @@ class GoogleMailApp(APIApplication):
899
775
  response.raise_for_status()
900
776
  return response.json()
901
777
 
902
- def update_label(
778
+ async def update_label(
903
779
  self,
904
780
  userId,
905
781
  id,
@@ -1014,7 +890,7 @@ class GoogleMailApp(APIApplication):
1014
890
  response.raise_for_status()
1015
891
  return response.json()
1016
892
 
1017
- def delete_label(
893
+ async def delete_label(
1018
894
  self,
1019
895
  userId,
1020
896
  id,
@@ -1080,7 +956,7 @@ class GoogleMailApp(APIApplication):
1080
956
  response.raise_for_status()
1081
957
  return response.json()
1082
958
 
1083
- def get_filter(
959
+ async def get_filter(
1084
960
  self,
1085
961
  userId,
1086
962
  id,
@@ -1146,7 +1022,7 @@ class GoogleMailApp(APIApplication):
1146
1022
  response.raise_for_status()
1147
1023
  return response.json()
1148
1024
 
1149
- def delete_filter(
1025
+ async def delete_filter(
1150
1026
  self,
1151
1027
  userId,
1152
1028
  id,
@@ -1212,7 +1088,7 @@ class GoogleMailApp(APIApplication):
1212
1088
  response.raise_for_status()
1213
1089
  return response.json()
1214
1090
 
1215
- def get_all_filters(
1091
+ async def get_all_filters(
1216
1092
  self,
1217
1093
  userId,
1218
1094
  access_token=None,
@@ -1274,7 +1150,7 @@ class GoogleMailApp(APIApplication):
1274
1150
  response.raise_for_status()
1275
1151
  return response.json()
1276
1152
 
1277
- def create_filter(
1153
+ async def create_filter(
1278
1154
  self,
1279
1155
  userId,
1280
1156
  access_token=None,
@@ -1348,11 +1224,7 @@ class GoogleMailApp(APIApplication):
1348
1224
  """
1349
1225
  if userId is None:
1350
1226
  raise ValueError("Missing required parameter 'userId'")
1351
- request_body = {
1352
- "action": action,
1353
- "criteria": criteria,
1354
- "id": id,
1355
- }
1227
+ request_body = {"action": action, "criteria": criteria, "id": id}
1356
1228
  request_body = {k: v for k, v in request_body.items() if v is not None}
1357
1229
  url = f"{self.base_url}/gmail/v1/users/{userId}/settings/filters"
1358
1230
  query_params = {
@@ -1388,7 +1260,6 @@ class GoogleMailApp(APIApplication):
1388
1260
  self.list_labels,
1389
1261
  self.create_label,
1390
1262
  self.get_profile,
1391
- # Auto Generated from openapi spec
1392
1263
  self.update_draft,
1393
1264
  self.trash_message,
1394
1265
  self.untrash_message,