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.

Files changed (120) hide show
  1. universal_mcp/applications/ahrefs/app.py +92 -238
  2. universal_mcp/applications/airtable/app.py +23 -122
  3. universal_mcp/applications/apollo/app.py +122 -475
  4. universal_mcp/applications/asana/app.py +605 -1755
  5. universal_mcp/applications/aws_s3/app.py +36 -103
  6. universal_mcp/applications/bill/app.py +644 -2055
  7. universal_mcp/applications/box/app.py +1246 -4159
  8. universal_mcp/applications/braze/app.py +410 -1476
  9. universal_mcp/applications/browser_use/README.md +15 -1
  10. universal_mcp/applications/browser_use/__init__.py +1 -0
  11. universal_mcp/applications/browser_use/app.py +94 -37
  12. universal_mcp/applications/cal_com_v2/app.py +207 -625
  13. universal_mcp/applications/calendly/app.py +103 -242
  14. universal_mcp/applications/canva/app.py +75 -140
  15. universal_mcp/applications/clickup/app.py +331 -798
  16. universal_mcp/applications/coda/app.py +240 -520
  17. universal_mcp/applications/confluence/app.py +497 -1285
  18. universal_mcp/applications/contentful/app.py +36 -151
  19. universal_mcp/applications/crustdata/app.py +42 -121
  20. universal_mcp/applications/dialpad/app.py +451 -924
  21. universal_mcp/applications/digitalocean/app.py +2071 -6082
  22. universal_mcp/applications/domain_checker/app.py +3 -54
  23. universal_mcp/applications/e2b/app.py +14 -64
  24. universal_mcp/applications/elevenlabs/app.py +9 -47
  25. universal_mcp/applications/exa/README.md +8 -4
  26. universal_mcp/applications/exa/app.py +408 -186
  27. universal_mcp/applications/falai/app.py +24 -101
  28. universal_mcp/applications/figma/app.py +91 -175
  29. universal_mcp/applications/file_system/app.py +2 -13
  30. universal_mcp/applications/firecrawl/app.py +186 -163
  31. universal_mcp/applications/fireflies/app.py +59 -281
  32. universal_mcp/applications/fpl/app.py +92 -529
  33. universal_mcp/applications/fpl/utils/fixtures.py +15 -49
  34. universal_mcp/applications/fpl/utils/helper.py +25 -89
  35. universal_mcp/applications/fpl/utils/league_utils.py +20 -64
  36. universal_mcp/applications/ghost_content/app.py +66 -175
  37. universal_mcp/applications/github/app.py +28 -65
  38. universal_mcp/applications/gong/app.py +140 -300
  39. universal_mcp/applications/google_calendar/app.py +26 -78
  40. universal_mcp/applications/google_docs/app.py +324 -354
  41. universal_mcp/applications/google_drive/app.py +194 -793
  42. universal_mcp/applications/google_gemini/app.py +29 -64
  43. universal_mcp/applications/google_mail/README.md +1 -0
  44. universal_mcp/applications/google_mail/app.py +93 -214
  45. universal_mcp/applications/google_searchconsole/app.py +25 -58
  46. universal_mcp/applications/google_sheet/app.py +174 -623
  47. universal_mcp/applications/google_sheet/helper.py +26 -53
  48. universal_mcp/applications/hashnode/app.py +57 -269
  49. universal_mcp/applications/heygen/app.py +77 -155
  50. universal_mcp/applications/http_tools/app.py +10 -32
  51. universal_mcp/applications/hubspot/README.md +1 -1
  52. universal_mcp/applications/hubspot/app.py +7508 -99
  53. universal_mcp/applications/jira/app.py +2419 -8334
  54. universal_mcp/applications/klaviyo/app.py +737 -1619
  55. universal_mcp/applications/linkedin/README.md +23 -4
  56. universal_mcp/applications/linkedin/app.py +861 -155
  57. universal_mcp/applications/mailchimp/app.py +696 -1851
  58. universal_mcp/applications/markitdown/app.py +8 -20
  59. universal_mcp/applications/miro/app.py +333 -815
  60. universal_mcp/applications/ms_teams/app.py +85 -207
  61. universal_mcp/applications/neon/app.py +144 -250
  62. universal_mcp/applications/notion/app.py +36 -51
  63. universal_mcp/applications/onedrive/README.md +24 -0
  64. universal_mcp/applications/onedrive/__init__.py +1 -0
  65. universal_mcp/applications/onedrive/app.py +316 -0
  66. universal_mcp/applications/openai/app.py +42 -165
  67. universal_mcp/applications/outlook/README.md +22 -9
  68. universal_mcp/applications/outlook/app.py +606 -262
  69. universal_mcp/applications/perplexity/README.md +2 -1
  70. universal_mcp/applications/perplexity/app.py +162 -20
  71. universal_mcp/applications/pipedrive/app.py +1021 -3331
  72. universal_mcp/applications/posthog/app.py +272 -541
  73. universal_mcp/applications/reddit/app.py +88 -204
  74. universal_mcp/applications/resend/app.py +41 -107
  75. universal_mcp/applications/retell/app.py +23 -50
  76. universal_mcp/applications/rocketlane/app.py +250 -963
  77. universal_mcp/applications/scraper/README.md +7 -4
  78. universal_mcp/applications/scraper/app.py +245 -283
  79. universal_mcp/applications/semanticscholar/app.py +36 -78
  80. universal_mcp/applications/semrush/app.py +43 -77
  81. universal_mcp/applications/sendgrid/app.py +826 -1576
  82. universal_mcp/applications/sentry/app.py +444 -1079
  83. universal_mcp/applications/serpapi/app.py +40 -143
  84. universal_mcp/applications/sharepoint/README.md +16 -14
  85. universal_mcp/applications/sharepoint/app.py +245 -154
  86. universal_mcp/applications/shopify/app.py +1743 -4479
  87. universal_mcp/applications/shortcut/app.py +272 -534
  88. universal_mcp/applications/slack/app.py +58 -109
  89. universal_mcp/applications/spotify/app.py +206 -405
  90. universal_mcp/applications/supabase/app.py +174 -283
  91. universal_mcp/applications/tavily/app.py +2 -2
  92. universal_mcp/applications/trello/app.py +853 -2816
  93. universal_mcp/applications/twilio/app.py +14 -50
  94. universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
  95. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
  96. universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
  97. universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
  98. universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
  99. universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
  100. universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
  101. universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
  102. universal_mcp/applications/whatsapp/app.py +35 -186
  103. universal_mcp/applications/whatsapp/audio.py +2 -6
  104. universal_mcp/applications/whatsapp/whatsapp.py +17 -51
  105. universal_mcp/applications/whatsapp_business/app.py +86 -299
  106. universal_mcp/applications/wrike/app.py +80 -153
  107. universal_mcp/applications/yahoo_finance/app.py +19 -65
  108. universal_mcp/applications/youtube/app.py +120 -306
  109. universal_mcp/applications/zenquotes/app.py +4 -4
  110. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/METADATA +4 -2
  111. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/RECORD +113 -117
  112. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/WHEEL +1 -1
  113. universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
  114. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
  115. universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
  116. universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
  117. universal_mcp/applications/unipile/README.md +0 -28
  118. universal_mcp/applications/unipile/__init__.py +0 -1
  119. universal_mcp/applications/unipile/app.py +0 -1077
  120. {universal_mcp_applications-0.1.22.dist-info → universal_mcp_applications-0.1.39rc8.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,5 @@
1
1
  from typing import Any
2
2
  from urllib.parse import parse_qs, urlparse
3
-
4
3
  from universal_mcp.applications.application import APIApplication
5
4
  from universal_mcp.integrations import Integration
6
5
 
@@ -10,163 +9,146 @@ class OutlookApp(APIApplication):
10
9
  super().__init__(name="outlook", integration=integration, **kwargs)
11
10
  self.base_url = "https://graph.microsoft.com/v1.0"
12
11
 
13
- def reply_to_message(
14
- self,
15
- message_id: str,
16
- user_id: str | None = None,
17
- comment: str | None = None,
18
- message: dict[str, Any] | None = None,
19
- ) -> Any:
12
+ async def _get_user_id(self) -> str:
13
+ """Helper to get the userPrincipalName from the profile."""
14
+ user_info = await self.get_my_profile()
15
+ user_id = user_info.get("userPrincipalName")
16
+ if not user_id:
17
+ raise ValueError("Could not retrieve user ID from get_my_profile response.")
18
+ return user_id
19
+
20
+ async def reply_to_email(
21
+ self, message_id: str, comment: str, attachments: list[dict[str, Any]] | None = None
22
+ ) -> dict[str, Any]:
20
23
  """
21
- Replies to an email using its message ID, with either a simple comment or a full message object including attachments. Unlike `send_mail`, which creates a new email, this function targets an existing message. It defaults to the current user if no user ID is specified.
24
+ Replies to a specific email message.
22
25
 
23
26
  Args:
24
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
25
- message_id (string): message-id
26
- comment (string): A comment to include in the reply. Example: 'Thank you for your email. Here is my reply.'.
27
- message (object): A message object to specify additional properties for the reply, such as attachments. Example: {'subject': 'RE: Project Update', 'body': {'contentType': 'Text', 'content': 'Thank you for the update. Looking forward to the next steps.'}, 'toRecipients': [{'emailAddress': {'address': 'alice@contoso.com'}}], 'attachments': [{'@odata.type': '#microsoft.graph.fileAttachment', 'name': 'agenda.pdf', 'contentType': 'application/pdf', 'contentBytes': 'SGVsbG8gV29ybGQh'}]}.
27
+ message_id (str): The ID of the email message to reply to.
28
+ comment (str): The body of the reply.
29
+ attachments (list[dict[str, Any]], optional): A list of attachment objects to include in the reply.
30
+ Each attachment dictionary should conform to the Microsoft Graph API specification.
31
+ Example:
32
+ [
33
+ {
34
+ "@odata.type": "#microsoft.graph.fileAttachment",
35
+ "name": "attachment.txt",
36
+ "contentType": "text/plain",
37
+ "contentBytes": "SGVsbG8gV29ybGQh"
38
+ }
39
+ ]
28
40
 
29
41
  Returns:
30
- Any: Success
42
+ dict[str, Any]: A dictionary confirming the reply action.
31
43
 
32
44
  Raises:
33
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
45
+ HTTPStatusError: If the API request fails.
46
+ ValueError: If message_id is missing.
34
47
 
35
48
  Tags:
36
- users.message, important
37
- """
38
- # If user_id is not provided, get it automatically
39
- if user_id is None:
40
- user_info = self.get_current_user_profile()
41
- user_id = user_info.get("userPrincipalName")
42
- if not user_id:
43
- raise ValueError(
44
- "Could not retrieve user ID from get_current_user_profile response."
45
- )
46
- if message_id is None:
47
- raise ValueError("Missing required parameter 'message-id'.")
48
- request_body_data = None
49
- request_body_data = {
50
- "comment": comment,
51
- "message": message,
52
- }
53
- request_body_data = {
54
- k: v for k, v in request_body_data.items() if v is not None
55
- }
49
+ important
50
+ """
51
+ user_id = await self._get_user_id()
52
+ if not message_id:
53
+ raise ValueError("Missing required parameter 'message_id'.")
54
+ request_body_data = {"comment": comment}
55
+ if attachments:
56
+ request_body_data["message"] = {"attachments": attachments}
56
57
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/reply"
57
- query_params = {}
58
- response = self._post(
59
- url,
60
- data=request_body_data,
61
- params=query_params,
62
- content_type="application/json",
63
- )
58
+ response = await self._apost(url, data=request_body_data, params={}, content_type="application/json")
64
59
  return self._handle_response(response)
65
60
 
66
- def send_mail(
61
+ async def send_email(
67
62
  self,
68
- message: dict[str, Any],
69
- user_id: str | None = None,
70
- saveToSentItems: bool | None = None,
71
- ) -> Any:
63
+ subject: str,
64
+ body: str,
65
+ to_recipients: list[str],
66
+ cc_recipients: list[str] | None = None,
67
+ bcc_recipients: list[str] | None = None,
68
+ attachments: list[dict[str, Any]] | None = None,
69
+ body_content_type: str = "Text",
70
+ save_to_sent_items: bool = True,
71
+ ) -> dict[str, Any]:
72
72
  """
73
- Sends a new email on behalf of a specified or current user, using a dictionary for content like recipients and subject. Unlike `reply_to_message`, which replies to an existing message, this function composes and sends an entirely new email from scratch.
73
+ Sends a new email.
74
74
 
75
75
  Args:
76
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
77
- message (object): message Example: {'subject': 'Meet for lunch?', 'body': {'contentType': 'Text', 'content': 'The new cafeteria is open.'}, 'toRecipients': [{'emailAddress': {'address': 'frannis@contoso.com'}}], 'ccRecipients': [{'emailAddress': {'address': 'danas@contoso.com'}}], 'bccRecipients': [{'emailAddress': {'address': 'bccuser@contoso.com'}}], 'attachments': [{'@odata.type': '#microsoft.graph.fileAttachment', 'name': 'attachment.txt', 'contentType': 'text/plain', 'contentBytes': 'SGVsbG8gV29ybGQh'}]}.
78
- saveToSentItems (boolean): saveToSentItems Example: 'False'.
76
+ subject (str): The subject of the email.
77
+ body (str): The body of the email.
78
+ to_recipients (list[str]): A list of email addresses for the 'To' recipients.
79
+ cc_recipients (list[str], optional): A list of email addresses for the 'Cc' recipients.
80
+ bcc_recipients (list[str], optional): A list of email addresses for the 'Bcc' recipients.
81
+ attachments (list[dict[str, Any]], optional): A list of attachment objects. See `reply_to_email` for an example.
82
+ body_content_type (str, optional): The content type of the email body, e.g., "Text" or "HTML". Defaults to "Text".
83
+ save_to_sent_items (bool, optional): Whether to save the email to the 'Sent Items' folder. Defaults to True.
79
84
 
80
85
  Returns:
81
- Any: Success
86
+ dict[str, Any]: A dictionary confirming the send action.
82
87
 
83
88
  Raises:
84
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
89
+ HTTPStatusError: If the API request fails.
85
90
 
86
91
  Tags:
87
- users.user.Actions, important
88
- """
89
- # If user_id is not provided, get it automatically
90
- if user_id is None:
91
- user_info = self.get_current_user_profile()
92
- user_id = user_info.get("userPrincipalName")
93
- if not user_id:
94
- raise ValueError(
95
- "Could not retrieve user ID from get_current_user_profile response."
96
- )
97
- request_body_data = None
98
- request_body_data = {
99
- "message": message,
100
- "saveToSentItems": saveToSentItems,
101
- }
102
- request_body_data = {
103
- k: v for k, v in request_body_data.items() if v is not None
92
+ important
93
+ """
94
+ user_id = await self._get_user_id()
95
+ message = {
96
+ "subject": subject,
97
+ "body": {"contentType": body_content_type, "content": body},
98
+ "toRecipients": [{"emailAddress": {"address": email}} for email in to_recipients],
104
99
  }
100
+ if cc_recipients:
101
+ message["ccRecipients"] = [{"emailAddress": {"address": email}} for email in cc_recipients]
102
+ if bcc_recipients:
103
+ message["bccRecipients"] = [{"emailAddress": {"address": email}} for email in bcc_recipients]
104
+ if attachments:
105
+ message["attachments"] = attachments
106
+ request_body_data = {"message": message, "saveToSentItems": save_to_sent_items}
105
107
  url = f"{self.base_url}/users/{user_id}/sendMail"
106
- query_params = {}
107
- response = self._post(
108
- url,
109
- data=request_body_data,
110
- params=query_params,
111
- content_type="application/json",
112
- )
108
+ response = await self._apost(url, data=request_body_data, params={}, content_type="application/json")
113
109
  return self._handle_response(response)
114
110
 
115
- def get_mail_folder(
111
+ async def get_email_folder(
116
112
  self,
117
- mailFolder_id: str,
118
- user_id: str | None = None,
119
- includeHiddenFolders: str | None = None,
113
+ folder_id: str,
114
+ include_hidden: bool | None = None,
120
115
  select: list[str] | None = None,
121
116
  expand: list[str] | None = None,
122
- ) -> Any:
117
+ ) -> dict[str, Any]:
123
118
  """
124
- Retrieves a specific mail folder's metadata by its ID for a given user. The response can be customized to include hidden folders or select specific properties. Unlike `list_user_messages`, this function fetches folder details, not the emails contained within it.
119
+ Retrieves a specific email folder's metadata by its ID.
125
120
 
126
121
  Args:
127
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
128
- mailFolder_id (string): mailFolder-id
129
- includeHiddenFolders (string): Include Hidden Folders
130
- select (array): Select properties to be returned
131
- expand (array): Expand related entities
122
+ folder_id (str): The unique identifier for the mail folder.
123
+ include_hidden (bool, optional): If true, includes hidden folders in the results.
124
+ select (list[str], optional): A list of properties to return.
125
+ expand (list[str], optional): A list of related entities to expand.
132
126
 
133
127
  Returns:
134
- Any: Retrieved navigation property
128
+ dict[str, Any]: A dictionary containing the mail folder's metadata.
135
129
 
136
130
  Raises:
137
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
138
-
131
+ HTTPStatusError: If the API request fails.
132
+ ValueError: If folder_id is missing.
139
133
  Tags:
140
- users.mailFolder, important
141
- """
142
- # If user_id is not provided, get it automatically
143
- if user_id is None:
144
- user_info = self.get_current_user_profile()
145
- user_id = user_info.get("userPrincipalName")
146
- if not user_id:
147
- raise ValueError(
148
- "Could not retrieve user ID from get_current_user_profile response."
149
- )
150
- if mailFolder_id is None:
151
- raise ValueError("Missing required parameter 'mailFolder-id'.")
152
- url = f"{self.base_url}/users/{user_id}/mailFolders/{mailFolder_id}"
134
+ important
135
+ """
136
+ user_id = await self._get_user_id()
137
+ if not folder_id:
138
+ raise ValueError("Missing required parameter 'folder_id'.")
139
+ url = f"{self.base_url}/users/{user_id}/mailFolders/{folder_id}"
140
+ select_str = ",".join(select) if select else None
141
+ expand_str = ",".join(expand) if expand else None
153
142
  query_params = {
154
- k: v
155
- for k, v in [
156
- ("includeHiddenFolders", includeHiddenFolders),
157
- ("$select", select),
158
- ("$expand", expand),
159
- ]
160
- if v is not None
143
+ k: v for k, v in [("includeHiddenFolders", include_hidden), ("$select", select_str), ("$expand", expand_str)] if v is not None
161
144
  }
162
- response = self._get(url, params=query_params)
145
+ response = await self._aget(url, params=query_params)
163
146
  return self._handle_response(response)
164
147
 
165
- def list_user_messages(
148
+ async def list_emails(
166
149
  self,
167
- user_id: str | None = None,
168
150
  select: list[str] = ["bodyPreview"],
169
- includeHiddenMessages: bool | None = None,
151
+ include_hidden: bool | None = None,
170
152
  top: int | None = None,
171
153
  skip: int | None = None,
172
154
  search: str | None = None,
@@ -176,56 +158,44 @@ class OutlookApp(APIApplication):
176
158
  expand: list[str] | None = None,
177
159
  ) -> dict[str, Any]:
178
160
  """
179
- Retrieves a list of messages from a user's mailbox. This function supports powerful querying using optional parameters for filtering, searching, sorting, and pagination, unlike `get_user_message`, which fetches a single email by its ID.
161
+ Retrieves a list of emails from a user's mailbox.
180
162
 
181
163
  Args:
182
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
183
- select (list): Select properties to be returned. Defaults to ['bodyPreview'].
184
- Example: [
185
- 'id', 'categories', 'receivedDateTime', 'sentDateTime', 'hasAttachments', 'internetMessageId',
186
- 'subject', 'body', 'bodyPreview', 'importance', 'parentFolderId', 'conversationId',
187
- 'conversationIndex', 'isDeliveryReceiptRequested', 'isReadReceiptRequested', 'isRead', 'isDraft',
188
- 'webLink', 'inferenceClassification', 'sender', 'from', 'toRecipients', 'ccRecipients',
189
- 'bccRecipients', 'replyTo', 'flag', 'attachments', 'extensions', 'mentions', 'uniqueBody'
190
- ]
191
- includeHiddenMessages (boolean): Include Hidden Messages
192
- top (integer): Specify the number of items to be included in the result Example: '50'.
193
- skip (integer): Specify the number of items to skip in the result Example: '10'.
194
- search (string): Search items by search phrases
195
- filter (string): Filter items by property values
196
- count (boolean): Include count of items
197
- orderby (array): Order items by property values
198
- expand (array): Expand related entities
164
+ select (list[str], optional): A list of properties to return for each email. Defaults to ['bodyPreview'].
165
+ include_hidden (bool, optional): If true, includes hidden messages.
166
+ top (int, optional): The maximum number of emails to return.
167
+ skip (int, optional): The number of emails to skip. Cannot be used with 'search'.
168
+ search (str, optional): A search query. Cannot be used with 'filter', 'orderby', or 'skip'.
169
+ filter (str, optional): A filter query. Cannot be used with 'search'.
170
+ count (bool, optional): If true, includes the total count of emails in the response.
171
+ orderby (list[str], optional): A list of properties to sort the results by. Cannot be used with 'search'.
172
+ expand (list[str], optional): A list of related entities to expand.
199
173
 
200
174
  Returns:
201
- dict[str, Any]: Retrieved collection
175
+ dict[str, Any]: A dictionary containing a list of emails and pagination information.
202
176
 
203
177
  Raises:
204
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
205
-
178
+ ValueError: If incompatible parameters are used together (e.g., 'search' with 'filter').
179
+ HTTPStatusError: If the API request fails.
206
180
  Tags:
207
- users.message, important
181
+ important
208
182
  """
209
- # If user_id is not provided, get it automatically
210
- if user_id is None:
211
- user_info = self.get_current_user_profile()
212
- user_id = user_info.get("userPrincipalName")
213
- if not user_id:
214
- raise ValueError(
215
- "Could not retrieve user ID from get_current_user_profile response."
216
- )
217
-
183
+ if search:
184
+ if filter:
185
+ raise ValueError("The 'search' parameter cannot be used with 'filter'.")
186
+ if orderby:
187
+ raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
188
+ if skip:
189
+ raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
190
+ user_id = await self._get_user_id()
218
191
  url = f"{self.base_url}/users/{user_id}/messages"
219
-
220
- # Handle list parameters by joining with commas
221
192
  select_str = ",".join(select) if select else None
222
193
  orderby_str = ",".join(orderby) if orderby else None
223
194
  expand_str = ",".join(expand) if expand else None
224
-
225
195
  query_params = {
226
196
  k: v
227
197
  for k, v in [
228
- ("includeHiddenMessages", includeHiddenMessages),
198
+ ("includeHiddenMessages", include_hidden),
229
199
  ("$top", top),
230
200
  ("$skip", skip),
231
201
  ("$search", search),
@@ -237,96 +207,72 @@ class OutlookApp(APIApplication):
237
207
  ]
238
208
  if v is not None
239
209
  }
240
-
241
- response = self._get(url, params=query_params)
210
+ response = await self._aget(url, params=query_params)
242
211
  return self._handle_response(response)
243
212
 
244
- def get_user_message(
213
+ async def get_email(
245
214
  self,
246
215
  message_id: str,
247
- user_id: str | None = None,
248
- includeHiddenMessages: str | None = None,
216
+ include_hidden: bool | None = None,
249
217
  select: list[str] | None = None,
250
218
  expand: list[str] | None = None,
251
- ) -> Any:
219
+ ) -> dict[str, Any]:
252
220
  """
253
- Retrieves a specific email message by its ID for a given user, with options to select specific fields or expand related data. Unlike `list_user_messages`, which fetches a collection of emails with advanced filtering, this function is designed to retrieve a single, known message.
221
+ Retrieves a specific email by its ID.
254
222
 
255
223
  Args:
256
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
257
- message_id (string): message-id
258
- includeHiddenMessages (string): Include Hidden Messages
259
- select (array): Select properties to be returned
260
- expand (array): Expand related entities
224
+ message_id (str): The unique identifier for the email.
225
+ include_hidden (bool, optional): If true, includes hidden messages.
226
+ select (list[str], optional): A list of properties to return.
227
+ expand (list[str], optional): A list of related entities to expand.
261
228
 
262
229
  Returns:
263
- Any: Retrieved navigation property
230
+ dict[str, Any]: A dictionary containing the email's details.
264
231
 
265
232
  Raises:
266
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
267
-
233
+ HTTPStatusError: If the API request fails.
234
+ ValueError: If user_id cannot be retrieved or message_id is missing.
268
235
  Tags:
269
- users.message, important
270
- """
271
- # If user_id is not provided, get it automatically
272
- if user_id is None:
273
- user_info = self.get_current_user_profile()
274
- user_id = user_info.get("userPrincipalName")
275
- if not user_id:
276
- raise ValueError(
277
- "Could not retrieve user ID from get_current_user_profile response."
278
- )
279
- if message_id is None:
280
- raise ValueError("Missing required parameter 'message-id'.")
236
+ important
237
+ """
238
+ user_id = await self._get_user_id()
239
+ if not message_id:
240
+ raise ValueError("Missing required parameter 'message_id'.")
281
241
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
242
+ select_str = ",".join(select) if select else None
243
+ expand_str = ",".join(expand) if expand else None
282
244
  query_params = {
283
- k: v
284
- for k, v in [
285
- ("includeHiddenMessages", includeHiddenMessages),
286
- ("$select", select),
287
- ("$expand", expand),
288
- ]
289
- if v is not None
245
+ k: v for k, v in [("includeHiddenMessages", include_hidden), ("$select", select_str), ("$expand", expand_str)] if v is not None
290
246
  }
291
- response = self._get(url, params=query_params)
247
+ response = await self._aget(url, params=query_params)
292
248
  return self._handle_response(response)
293
249
 
294
- def user_delete_message(self, message_id: str, user_id: str | None = None) -> Any:
250
+ async def delete_email(self, message_id: str) -> dict[str, Any]:
295
251
  """
296
- Permanently deletes a specific email, identified by `message_id`, from a user's mailbox. If `user_id` is not provided, it defaults to the current authenticated user. Unlike retrieval functions such as `get_user_message`, this performs a destructive action to remove the specified email from Outlook.
252
+ Permanently deletes a specific email by its ID.
297
253
 
298
254
  Args:
299
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
300
- message_id (string): message-id
255
+ message_id (str): The unique identifier for the email to be deleted.
301
256
 
302
257
  Returns:
303
- Any: Success
258
+ dict[str, Any]: A dictionary confirming the deletion.
304
259
 
305
260
  Raises:
306
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
307
-
261
+ HTTPStatusError: If the API request fails.
262
+ ValueError: If message_id is missing.
308
263
  Tags:
309
- users.message, important
310
- """
311
- # If user_id is not provided, get it automatically
312
- if user_id is None:
313
- user_info = self.get_current_user_profile()
314
- user_id = user_info.get("userPrincipalName")
315
- if not user_id:
316
- raise ValueError(
317
- "Could not retrieve user ID from get_current_user_profile response."
318
- )
319
- if message_id is None:
320
- raise ValueError("Missing required parameter 'message-id'.")
264
+ important
265
+ """
266
+ user_id = await self._get_user_id()
267
+ if not message_id:
268
+ raise ValueError("Missing required parameter 'message_id'.")
321
269
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}"
322
- query_params = {}
323
- response = self._delete(url, params=query_params)
270
+ response = await self._adelete(url, params={})
324
271
  return self._handle_response(response)
325
272
 
326
- def list_message_attachments(
273
+ async def list_email_attachments(
327
274
  self,
328
275
  message_id: str,
329
- user_id: str | None = None,
330
276
  top: int | None = None,
331
277
  skip: int | None = None,
332
278
  search: str | None = None,
@@ -337,40 +283,42 @@ class OutlookApp(APIApplication):
337
283
  expand: list[str] | None = None,
338
284
  ) -> dict[str, Any]:
339
285
  """
340
- Retrieves attachments for a specific email message, identified by its ID. Supports advanced querying for filtering, sorting, and pagination, allowing users to select specific fields to return in the result set, focusing only on attachments rather than the full message content.
286
+ Retrieves attachments for a specific email.
341
287
 
342
288
  Args:
343
- user_id (string, optional): user-id. If not provided, will automatically get the current user's ID.
344
- message_id (string): message-id
345
- top (integer): Show only the first n items Example: '50'.
346
- skip (integer): Skip the first n items
347
- search (string): Search items by search phrases
348
- filter (string): Filter items by property values
349
- count (boolean): Include count of items
350
- orderby (array): Order items by property values
351
- select (array): Select properties to be returned
352
- expand (array): Expand related entities
289
+ message_id (str): The unique identifier for the email.
290
+ top (int, optional): The maximum number of attachments to return.
291
+ skip (int, optional): The number of attachments to skip. Cannot be used with 'search'.
292
+ search (str, optional): A search query. Cannot be used with 'filter', 'orderby', or 'skip'.
293
+ filter (str, optional): A filter query. Cannot be used with 'search'.
294
+ count (bool, optional): If true, includes the total count of attachments.
295
+ orderby (list[str], optional): A list of properties to sort by. Cannot be used with 'search'.
296
+ select (list[str], optional): A list of properties to return.
297
+ expand (list[str], optional): A list of related entities to expand.
353
298
 
354
299
  Returns:
355
- dict[str, Any]: Retrieved collection
300
+ dict[str, Any]: A dictionary containing a list of attachments.
356
301
 
357
302
  Raises:
358
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
359
-
303
+ ValueError: If incompatible parameters are used together.
304
+ HTTPStatusError: If the API request fails.
360
305
  Tags:
361
- users.message, important
362
- """
363
- # If user_id is not provided, get it automatically
364
- if user_id is None:
365
- user_info = self.get_current_user_profile()
366
- user_id = user_info.get("userPrincipalName")
367
- if not user_id:
368
- raise ValueError(
369
- "Could not retrieve user ID from get_current_user_profile response."
370
- )
371
- if message_id is None:
372
- raise ValueError("Missing required parameter 'message-id'.")
306
+ important
307
+ """
308
+ if search:
309
+ if filter:
310
+ raise ValueError("The 'search' parameter cannot be used with 'filter'.")
311
+ if orderby:
312
+ raise ValueError("The 'search' parameter cannot be used with 'orderby'.")
313
+ if skip:
314
+ raise ValueError("The 'search' parameter cannot be used with 'skip'. Use pagination via @odata.nextLink instead.")
315
+ user_id = await self._get_user_id()
316
+ if not message_id:
317
+ raise ValueError("Missing required parameter 'message_id'.")
373
318
  url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments"
319
+ orderby_str = ",".join(orderby) if orderby else None
320
+ select_str = ",".join(select) if select else None
321
+ expand_str = ",".join(expand) if expand else None
374
322
  query_params = {
375
323
  k: v
376
324
  for k, v in [
@@ -379,64 +327,460 @@ class OutlookApp(APIApplication):
379
327
  ("$search", search),
380
328
  ("$filter", filter),
381
329
  ("$count", count),
382
- ("$orderby", orderby),
383
- ("$select", select),
384
- ("$expand", expand),
330
+ ("$orderby", orderby_str),
331
+ ("$select", select_str),
332
+ ("$expand", expand_str),
385
333
  ]
386
334
  if v is not None
387
335
  }
388
- response = self._get(url, params=query_params)
336
+ response = await self._aget(url, params=query_params)
389
337
  return self._handle_response(response)
390
338
 
391
- def get_current_user_profile(
339
+ async def get_attachment(self, message_id: str, attachment_id: str) -> dict[str, Any]:
340
+ """
341
+ Retrieves a specific attachment from an email message and formats it as a dictionary.
342
+
343
+ Args:
344
+ message_id (str): The ID of the email message.
345
+ attachment_id (str): The ID of the attachment.
346
+
347
+ Returns:
348
+ dict[str, Any]: A dictionary containing the attachment details:
349
+ - 'type' (str): The general type of the attachment (e.g., "image", "audio", "video", "file").
350
+ - 'data' (str): The base64 encoded content of the attachment.
351
+ - 'mime_type' (str): The MIME type of the attachment.
352
+ - 'file_name' (str): The name of the attachment file.
353
+ Tags:
354
+ important
355
+ """
356
+ user_id = await self._get_user_id()
357
+ if not message_id or not attachment_id:
358
+ raise ValueError("Missing required parameter 'message_id' or 'attachment_id'.")
359
+ url = f"{self.base_url}/users/{user_id}/messages/{message_id}/attachments/{attachment_id}"
360
+ response = await self._aget(url, params={})
361
+ attachment_data = self._handle_response(response)
362
+ content_type = attachment_data.get("contentType", "application/octet-stream")
363
+ attachment_type = content_type.split("/")[0] if "/" in content_type else "file"
364
+ if attachment_type not in ["image", "audio", "video", "text"]:
365
+ attachment_type = "file"
366
+ return {
367
+ "type": attachment_type,
368
+ "data": attachment_data.get("contentBytes"),
369
+ "mime_type": content_type,
370
+ "file_name": attachment_data.get("name"),
371
+ }
372
+
373
+ async def get_my_profile(self) -> dict[str, Any]:
374
+ """
375
+ Fetches the userPrincipalName for the currently authenticated user.
376
+
377
+ Returns:
378
+ dict[str, Any]: A dictionary containing the user's principal name.
379
+
380
+ Raises:
381
+ HTTPStatusError: If the API request fails.
382
+ """
383
+ url = f"{self.base_url}/me"
384
+ query_params = {"$select": "userPrincipalName"}
385
+ response = await self._aget(url, params=query_params)
386
+ return self._handle_response(response)
387
+
388
+ async def list_calendars(
392
389
  self,
390
+ top: int | None = None,
391
+ skip: int | None = None,
392
+ select: list[str] | None = None,
393
393
  ) -> dict[str, Any]:
394
394
  """
395
- Fetches the `userPrincipalName` for the currently authenticated user from the `/me` endpoint. This internal helper is used by other methods to automatically obtain the user's ID for API calls when a `user_id` is not explicitly provided.
395
+ Retrieves a list of calendars for the user.
396
396
 
397
+ Args:
398
+ top (int, optional): The maximum number of calendars to return.
399
+ skip (int, optional): The number of calendars to skip.
400
+ select (list[str], optional): A list of properties to return for each calendar.
397
401
 
398
402
  Returns:
399
- dict[str, Any]: Current user information
403
+ dict[str, Any]: A dictionary containing a list of calendars.
400
404
 
401
- Raises:
402
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.
405
+ Tags:
406
+ important
407
+ """
408
+ user_id = await self._get_user_id()
409
+ url = f"{self.base_url}/users/{user_id}/calendars"
410
+ select_str = ",".join(select) if select else None
411
+ query_params = {k: v for k, v in [("$top", top), ("$skip", skip), ("$select", select_str)] if v is not None}
412
+ response = await self._aget(url, params=query_params)
413
+ return self._handle_response(response)
414
+
415
+ async def get_calendar(
416
+ self, calendar_id: str, select: list[str] | None = None
417
+ ) -> dict[str, Any]:
418
+ """
419
+ Retrieves a specific calendar by its ID.
420
+
421
+ Args:
422
+ calendar_id (str): The unique identifier for the calendar.
423
+ select (list[str], optional): A list of properties to return.
424
+
425
+ Returns:
426
+ dict[str, Any]: A dictionary containing the calendar's details.
403
427
 
404
428
  Tags:
405
- me, important
429
+ important
406
430
  """
407
- url = f"{self.base_url}/me"
431
+ user_id = await self._get_user_id()
432
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}"
433
+ select_str = ",".join(select) if select else None
434
+ query_params = {"$select": select_str} if select_str else {}
435
+ response = await self._aget(url, params=query_params)
436
+ return self._handle_response(response)
437
+
438
+ async def create_calendar(self, name: str) -> dict[str, Any]:
439
+ """
440
+ Creates a new calendar for the user.
441
+
442
+ Args:
443
+ name (str): The name of the new calendar.
444
+
445
+ Returns:
446
+ dict[str, Any]: A dictionary containing the created calendar's details.
447
+
448
+ Tags:
449
+ important
450
+ """
451
+ user_id = await self._get_user_id()
452
+ url = f"{self.base_url}/users/{user_id}/calendars"
453
+ request_body = {"name": name}
454
+ response = await self._apost(url, data=request_body, params={}, content_type="application/json")
455
+ return self._handle_response(response)
456
+
457
+ async def update_calendar(self, calendar_id: str, name: str) -> dict[str, Any]:
458
+ """
459
+ Updates an existing calendar's name.
460
+
461
+ Args:
462
+ calendar_id (str): The unique identifier for the calendar.
463
+ name (str): The new name for the calendar.
464
+
465
+ Returns:
466
+ dict[str, Any]: A dictionary containing the updated calendar's details.
467
+
468
+ Tags:
469
+ important
470
+ """
471
+ user_id = await self._get_user_id()
472
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}"
473
+ request_body = {"name": name}
474
+ response = await self._apatch(url, data=request_body, params={}, content_type="application/json")
475
+ return self._handle_response(response)
476
+
477
+ async def delete_calendar(self, calendar_id: str) -> dict[str, Any]:
478
+ """
479
+ Deletes a specific calendar.
480
+
481
+ Args:
482
+ calendar_id (str): The unique identifier for the calendar to delete.
483
+
484
+ Returns:
485
+ dict[str, Any]: A dictionary confirming the deletion.
486
+
487
+ Tags:
488
+ important
489
+ """
490
+ user_id = await self._get_user_id()
491
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}"
492
+ response = await self._adelete(url, params={})
493
+ return self._handle_response(response)
494
+
495
+ async def list_events(
496
+ self,
497
+ calendar_id: str | None = None,
498
+ top: int | None = None,
499
+ skip: int | None = None,
500
+ filter: str | None = None,
501
+ select: list[str] | None = None,
502
+ orderby: list[str] | None = None,
503
+ ) -> dict[str, Any]:
504
+ """
505
+ Retrieves a list of events from a calendar or the user's default calendar.
506
+
507
+ Args:
508
+ calendar_id (str, optional): The ID of the calendar. If not provided, the default calendar is used.
509
+ top (int, optional): The maximum number of events to return.
510
+ skip (int, optional): The number of events to skip.
511
+ filter (str, optional): A filter query (e.g., "start/dateTime ge '2023-01-01T00:00:00Z'").
512
+ select (list[str], optional): A list of properties to return for each event.
513
+ orderby (list[str], optional): A list of properties to sort the results by.
514
+
515
+ Returns:
516
+ dict[str, Any]: A dictionary containing a list of events.
517
+
518
+ Tags:
519
+ important
520
+ """
521
+ user_id = await self._get_user_id()
522
+ if calendar_id:
523
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}/events"
524
+ else:
525
+ url = f"{self.base_url}/users/{user_id}/calendar/events"
526
+ select_str = ",".join(select) if select else None
527
+ orderby_str = ",".join(orderby) if orderby else None
408
528
  query_params = {
409
- "$select": "userPrincipalName",
529
+ k: v
530
+ for k, v in [("$top", top), ("$skip", skip), ("$filter", filter), ("$select", select_str), ("$orderby", orderby_str)]
531
+ if v is not None
410
532
  }
