universal-mcp-applications 0.1.32__py3-none-any.whl → 0.1.36rc2__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.
Files changed (105) 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 +36 -103
  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 +24 -101
  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 +85 -189
  38. universal_mcp/applications/google_drive/app.py +141 -463
  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 +100 -581
  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 +9 -2
  53. universal_mcp/applications/linkedin/app.py +240 -181
  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 +16 -38
  61. universal_mcp/applications/openai/app.py +42 -165
  62. universal_mcp/applications/outlook/app.py +24 -84
  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 +50 -109
  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 +17 -39
  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 +33 -115
  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.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
  103. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
  104. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
  105. {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/licenses/LICENSE +0 -0
@@ -1,49 +1,21 @@
1
1
  from typing import Any
2
-
3
2
  import requests
4
3
  from universal_mcp.agentr.integration import AgentrIntegration
5
4
  from universal_mcp.applications.application import BaseApplication
6
5
  from universal_mcp.exceptions import NotAuthorizedError
7
-
8
- from universal_mcp.applications.whatsapp.whatsapp import (
9
- WHATSAPP_API_BASE_URL,
10
- )
11
- from universal_mcp.applications.whatsapp.whatsapp import (
12
- download_media as whatsapp_download_media,
13
- )
14
- from universal_mcp.applications.whatsapp.whatsapp import (
15
- get_chat as whatsapp_get_chat,
16
- )
17
- from universal_mcp.applications.whatsapp.whatsapp import (
18
- get_contact_chats as whatsapp_get_contact_chats,
19
- )
20
- from universal_mcp.applications.whatsapp.whatsapp import (
21
- get_direct_chat_by_contact as whatsapp_get_direct_chat_by_contact,
22
- )
23
- from universal_mcp.applications.whatsapp.whatsapp import (
24
- get_last_interaction as whatsapp_get_last_interaction,
25
- )
26
- from universal_mcp.applications.whatsapp.whatsapp import (
27
- get_message_context as whatsapp_get_message_context,
28
- )
29
- from universal_mcp.applications.whatsapp.whatsapp import (
30
- list_chats as whatsapp_list_chats,
31
- )
32
- from universal_mcp.applications.whatsapp.whatsapp import (
33
- list_messages as whatsapp_list_messages,
34
- )
35
- from universal_mcp.applications.whatsapp.whatsapp import (
36
- search_contacts as whatsapp_search_contacts,
37
- )
38
- from universal_mcp.applications.whatsapp.whatsapp import (
39
- send_audio_message as whatsapp_audio_voice_message,
40
- )
41
- from universal_mcp.applications.whatsapp.whatsapp import (
42
- send_file as whatsapp_send_file,
43
- )
44
- from universal_mcp.applications.whatsapp.whatsapp import (
45
- send_message as whatsapp_send_message,
46
- )
6
+ from universal_mcp.applications.whatsapp.whatsapp import WHATSAPP_API_BASE_URL
7
+ from universal_mcp.applications.whatsapp.whatsapp import download_media as whatsapp_download_media
8
+ from universal_mcp.applications.whatsapp.whatsapp import get_chat as whatsapp_get_chat
9
+ from universal_mcp.applications.whatsapp.whatsapp import get_contact_chats as whatsapp_get_contact_chats
10
+ from universal_mcp.applications.whatsapp.whatsapp import get_direct_chat_by_contact as whatsapp_get_direct_chat_by_contact
11
+ from universal_mcp.applications.whatsapp.whatsapp import get_last_interaction as whatsapp_get_last_interaction
12
+ from universal_mcp.applications.whatsapp.whatsapp import get_message_context as whatsapp_get_message_context
13
+ from universal_mcp.applications.whatsapp.whatsapp import list_chats as whatsapp_list_chats
14
+ from universal_mcp.applications.whatsapp.whatsapp import list_messages as whatsapp_list_messages
15
+ from universal_mcp.applications.whatsapp.whatsapp import search_contacts as whatsapp_search_contacts
16
+ from universal_mcp.applications.whatsapp.whatsapp import send_audio_message as whatsapp_audio_voice_message
17
+ from universal_mcp.applications.whatsapp.whatsapp import send_file as whatsapp_send_file
18
+ from universal_mcp.applications.whatsapp.whatsapp import send_message as whatsapp_send_message
47
19
 
48
20
 
49
21
  class WhatsappApp(BaseApplication):
@@ -63,7 +35,6 @@ class WhatsappApp(BaseApplication):
63
35
  """
64
36
  if not self.integration:
65
37
  raise ValueError("No integration available to get API key from")
66
-
67
38
  try:
68
39
  headers = self.integration.client.client.headers
69
40
  api_key = headers.get("X-API-KEY")
@@ -89,19 +60,13 @@ class WhatsappApp(BaseApplication):
89
60
  Triggers WhatsApp authentication flow when no integration is available.
90
61
  Raises NotAuthorizedError with authorization URL when authentication is needed.
91
62
  """
92
-
93
- # Try WhatsApp authentication
94
63
  auth_result = self._authenticate_whatsapp()
95
64
  if auth_result[0] is True:
96
65
  return True
97
66
  elif isinstance(auth_result[1], str):
98
- # auth_result contains the authorization URL message
99
67
  raise NotAuthorizedError(auth_result[1])
100
68
  else:
101
- # WhatsApp authentication failed but no URL provided
102
- raise NotAuthorizedError(
103
- "WhatsApp authentication failed. Please check your configuration."
104
- )
69
+ raise NotAuthorizedError("WhatsApp authentication failed. Please check your configuration.")
105
70
 
106
71
  def _authenticate_whatsapp(self) -> tuple[bool, str]:
107
72
  """
@@ -109,20 +74,11 @@ class WhatsappApp(BaseApplication):
109
74
  Makes a POST request to the auth endpoint.
110
75
  """
111
76
  try:
112
- # Use the API key from the integration
113
77
  user_id = self.api_key
114
78
  if not user_id:
115
79
  raise ValueError("No API key available from integration")
116
-
117
80
  auth_url = f"{self.base_url}/api/auth"
118
-
119
- response = requests.post(
120
- auth_url,
121
- headers={"Content-Type": "application/json"},
122
- json={"user_id": user_id},
123
- timeout=60,
124
- )
125
-
81
+ response = requests.post(auth_url, headers={"Content-Type": "application/json"}, json={"user_id": user_id}, timeout=60)
126
82
  if response.status_code == 200:
127
83
  result = response.json()
128
84
  if result.get("status") == "qr_required":
@@ -134,15 +90,12 @@ class WhatsappApp(BaseApplication):
134
90
  elif result.get("status") == "connected":
135
91
  return (True, "User already authenticated")
136
92
  else:
137
- # Return QR URL even when auth fails, so user can try to authenticate
138
93
  qr_url = f"{self.base_url}/api/qr?user_id={user_id}"
139
94
  return (
140
95
  False,
141
96
  f"Please ask the user to visit the following url to authorize WhatsApp: {qr_url}. Render the url in proper markdown format with a clickable link.",
142
97
  )
143
-
144
98
  except Exception:
145
- # Return QR URL when there's an exception, so user can try to authenticate
146
99
  user_id = self.api_key
147
100
  if user_id:
148
101
  qr_url = f"{self.base_url}/api/qr?user_id={user_id}"
@@ -153,10 +106,7 @@ class WhatsappApp(BaseApplication):
153
106
  else:
154
107
  return (False, "No API key available from integration")
155
108
 
156
- def search_contacts(
157
- self,
158
- query: str,
159
- ) -> list[dict[str, Any]]:
109
+ async def search_contacts(self, query: str) -> list[dict[str, Any]]:
160
110
  """
161
111
  Searches for WhatsApp contacts by name or phone number. This function takes a query string, handles user authentication, and calls the underlying API to find and return a list of matching contacts. It serves as the primary method to look up contact information within the application.
162
112
 
@@ -174,15 +124,12 @@ class WhatsappApp(BaseApplication):
174
124
  """
175
125
  if query is None:
176
126
  raise ValueError("Missing required parameter 'query'.")
177
-
178
- # Trigger authentication
179
127
  self._authenticator()
180
-
181
128
  user_id = self.api_key
182
129
  contacts = whatsapp_search_contacts(query, user_id)
183
130
  return contacts
184
131
 
185
- def search_messages(
132
+ async def search_messages(
186
133
  self,
187
134
  after: str | None = None,
188
135
  before: str | None = None,
@@ -219,9 +166,7 @@ class WhatsappApp(BaseApplication):
219
166
  Tags:
220
167
  whatsapp.messages, important
221
168
  """
222
- # Trigger authentication
223
169
  self._authenticator()
224
-
225
170
  user_id = self.api_key
226
171
  messages = whatsapp_list_messages(
227
172
  after=after,
@@ -238,13 +183,8 @@ class WhatsappApp(BaseApplication):
238
183
  )
239
184
  return messages
240
185
 
241
- def search_chats(
242
- self,
243
- query: str | None = None,
244
- limit: int = 20,
245
- page: int = 0,
246
- include_last_message: bool = True,
247
- sort_by: str = "last_active",
186
+ async def search_chats(
187
+ self, query: str | None = None, limit: int = 20, page: int = 0, include_last_message: bool = True, sort_by: str = "last_active"
248
188
  ) -> list[dict[str, Any]]:
249
189
  """
250
190
  Retrieves a paginated list of WhatsApp chats, allowing filtering by a search query and sorting by activity or name. Unlike `get_chat`, which fetches a single known chat, this function provides broad search and discovery capabilities across multiple user conversations.
@@ -265,25 +205,14 @@ class WhatsappApp(BaseApplication):
265
205
  Tags:
266
206
  whatsapp.chats, important
267
207
  """
268
- # Trigger authentication
269
208
  self._authenticator()
270
-
271
209
  user_id = self.api_key
272
210
  chats = whatsapp_list_chats(
273
- query=query,
274
- limit=limit,
275
- page=page,
276
- include_last_message=include_last_message,
277
- sort_by=sort_by,
278
- user_id=user_id,
211
+ query=query, limit=limit, page=page, include_last_message=include_last_message, sort_by=sort_by, user_id=user_id
279
212
  )
280
213
  return chats
281
214
 
282
- def get_chat_by_jid(
283
- self,
284
- chat_jid: str,
285
- include_last_message: bool = True,
286
- ) -> dict[str, Any]:
215
+ async def get_chat_by_jid(self, chat_jid: str, include_last_message: bool = True) -> dict[str, Any]:
287
216
  """
288
217
  Retrieves metadata for a specific WhatsApp chat (direct or group) using its unique JID. It can optionally include the most recent message. This precise JID-based lookup distinguishes it from `get_direct_chat_by_contact`, which uses a phone number, and `list_chats`, which performs a broader search.
289
218
 
@@ -302,18 +231,12 @@ class WhatsappApp(BaseApplication):
302
231
  """
303
232
  if chat_jid is None:
304
233
  raise ValueError("Missing required parameter 'chat_jid'.")
305
-
306
- # Trigger authentication
307
234
  self._authenticator()
308
-
309
235
  user_id = self.api_key
310
236
  chat = whatsapp_get_chat(chat_jid, include_last_message, user_id)
311
237
  return chat
312
238
 
313
- def get_direct_chat_by_phone_number(
314
- self,
315
- sender_phone_number: str,
316
- ) -> dict[str, Any]:
239
+ async def get_direct_chat_by_phone_number(self, sender_phone_number: str) -> dict[str, Any]:
317
240
  """
318
241
  Retrieves metadata for a direct (one-on-one) WhatsApp chat using a contact's phone number. Unlike `get_chat` which requires a JID, this provides a simpler way to find direct conversations. Returns a dictionary containing the chat's details, such as its JID and name.
319
242
 
@@ -331,20 +254,12 @@ class WhatsappApp(BaseApplication):
331
254
  """
332
255
  if sender_phone_number is None:
333
256
  raise ValueError("Missing required parameter 'sender_phone_number'.")
334
-
335
- # Trigger authentication
336
257
  self._authenticator()
337
-
338
258
  user_id = self.api_key
339
259
  chat = whatsapp_get_direct_chat_by_contact(sender_phone_number, user_id)
340
260
  return chat
341
261
 
342
- def list_chats_by_contact_jid(
343
- self,
344
- jid: str,
345
- limit: int = 20,
346
- page: int = 0,
347
- ) -> list[dict[str, Any]]:
262
+ async def list_chats_by_contact_jid(self, jid: str, limit: int = 20, page: int = 0) -> list[dict[str, Any]]:
348
263
  """
349
264
  Retrieves a paginated list of all WhatsApp chats, including direct messages and groups, that a specific contact participates in. The contact is identified by their unique JID. This differs from `get_direct_chat_by_contact` which only finds one-on-one chats.
350
265
 
@@ -364,18 +279,12 @@ class WhatsappApp(BaseApplication):
364
279
  """
365
280
  if jid is None:
366
281
  raise ValueError("Missing required parameter 'jid'.")
367
-
368
- # Trigger authentication
369
282
  self._authenticator()
370
-
371
283
  user_id = self.api_key
372
284
  chats = whatsapp_get_contact_chats(jid, limit, page, user_id)
373
285
  return chats
374
286
 
375
- def get_last_message_by_jid(
376
- self,
377
- jid: str,
378
- ) -> str:
287
+ async def get_last_message_by_jid(self, jid: str) -> str:
379
288
  """
380
289
  Retrieves the content of the most recent message involving a specific contact, identified by their JID. It authenticates the user and returns the message directly as a string, offering a quick way to view the last communication without fetching full message objects or chat histories.
381
290
 
@@ -393,20 +302,12 @@ class WhatsappApp(BaseApplication):
393
302
  """
394
303
  if jid is None:
395
304
  raise ValueError("Missing required parameter 'jid'.")
396
-
397
- # Trigger authentication
398
305
  self._authenticator()
399
-
400
306
  user_id = self.api_key
401
307
  message = whatsapp_get_last_interaction(jid, user_id)
402
308
  return message
403
309
 
404
- def get_message_context(
405
- self,
406
- message_id: str,
407
- before: int = 5,
408
- after: int = 5,
409
- ) -> dict[str, Any]:
310
+ async def get_message_context(self, message_id: str, before: int = 5, after: int = 5) -> dict[str, Any]:
410
311
  """
411
312
  Fetches the conversational context surrounding a specific WhatsApp message ID. It retrieves a configurable number of messages immediately preceding and following the target message. This provides a focused view of a dialogue, unlike `list_messages` which performs broader, filter-based searches.
412
313
 
@@ -426,19 +327,12 @@ class WhatsappApp(BaseApplication):
426
327
  """
427
328
  if message_id is None:
428
329
  raise ValueError("Missing required parameter 'message_id'.")
429
-
430
- # Trigger authentication
431
330
  self._authenticator()
432
-
433
331
  user_id = self.api_key
434
332
  context = whatsapp_get_message_context(message_id, before, after, user_id)
435
333
  return context
436
334
 
437
- def send_text_message(
438
- self,
439
- recipient: str,
440
- message: str,
441
- ) -> dict[str, Any]:
335
+ async def send_text_message(self, recipient: str, message: str) -> dict[str, Any]:
442
336
  """
443
337
  Authenticates the user and sends a text message to a specified WhatsApp recipient. The recipient can be an individual (via phone number) or a group (via JID). It returns a dictionary indicating the operation's success status and a corresponding message.
444
338
 
@@ -460,23 +354,12 @@ class WhatsappApp(BaseApplication):
460
354
  raise ValueError("Missing required parameter 'recipient'.")
461
355
  if message is None:
462
356
  raise ValueError("Missing required parameter 'message'.")
463
-
464
- # Trigger authentication
465
357
  self._authenticator()
466
-
467
358
  user_id = self.api_key
468
- # Call the whatsapp_send_message function with the unified recipient parameter
469
359
  success, status_message = whatsapp_send_message(recipient, message, user_id)
470
- return {
471
- "success": success,
472
- "message": status_message,
473
- }
360
+ return {"success": success, "message": status_message}
474
361
 
475
- def send_attachment(
476
- self,
477
- recipient: str,
478
- media_path: str,
479
- ) -> dict[str, Any]:
362
+ async def send_attachment(self, recipient: str, media_path: str) -> dict[str, Any]:
480
363
  """
481
364
  Sends a media file (image, video, document, raw audio) as a standard attachment to a WhatsApp contact or group using their phone number or JID. Unlike `send_audio_message`, which creates a playable voice note, this function handles general file transfers. Returns a success status dictionary.
482
365
 
@@ -498,23 +381,12 @@ class WhatsappApp(BaseApplication):
498
381
  raise ValueError("Missing required parameter 'recipient'.")
499
382
  if media_path is None:
500
383
  raise ValueError("Missing required parameter 'media_path'.")
501
-
502
- # Trigger authentication
503
384
  self._authenticator()
504
-
505
385
  user_id = self.api_key
506
- # Call the whatsapp_send_file function
507
386
  success, status_message = whatsapp_send_file(recipient, media_path, user_id)
508
- return {
509
- "success": success,
510
- "message": status_message,
511
- }
387
+ return {"success": success, "message": status_message}
512
388
 
513
- def send_voice_message(
514
- self,
515
- recipient: str,
516
- media_path: str,
517
- ) -> dict[str, Any]:
389
+ async def send_voice_message(self, recipient: str, media_path: str) -> dict[str, Any]:
518
390
  """
519
391
  Sends a local audio file as a playable WhatsApp voice message, converting it to the required format. Unlike `send_file` which sends audio as a document attachment, this function formats the audio as a voice note. It can be sent to an individual contact or a group chat.
520
392
 
@@ -536,24 +408,12 @@ class WhatsappApp(BaseApplication):
536
408
  raise ValueError("Missing required parameter 'recipient'.")
537
409
  if media_path is None:
538
410
  raise ValueError("Missing required parameter 'media_path'.")
539
-
540
- # Trigger authentication
541
411
  self._authenticator()
542
-
543
412
  user_id = self.api_key
544
- success, status_message = whatsapp_audio_voice_message(
545
- recipient, media_path, user_id
546
- )
547
- return {
548
- "success": success,
549
- "message": status_message,
550
- }
413
+ success, status_message = whatsapp_audio_voice_message(recipient, media_path, user_id)
414
+ return {"success": success, "message": status_message}
551
415
 
552
- def download_media_from_message(
553
- self,
554
- message_id: str,
555
- chat_jid: str,
556
- ) -> dict[str, Any]:
416
+ async def download_media_from_message(self, message_id: str, chat_jid: str) -> dict[str, Any]:
557
417
  """
558
418
  Downloads media from a specific WhatsApp message, identified by its ID and chat JID. It saves the content to a local file and returns the file's path upon success. The function automatically handles user authentication before initiating the download.
559
419
 
@@ -574,24 +434,13 @@ class WhatsappApp(BaseApplication):
574
434
  raise ValueError("Missing required parameter 'message_id'.")
575
435
  if chat_jid is None:
576
436
  raise ValueError("Missing required parameter 'chat_jid'.")
577
-
578
- # Trigger authentication
579
437
  self._authenticator()
580
-
581
438
  user_id = self.api_key
582
439
  file_path = whatsapp_download_media(message_id, chat_jid, user_id)
583
-
584
440
  if file_path:
585
- return {
586
- "success": True,
587
- "message": "Media downloaded successfully",
588
- "file_path": file_path,
589
- }
441
+ return {"success": True, "message": "Media downloaded successfully", "file_path": file_path}
590
442
  else:
591
- return {
592
- "success": False,
593
- "message": "Failed to download media",
594
- }
443
+ return {"success": False, "message": "Failed to download media"}
595
444
 
596
445
  def list_tools(self):
597
446
  """
@@ -58,14 +58,10 @@ def convert_to_opus_ogg(input_file, output_file=None, bitrate="32k", sample_rate
58
58
 
59
59
  try:
60
60
  # Run the ffmpeg command and capture output
61
- subprocess.run(
62
- cmd, capture_output=True, text=True, check=True
63
- )
61
+ subprocess.run(cmd, capture_output=True, text=True, check=True)
64
62
  return output_file
65
63
  except subprocess.CalledProcessError as e:
66
- raise RuntimeError(
67
- f"Failed to convert audio. You likely need to install ffmpeg {e.stderr}"
68
- )
64
+ raise RuntimeError(f"Failed to convert audio. You likely need to install ffmpeg {e.stderr}")
69
65
 
70
66
 
71
67
  def convert_to_opus_ogg_temp(input_file, bitrate="32k", sample_rate=24000):
@@ -54,9 +54,7 @@ class MessageContext:
54
54
  after: list[Message]
55
55
 
56
56
 
57
- def _make_api_request(
58
- endpoint: str, method: str = "GET", data: dict = None, user_id: str = "default_user"
59
- ) -> dict:
57
+ def _make_api_request(endpoint: str, method: str = "GET", data: dict = None, user_id: str = "default_user") -> dict:
60
58
  """Make HTTP request to WhatsApp Bridge API."""
61
59
  url = f"{WHATSAPP_API_BASE_URL}/api/{endpoint}"
62
60
 
@@ -87,9 +85,7 @@ def _make_api_request(
87
85
 
88
86
  def get_sender_name(sender_jid: str, user_id: str = "default_user") -> str:
89
87
  """Get sender name via API call."""
90
- result = _make_api_request(
91
- "sender_name", data={"sender_jid": sender_jid}, user_id=user_id
92
- )
88
+ result = _make_api_request("sender_name", data={"sender_jid": sender_jid}, user_id=user_id)
93
89
 
94
90
  if "error" in result:
95
91
  return sender_jid
@@ -97,9 +93,7 @@ def get_sender_name(sender_jid: str, user_id: str = "default_user") -> str:
97
93
  return result.get("name", sender_jid)
98
94
 
99
95
 
100
- def format_message(
101
- message: Message, show_chat_info: bool = True, user_id: str = "default_user"
102
- ) -> str:
96
+ def format_message(message: Message, show_chat_info: bool = True, user_id: str = "default_user") -> str:
103
97
  """Print a single message with consistent formatting."""
104
98
  output = ""
105
99
 
@@ -113,18 +107,14 @@ def format_message(
113
107
  content_prefix = f"[{message.media_type} - Message ID: {message.id} - Chat JID: {message.chat_jid}] "
114
108
 
115
109
  try:
116
- sender_name = (
117
- get_sender_name(message.sender, user_id) if not message.is_from_me else "Me"
118
- )
110
+ sender_name = get_sender_name(message.sender, user_id) if not message.is_from_me else "Me"
119
111
  output += f"From: {sender_name}: {content_prefix}{message.content}\n"
120
112
  except Exception:
121
113
  pass
122
114
  return output
123
115
 
124
116
 
125
- def format_messages_list(
126
- messages: list[Message], show_chat_info: bool = True, user_id: str = "default_user"
127
- ) -> str:
117
+ def format_messages_list(messages: list[Message], show_chat_info: bool = True, user_id: str = "default_user") -> str:
128
118
  output = ""
129
119
  if not messages:
130
120
  output += "No messages to display."
@@ -173,9 +163,7 @@ def list_messages(
173
163
  return result.get("messages", "No messages found")
174
164
 
175
165
 
176
- def get_message_context(
177
- message_id: str, before: int = 5, after: int = 5, user_id: str = "default_user"
178
- ) -> MessageContext:
166
+ def get_message_context(message_id: str, before: int = 5, after: int = 5, user_id: str = "default_user") -> MessageContext:
179
167
  """Get context around a specific message via API."""
180
168
  params = {"message_id": message_id, "before": before, "after": after}
181
169
 
@@ -222,9 +210,7 @@ def list_chats(
222
210
  chat = Chat(
223
211
  jid=chat_data["jid"],
224
212
  name=chat_data.get("name"),
225
- last_message_time=datetime.fromisoformat(chat_data["last_message_time"])
226
- if chat_data.get("last_message_time")
227
- else None,
213
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
228
214
  last_message=chat_data.get("last_message"),
229
215
  last_sender=chat_data.get("last_sender"),
230
216
  last_is_from_me=chat_data.get("last_is_from_me"),
@@ -255,9 +241,7 @@ def search_contacts(query: str, user_id: str = "default_user") -> list[Contact]:
255
241
  return contacts
256
242
 
257
243
 
258
- def get_contact_chats(
259
- jid: str, limit: int = 20, page: int = 0, user_id: str = "default_user"
260
- ) -> list[Chat]:
244
+ def get_contact_chats(jid: str, limit: int = 20, page: int = 0, user_id: str = "default_user") -> list[Chat]:
261
245
  """Get all chats involving the contact via API."""
262
246
  params = {"jid": jid, "limit": limit, "page": page}
263
247
 
@@ -273,9 +257,7 @@ def get_contact_chats(
273
257
  chat = Chat(
274
258
  jid=chat_data["jid"],
275
259
  name=chat_data.get("name"),
276
- last_message_time=datetime.fromisoformat(chat_data["last_message_time"])
277
- if chat_data.get("last_message_time")
278
- else None,
260
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
279
261
  last_message=chat_data.get("last_message"),
280
262
  last_sender=chat_data.get("last_sender"),
281
263
  last_is_from_me=chat_data.get("last_is_from_me"),
@@ -295,9 +277,7 @@ def get_last_interaction(jid: str, user_id: str = "default_user") -> str:
295
277
  return result.get("message", "No interaction found")
296
278
 
297
279
 
298
- def get_chat(
299
- chat_jid: str, include_last_message: bool = True, user_id: str = "default_user"
300
- ) -> Chat | None:
280
+ def get_chat(chat_jid: str, include_last_message: bool = True, user_id: str = "default_user") -> Chat | None:
301
281
  """Get chat metadata by JID via API."""
302
282
  params = {"chat_jid": chat_jid, "include_last_message": include_last_message}
303
283
 
@@ -313,18 +293,14 @@ def get_chat(
313
293
  return Chat(
314
294
  jid=chat_data["jid"],
315
295
  name=chat_data.get("name"),
316
- last_message_time=datetime.fromisoformat(chat_data["last_message_time"])
317
- if chat_data.get("last_message_time")
318
- else None,
296
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
319
297
  last_message=chat_data.get("last_message"),
320
298
  last_sender=chat_data.get("last_sender"),
321
299
  last_is_from_me=chat_data.get("last_is_from_me"),
322
300
  )
323
301
 
324
302
 
325
- def get_direct_chat_by_contact(
326
- sender_phone_number: str, user_id: str = "default_user"
327
- ) -> Chat | None:
303
+ def get_direct_chat_by_contact(sender_phone_number: str, user_id: str = "default_user") -> Chat | None:
328
304
  """Get chat metadata by sender phone number via API."""
329
305
  result = _make_api_request(
330
306
  "direct_chat",
@@ -342,18 +318,14 @@ def get_direct_chat_by_contact(
342
318
  return Chat(
343
319
  jid=chat_data["jid"],
344
320
  name=chat_data.get("name"),
345
- last_message_time=datetime.fromisoformat(chat_data["last_message_time"])
346
- if chat_data.get("last_message_time")
347
- else None,
321
+ last_message_time=datetime.fromisoformat(chat_data["last_message_time"]) if chat_data.get("last_message_time") else None,
348
322
  last_message=chat_data.get("last_message"),
349
323
  last_sender=chat_data.get("last_sender"),
350
324
  last_is_from_me=chat_data.get("last_is_from_me"),
351
325
  )
352
326
 
353
327
 
354
- def send_message(
355
- recipient: str, message: str, user_id: str = "default_user"
356
- ) -> tuple[bool, str]:
328
+ def send_message(recipient: str, message: str, user_id: str = "default_user") -> tuple[bool, str]:
357
329
  """Send message via API."""
358
330
  payload = {"user_id": user_id, "recipient": recipient, "message": message}
359
331
 
@@ -365,9 +337,7 @@ def send_message(
365
337
  return result.get("success", False), result.get("message", "Unknown response")
366
338
 
367
339
 
368
- def send_file(
369
- recipient: str, media_path: str, user_id: str = "default_user"
370
- ) -> tuple[bool, str]:
340
+ def send_file(recipient: str, media_path: str, user_id: str = "default_user") -> tuple[bool, str]:
371
341
  """Send file via API."""
372
342
  payload = {"user_id": user_id, "recipient": recipient, "media_path": media_path}
373
343
 
@@ -379,9 +349,7 @@ def send_file(
379
349
  return result.get("success", False), result.get("message", "Unknown response")
380
350
 
381
351
 
382
- def send_audio_message(
383
- recipient: str, media_path: str, user_id: str = "default_user"
384
- ) -> tuple[bool, str]:
352
+ def send_audio_message(recipient: str, media_path: str, user_id: str = "default_user") -> tuple[bool, str]:
385
353
  """Send audio message via API."""
386
354
  if not media_path.endswith(".ogg"):
387
355
  try:
@@ -402,9 +370,7 @@ def send_audio_message(
402
370
  return result.get("success", False), result.get("message", "Unknown response")
403
371
 
404
372
 
405
- def download_media(
406
- message_id: str, chat_jid: str, user_id: str = "default_user"
407
- ) -> str | None:
373
+ def download_media(message_id: str, chat_jid: str, user_id: str = "default_user") -> str | None:
408
374
  """Download media from a message via API."""
409
375
  payload = {"user_id": user_id, "message_id": message_id, "chat_jid": chat_jid}
410
376