411
- response = self._get(url, params=query_params)
533
+ response = await self._aget(url, params=query_params)
412
534
  return self._handle_response(response)
413
535
 
414
- def get_next_page(self, url: str) -> dict[str, Any]:
536
+ async def get_event(
537
+ self, event_id: str, select: list[str] | None = None
538
+ ) -> dict[str, Any]:
415
539
  """
416
- Executes a GET request for a full URL, typically the `@odata.nextLink` from a previous paginated API response. It simplifies retrieving subsequent pages of data from list functions by handling URL validation and parsing before fetching the results for the next page.
540
+ Retrieves a specific event by its ID.
541
+
542
+ Args:
543
+ event_id (str): The unique identifier for the event.
544
+ select (list[str], optional): A list of properties to return.
545
+
546
+ Returns:
547
+ dict[str, Any]: A dictionary containing the event's details.
548
+
549
+ Tags:
550
+ important
551
+ """
552
+ user_id = await self._get_user_id()
553
+ url = f"{self.base_url}/users/{user_id}/events/{event_id}"
554
+ select_str = ",".join(select) if select else None
555
+ query_params = {"$select": select_str} if select_str else {}
556
+ response = await self._aget(url, params=query_params)
557
+ return self._handle_response(response)
558
+
559
+ async def create_event(
560
+ self,
561
+ subject: str,
562
+ start_datetime: str,
563
+ end_datetime: str,
564
+ start_timezone: str = "UTC",
565
+ end_timezone: str = "UTC",
566
+ body: str | None = None,
567
+ body_content_type: str = "HTML",
568
+ location_display_name: str | None = None,
569
+ attendees: list[dict[str, Any]] | None = None,
570
+ calendar_id: str | None = None,
571
+ **kwargs,
572
+ ) -> dict[str, Any]:
573
+ """
574
+ Creates a new event in a calendar.
575
+
576
+ Args:
577
+ subject (str): The subject of the event.
578
+ start_datetime (str): The start time of the event (ISO 8601 string, e.g., '2023-12-25T09:00:00').
579
+ end_datetime (str): The end time of the event (ISO 8601 string, e.g., '2023-12-25T10:00:00').
580
+ start_timezone (str, optional): The timezone for the start time. Defaults to 'UTC'.
581
+ end_timezone (str, optional): The timezone for the end time. Defaults to 'UTC'.
582
+ body (str, optional): The body content of the event.
583
+ body_content_type (str, optional): The content type of the body (Text or HTML). Defaults to 'HTML'.
584
+ location_display_name (str, optional): The display name for the event location.
585
+ attendees (list[dict[str, Any]], optional): A list of attendee objects.
586
+ Example attendee: {"type": "required", "emailAddress": {"address": "bob@example.com", "name": "Bob"}}
587
+ calendar_id (str, optional): The ID of the calendar. If not provided, the default calendar is used.
588
+ **kwargs: Additional properties for the event (e.g., isOnlineMeeting, reminderMinutesBeforeStart).
589
+
590
+ Returns:
591
+ dict[str, Any]: A dictionary containing the created event's details.
592
+
593
+ Tags:
594
+ important
595
+ """
596
+ user_id = await self._get_user_id()
597
+ if calendar_id:
598
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}/events"
599
+ else:
600
+ url = f"{self.base_url}/users/{user_id}/calendar/events"
601
+ request_body = {
602
+ "subject": subject,
603
+ "start": {"dateTime": start_datetime, "timeZone": start_timezone},
604
+ "end": {"dateTime": end_datetime, "timeZone": end_timezone},
605
+ }
606
+ if body:
607
+ request_body["body"] = {"contentType": body_content_type, "content": body}
608
+ if location_display_name:
609
+ request_body["location"] = {"displayName": location_display_name}
610
+ if attendees:
611
+ request_body["attendees"] = attendees
612
+ request_body.update(kwargs)
613
+ response = await self._apost(url, data=request_body, params={}, content_type="application/json")
614
+ return self._handle_response(response)
615
+
616
+ async def update_event(
617
+ self,
618
+ event_id: str,
619
+ **kwargs,
620
+ ) -> dict[str, Any]:
621
+ """
622
+ Updates an existing event.
623
+
624
+ Args:
625
+ event_id (str): The unique identifier for the event.
626
+ **kwargs: Event properties to update (e.g., subject, start, end, body, attendees).
627
+ For start/end, use nested dictionaries: start={"dateTime": "...", "timeZone": "..."}.
628
+
629
+ Returns:
630
+ dict[str, Any]: A dictionary containing the updated event's details.
631
+
632
+ Tags:
633
+ important
634
+ """
635
+ user_id = await self._get_user_id()
636
+ url = f"{self.base_url}/users/{user_id}/events/{event_id}"
637
+ response = await self._apatch(url, data=kwargs, params={}, content_type="application/json")
638
+ return self._handle_response(response)
639
+
640
+ async def delete_event(self, event_id: str) -> dict[str, Any]:
641
+ """
642
+ Deletes a specific event.
643
+
644
+ Args:
645
+ event_id (str): The unique identifier for the event to delete.
646
+
647
+ Returns:
648
+ dict[str, Any]: A dictionary confirming the deletion.
649
+
650
+ Tags:
651
+ important
652
+ """
653
+ user_id = await self._get_user_id()
654
+ url = f"{self.base_url}/users/{user_id}/events/{event_id}"
655
+ response = await self._adelete(url, params={})
656
+ return self._handle_response(response)
657
+
658
+ async def list_calendar_view(
659
+ self,
660
+ start_datetime: str,
661
+ end_datetime: str,
662
+ calendar_id: str | None = None,
663
+ top: int | None = None,
664
+ skip: int | None = None,
665
+ select: list[str] | None = None,
666
+ ) -> dict[str, Any]:
667
+ """
668
+ Retrieves events between a start and end time (includes expanded recurring events).
669
+
670
+ Args:
671
+ start_datetime (str): The start of the time range (ISO 8601, e.g., '2023-12-25T00:00:00Z').
672
+ end_datetime (str): The end of the time range (ISO 8601, e.g., '2023-12-26T00:00:00Z').
673
+ calendar_id (str, optional): The ID of the calendar. If not provided, the default calendar is used.
674
+ top (int, optional): The maximum number of events to return.
675
+ skip (int, optional): The number of events to skip.
676
+ select (list[str], optional): A list of properties to return for each event.
677
+
678
+ Returns:
679
+ dict[str, Any]: A dictionary containing a list of event instances.
680
+
681
+ Tags:
682
+ important
683
+ """
684
+ user_id = await self._get_user_id()
685
+ if calendar_id:
686
+ url = f"{self.base_url}/users/{user_id}/calendars/{calendar_id}/calendarView"
687
+ else:
688
+ url = f"{self.base_url}/users/{user_id}/calendar/calendarView"
689
+ select_str = ",".join(select) if select else None
690
+ query_params = {
691
+ "startDateTime": start_datetime,
692
+ "endDateTime": end_datetime,
693
+ }
694
+ if top is not None:
695
+ query_params["$top"] = top
696
+ if skip is not None:
697
+ query_params["$skip"] = skip
698
+ if select_str:
699
+ query_params["$select"] = select_str
700
+ response = await self._aget(url, params=query_params)
701
+ return self._handle_response(response)
702
+
703
+ async def get_schedule(
704
+ self,
705
+ schedules: list[str],
706
+ start_datetime: str,
707
+ end_datetime: str,
708
+ availability_view_interval: int = 30,
709
+ ) -> dict[str, Any]:
710
+ """
711
+ Retrieves free/busy information for a set of users, groups, or resources.
712
+
713
+ Args:
714
+ schedules (list[str]): A list of SMTP addresses (emails) to get schedules for.
715
+ start_datetime (str): The start of the time range (ISO 8601 with 'Z', e.g., '2023-12-25T00:00:00Z').
716
+ end_datetime (str): The end of the time range (ISO 8601 with 'Z', e.g., '2023-12-26T00:00:00Z').
717
+ availability_view_interval (int, optional): The duration of each time slot in minutes. Defaults to 30.
718
+
719
+ Returns:
720
+ dict[str, Any]: A dictionary containing schedule information.
721
+
722
+ Tags:
723
+ important
724
+ """
725
+ user_id = await self._get_user_id()
726
+ url = f"{self.base_url}/users/{user_id}/calendar/getSchedule"
727
+ request_body = {
728
+ "schedules": schedules,
729
+ "startTime": {"dateTime": start_datetime, "timeZone": "UTC"},
730
+ "endTime": {"dateTime": end_datetime, "timeZone": "UTC"},
731
+ "availabilityViewInterval": availability_view_interval,
732
+ }
733
+ response = await self._apost(url, data=request_body, params={}, content_type="application/json")
734
+ return self._handle_response(response)
735
+
736
+ async def get_next_page_results(self, url: str) -> dict[str, Any]:
737
+ """
738
+ Retrieves the next page of results from a paginated API response.
739
+
740
+ Args:
741
+ url (str): The full URL for the next page of results (@odata.nextLink).
742
+
743
+ Returns:
744
+ dict[str, Any]: A dictionary containing the next page of results.
745
+
746
+ Raises:
747
+ ValueError: If the URL is missing or invalid.
748
+ Tags:
749
+ important
417
750
  """
418
751
  if not url:
419
752
  raise ValueError("Missing required parameter 'url'.")
420
753
  if not url.startswith(self.base_url):
421
- raise ValueError(
422
- f"The provided URL '{url}' does not start with the expected base URL '{self.base_url}'."
423
- )
754
+ raise ValueError(f"The provided URL must start with '{self.base_url}'.")
424
755
  relative_part = url[len(self.base_url) :]
425
756
  parsed_relative = urlparse(relative_part)
426
757
  path_only = parsed_relative.path
427
758
  params = {k: v[0] for k, v in parse_qs(parsed_relative.query).items()}
428
- response = self._get(path_only, params=params)
759
+ response = await self._aget(path_only, params=params)
429
760
  return self._handle_response(response)
430
761
 
431
762
  def list_tools(self):
432
763
  return [
433
- self.reply_to_message,
434
- self.send_mail,
435
- self.get_mail_folder,
436
- self.list_user_messages,
437
- self.get_user_message,
438
- self.user_delete_message,
439
- self.list_message_attachments,
440
- self.get_current_user_profile,
441
- self.get_next_page,
764
+ self.reply_to_email,
765
+ self.send_email,
766
+ self.get_email_folder,
767
+ self.list_emails,
768
+ self.get_email,
769
+ self.delete_email,
770
+ self.list_email_attachments,
771
+ self.get_attachment,
772
+ self.get_my_profile,
773
+ self.get_next_page_results,
774
+ self.list_calendars,
775
+ self.get_calendar,
776
+ self.create_calendar,
777
+ self.update_calendar,
778
+ self.delete_calendar,
779
+ self.list_events,
780
+ self.get_event,
781
+ self.create_event,
782
+ self.update_event,
783
+ self.delete_event,
784
+ self.list_calendar_view,
785
+ self.get_schedule,
442
786
  ]