agentr 0.1.7__py3-none-any.whl → 0.1.9__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.
- agentr/application.py +40 -17
- agentr/applications/github/app.py +212 -264
- agentr/applications/google_calendar/app.py +349 -408
- agentr/applications/reddit/app.py +308 -8
- agentr/cli.py +31 -0
- agentr/integration.py +79 -3
- agentr/integrations/README.md +25 -0
- agentr/integrations/__init__.py +5 -0
- agentr/integrations/agentr.py +87 -0
- agentr/integrations/api_key.py +16 -0
- agentr/integrations/base.py +60 -0
- agentr/server.py +23 -2
- agentr/test.py +4 -29
- agentr/utils/openapi.py +113 -25
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/METADATA +3 -3
- agentr-0.1.9.dist-info/RECORD +30 -0
- agentr-0.1.7.dist-info/RECORD +0 -25
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/WHEEL +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/entry_points.txt +0 -0
- {agentr-0.1.7.dist-info → agentr-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -14,11 +14,6 @@ class GoogleCalendarApp(APIApplication):
|
|
14
14
|
if not self.integration:
|
15
15
|
raise ValueError("Integration not configured for GoogleCalendarApp")
|
16
16
|
credentials = self.integration.get_credentials()
|
17
|
-
if not credentials:
|
18
|
-
logger.warning("No Google Calendar credentials found via integration.")
|
19
|
-
action = self.integration.authorize()
|
20
|
-
raise NotAuthorizedError(action)
|
21
|
-
|
22
17
|
if "headers" in credentials:
|
23
18
|
return credentials["headers"]
|
24
19
|
return {
|
@@ -71,82 +66,72 @@ class GoogleCalendarApp(APIApplication):
|
|
71
66
|
Returns:
|
72
67
|
A formatted list of events or an error message
|
73
68
|
"""
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
params["maxResults"] = max_results
|
95
|
-
|
96
|
-
if time_zone:
|
97
|
-
params["timeZone"] = time_zone
|
98
|
-
|
99
|
-
date_range = "today" if days == 1 else f"the next {days} days"
|
100
|
-
logger.info(f"Retrieving calendar events for {date_range}")
|
101
|
-
|
102
|
-
response = self._get(url, params=params)
|
69
|
+
# Get today's date in ISO format
|
70
|
+
today = datetime.utcnow().date()
|
71
|
+
end_date = today + timedelta(days=days)
|
72
|
+
|
73
|
+
# Format dates for API
|
74
|
+
time_min = f"{today.isoformat()}T00:00:00Z"
|
75
|
+
time_max = f"{end_date.isoformat()}T00:00:00Z"
|
76
|
+
|
77
|
+
url = f"{self.base_api_url}/events"
|
78
|
+
|
79
|
+
# Build query parameters
|
80
|
+
params = {
|
81
|
+
"timeMin": time_min,
|
82
|
+
"timeMax": time_max,
|
83
|
+
"singleEvents": "true",
|
84
|
+
"orderBy": "startTime"
|
85
|
+
}
|
86
|
+
|
87
|
+
if max_results is not None:
|
88
|
+
params["maxResults"] = max_results
|
103
89
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
#
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
90
|
+
if time_zone:
|
91
|
+
params["timeZone"] = time_zone
|
92
|
+
|
93
|
+
date_range = "today" if days == 1 else f"the next {days} days"
|
94
|
+
logger.info(f"Retrieving calendar events for {date_range}")
|
95
|
+
|
96
|
+
response = self._get(url, params=params)
|
97
|
+
response.raise_for_status()
|
98
|
+
|
99
|
+
events = response.json().get("items", [])
|
100
|
+
if not events:
|
101
|
+
return f"No events scheduled for {date_range}."
|
102
|
+
|
103
|
+
result = f"Events for {date_range}:\n\n"
|
104
|
+
for event in events:
|
105
|
+
# Extract event date and time
|
106
|
+
start = event.get("start", {})
|
107
|
+
event_date = start.get("date", start.get("dateTime", "")).split("T")[0] if "T" in start.get("dateTime", "") else start.get("date", "")
|
108
|
+
|
109
|
+
# Extract and format time
|
110
|
+
start_time = start.get("dateTime", start.get("date", "All day"))
|
111
|
+
|
112
|
+
# Format the time display
|
113
|
+
if "T" in start_time: # It's a datetime
|
114
|
+
formatted_time = self._format_datetime(start_time)
|
115
|
+
# For multi-day view, keep the date; for single day, just show time
|
116
|
+
if days > 1:
|
117
|
+
time_display = formatted_time
|
118
|
+
else:
|
119
|
+
# Extract just the time part
|
120
|
+
time_display = formatted_time.split(" ")[1] + " " + formatted_time.split(" ")[2]
|
121
|
+
else: # It's an all-day event
|
122
|
+
if days > 1:
|
123
|
+
time_display = f"{event_date} (All day)"
|
124
|
+
else:
|
125
|
+
time_display = "All day"
|
126
|
+
|
127
|
+
# Get event details
|
128
|
+
summary = event.get("summary", "Untitled event")
|
129
|
+
event_id = event.get("id", "No ID")
|
130
|
+
|
131
|
+
result += f"- {time_display}: {summary} (ID: {event_id})\n"
|
132
|
+
|
133
|
+
return result
|
134
|
+
|
150
135
|
def get_event(self, event_id: str, max_attendees: int = None, time_zone: str = None) -> str:
|
151
136
|
"""Get a specific event from your Google Calendar by ID
|
152
137
|
|
@@ -158,89 +143,77 @@ class GoogleCalendarApp(APIApplication):
|
|
158
143
|
Returns:
|
159
144
|
A formatted event details or an error message
|
160
145
|
"""
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
return result
|
233
|
-
elif response.status_code == 404:
|
234
|
-
return f"Event not found with ID: {event_id}"
|
235
|
-
else:
|
236
|
-
logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
|
237
|
-
return f"Error retrieving event: {response.status_code} - {response.text}"
|
238
|
-
except NotAuthorizedError as e:
|
239
|
-
logger.warning(f"Google Calendar authorization required: {e.message}")
|
240
|
-
return e.message
|
241
|
-
except Exception as e:
|
242
|
-
logger.exception(f"Error retrieving event: {type(e).__name__} - {str(e)}")
|
243
|
-
return f"Error retrieving event: {type(e).__name__} - {str(e)}"
|
146
|
+
url = f"{self.base_api_url}/events/{event_id}"
|
147
|
+
|
148
|
+
# Build query parameters
|
149
|
+
params = {}
|
150
|
+
if max_attendees is not None:
|
151
|
+
params["maxAttendees"] = max_attendees
|
152
|
+
if time_zone:
|
153
|
+
params["timeZone"] = time_zone
|
154
|
+
|
155
|
+
logger.info(f"Retrieving calendar event with ID: {event_id}")
|
156
|
+
|
157
|
+
response = self._get(url, params=params)
|
158
|
+
response.raise_for_status()
|
159
|
+
|
160
|
+
event = response.json()
|
161
|
+
|
162
|
+
# Extract event details
|
163
|
+
summary = event.get("summary", "Untitled event")
|
164
|
+
description = event.get("description", "No description")
|
165
|
+
location = event.get("location", "No location specified")
|
166
|
+
|
167
|
+
# Format dates
|
168
|
+
start = event.get("start", {})
|
169
|
+
end = event.get("end", {})
|
170
|
+
|
171
|
+
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
172
|
+
end_time = end.get("dateTime", end.get("date", "Unknown"))
|
173
|
+
|
174
|
+
# Format datetimes using the helper function
|
175
|
+
start_formatted = self._format_datetime(start_time)
|
176
|
+
end_formatted = self._format_datetime(end_time)
|
177
|
+
|
178
|
+
# Get creator and organizer
|
179
|
+
creator = event.get("creator", {}).get("email", "Unknown")
|
180
|
+
organizer = event.get("organizer", {}).get("email", "Unknown")
|
181
|
+
|
182
|
+
# Check if it's a recurring event
|
183
|
+
recurrence = "Yes" if "recurrence" in event else "No"
|
184
|
+
|
185
|
+
# Get attendees if any
|
186
|
+
attendees = event.get("attendees", [])
|
187
|
+
attendee_info = ""
|
188
|
+
if attendees:
|
189
|
+
attendee_info = "\nAttendees:\n"
|
190
|
+
for i, attendee in enumerate(attendees, 1):
|
191
|
+
email = attendee.get("email", "No email")
|
192
|
+
name = attendee.get("displayName", email)
|
193
|
+
response_status = attendee.get("responseStatus", "Unknown")
|
194
|
+
|
195
|
+
status_mapping = {
|
196
|
+
"accepted": "Accepted",
|
197
|
+
"declined": "Declined",
|
198
|
+
"tentative": "Maybe",
|
199
|
+
"needsAction": "Not responded"
|
200
|
+
}
|
201
|
+
|
202
|
+
formatted_status = status_mapping.get(response_status, response_status)
|
203
|
+
attendee_info += f" {i}. {name} ({email}) - {formatted_status}\n"
|
204
|
+
|
205
|
+
# Format the response
|
206
|
+
result = f"Event: {summary}\n"
|
207
|
+
result += f"ID: {event_id}\n"
|
208
|
+
result += f"When: {start_formatted} to {end_formatted}\n"
|
209
|
+
result += f"Where: {location}\n"
|
210
|
+
result += f"Description: {description}\n"
|
211
|
+
result += f"Creator: {creator}\n"
|
212
|
+
result += f"Organizer: {organizer}\n"
|
213
|
+
result += f"Recurring: {recurrence}\n"
|
214
|
+
result += attendee_info
|
215
|
+
|
216
|
+
return result
|
244
217
|
|
245
218
|
def list_events(self, max_results: int = 10, time_min: str = None, time_max: str = None,
|
246
219
|
q: str = None, order_by: str = "startTime", single_events: bool = True,
|
@@ -260,100 +233,90 @@ class GoogleCalendarApp(APIApplication):
|
|
260
233
|
Returns:
|
261
234
|
A formatted list of events or an error message
|
262
235
|
"""
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
236
|
+
url = f"{self.base_api_url}/events"
|
237
|
+
|
238
|
+
# Build query parameters
|
239
|
+
params = {
|
240
|
+
"maxResults": max_results,
|
241
|
+
"singleEvents": str(single_events).lower(),
|
242
|
+
"orderBy": order_by
|
243
|
+
}
|
244
|
+
|
245
|
+
# Set time boundaries if provided, otherwise default to now for time_min
|
246
|
+
if time_min:
|
247
|
+
params["timeMin"] = time_min
|
248
|
+
else:
|
249
|
+
# Default to current time if not specified
|
250
|
+
now = datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
|
251
|
+
params["timeMin"] = now
|
272
252
|
|
273
|
-
|
274
|
-
|
275
|
-
params["timeMin"] = time_min
|
276
|
-
else:
|
277
|
-
# Default to current time if not specified
|
278
|
-
now = datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
|
279
|
-
params["timeMin"] = now
|
280
|
-
|
281
|
-
if time_max:
|
282
|
-
params["timeMax"] = time_max
|
283
|
-
|
284
|
-
# Add optional filters if provided
|
285
|
-
if q:
|
286
|
-
params["q"] = q
|
287
|
-
|
288
|
-
if time_zone:
|
289
|
-
params["timeZone"] = time_zone
|
290
|
-
|
291
|
-
if page_token:
|
292
|
-
params["pageToken"] = page_token
|
253
|
+
if time_max:
|
254
|
+
params["timeMax"] = time_max
|
293
255
|
|
294
|
-
|
256
|
+
# Add optional filters if provided
|
257
|
+
if q:
|
258
|
+
params["q"] = q
|
295
259
|
|
296
|
-
|
260
|
+
if time_zone:
|
261
|
+
params["timeZone"] = time_zone
|
297
262
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
return f"Error retrieving events: {type(e).__name__} - {str(e)}"
|
356
|
-
|
263
|
+
if page_token:
|
264
|
+
params["pageToken"] = page_token
|
265
|
+
|
266
|
+
logger.info(f"Retrieving calendar events with params: {params}")
|
267
|
+
|
268
|
+
response = self._get(url, params=params)
|
269
|
+
response.raise_for_status()
|
270
|
+
|
271
|
+
data = response.json()
|
272
|
+
events = data.get("items", [])
|
273
|
+
|
274
|
+
if not events:
|
275
|
+
return "No events found matching your criteria."
|
276
|
+
|
277
|
+
# Extract calendar information
|
278
|
+
calendar_summary = data.get("summary", "Your Calendar")
|
279
|
+
time_zone_info = data.get("timeZone", "Unknown")
|
280
|
+
|
281
|
+
result = f"Events from {calendar_summary} (Time Zone: {time_zone_info}):\n\n"
|
282
|
+
|
283
|
+
# Process and format each event
|
284
|
+
for i, event in enumerate(events, 1):
|
285
|
+
# Get basic event details
|
286
|
+
event_id = event.get("id", "No ID")
|
287
|
+
summary = event.get("summary", "Untitled event")
|
288
|
+
|
289
|
+
# Get event times and format them
|
290
|
+
start = event.get("start", {})
|
291
|
+
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
292
|
+
|
293
|
+
# Format the start time using the helper function
|
294
|
+
start_formatted = self._format_datetime(start_time)
|
295
|
+
|
296
|
+
# Get location if available
|
297
|
+
location = event.get("location", "No location specified")
|
298
|
+
|
299
|
+
# Check if it's a recurring event
|
300
|
+
is_recurring = "recurrence" in event
|
301
|
+
recurring_info = " (Recurring)" if is_recurring else ""
|
302
|
+
|
303
|
+
# Format the event information
|
304
|
+
result += f"{i}. {summary}{recurring_info}\n"
|
305
|
+
result += f" ID: {event_id}\n"
|
306
|
+
result += f" When: {start_formatted}\n"
|
307
|
+
result += f" Where: {location}\n"
|
308
|
+
|
309
|
+
# Add a separator between events
|
310
|
+
if i < len(events):
|
311
|
+
result += "\n"
|
312
|
+
|
313
|
+
# Add pagination info if available
|
314
|
+
if "nextPageToken" in data:
|
315
|
+
next_token = data.get("nextPageToken")
|
316
|
+
result += f"\nMore events available. Use page_token='{next_token}' to see more."
|
317
|
+
|
318
|
+
return result
|
319
|
+
|
357
320
|
def quick_add_event(self, text: str, send_updates: str = "none") -> str:
|
358
321
|
"""Create a calendar event using natural language description
|
359
322
|
|
@@ -367,66 +330,56 @@ class GoogleCalendarApp(APIApplication):
|
|
367
330
|
Returns:
|
368
331
|
A confirmation message with the created event details or an error message
|
369
332
|
"""
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
333
|
+
url = f"{self.base_api_url}/events/quickAdd"
|
334
|
+
|
335
|
+
# Use params argument instead of manually constructing URL
|
336
|
+
params = {
|
337
|
+
"text": text,
|
338
|
+
"sendUpdates": send_updates
|
339
|
+
}
|
340
|
+
|
341
|
+
logger.info(f"Creating event via quickAdd: '{text}'")
|
342
|
+
|
343
|
+
# Pass params to _post method
|
344
|
+
response = self._post(url, data=None, params=params)
|
345
|
+
response.raise_for_status()
|
346
|
+
|
347
|
+
event = response.json()
|
348
|
+
|
349
|
+
# Extract event details
|
350
|
+
event_id = event.get("id", "Unknown")
|
351
|
+
summary = event.get("summary", "Untitled event")
|
352
|
+
|
353
|
+
# Format dates
|
354
|
+
start = event.get("start", {})
|
355
|
+
end = event.get("end", {})
|
356
|
+
|
357
|
+
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
358
|
+
end_time = end.get("dateTime", end.get("date", "Unknown"))
|
359
|
+
|
360
|
+
# Format datetimes using the helper function
|
361
|
+
start_formatted = self._format_datetime(start_time)
|
362
|
+
end_formatted = self._format_datetime(end_time)
|
363
|
+
|
364
|
+
# Get location if available
|
365
|
+
location = event.get("location", "No location specified")
|
366
|
+
|
367
|
+
# Format the confirmation message
|
368
|
+
result = f"Successfully created event!\n\n"
|
369
|
+
result += f"Summary: {summary}\n"
|
370
|
+
result += f"When: {start_formatted}"
|
371
|
+
|
372
|
+
# Only add end time if it's different from start (for all-day events they might be the same)
|
373
|
+
if start_formatted != end_formatted:
|
374
|
+
result += f" to {end_formatted}"
|
383
375
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
# Format dates
|
392
|
-
start = event.get("start", {})
|
393
|
-
end = event.get("end", {})
|
394
|
-
|
395
|
-
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
396
|
-
end_time = end.get("dateTime", end.get("date", "Unknown"))
|
397
|
-
|
398
|
-
# Format datetimes using the helper function
|
399
|
-
start_formatted = self._format_datetime(start_time)
|
400
|
-
end_formatted = self._format_datetime(end_time)
|
401
|
-
|
402
|
-
# Get location if available
|
403
|
-
location = event.get("location", "No location specified")
|
404
|
-
|
405
|
-
# Format the confirmation message
|
406
|
-
result = f"Successfully created event!\n\n"
|
407
|
-
result += f"Summary: {summary}\n"
|
408
|
-
result += f"When: {start_formatted}"
|
409
|
-
|
410
|
-
# Only add end time if it's different from start (for all-day events they might be the same)
|
411
|
-
if start_formatted != end_formatted:
|
412
|
-
result += f" to {end_formatted}"
|
413
|
-
|
414
|
-
result += f"\nWhere: {location}\n"
|
415
|
-
result += f"Event ID: {event_id}\n"
|
416
|
-
|
417
|
-
# Add a note about viewing the event
|
418
|
-
result += f"\nUse get_event('{event_id}') to see full details."
|
419
|
-
|
420
|
-
return result
|
421
|
-
else:
|
422
|
-
logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
|
423
|
-
return f"Error creating event: {response.status_code} - {response.text}"
|
424
|
-
except NotAuthorizedError as e:
|
425
|
-
logger.warning(f"Google Calendar authorization required: {e.message}")
|
426
|
-
return e.message
|
427
|
-
except Exception as e:
|
428
|
-
logger.exception(f"Error creating event: {type(e).__name__} - {str(e)}")
|
429
|
-
return f"Error creating event: {type(e).__name__} - {str(e)}"
|
376
|
+
result += f"\nWhere: {location}\n"
|
377
|
+
result += f"Event ID: {event_id}\n"
|
378
|
+
|
379
|
+
# Add a note about viewing the event
|
380
|
+
result += f"\nUse get_event('{event_id}') to see full details."
|
381
|
+
|
382
|
+
return result
|
430
383
|
|
431
384
|
def get_event_instances(self, event_id: str, max_results: int = 25, time_min: str = None,
|
432
385
|
time_max: str = None, time_zone: str = None, show_deleted: bool = False,
|
@@ -447,102 +400,90 @@ class GoogleCalendarApp(APIApplication):
|
|
447
400
|
Returns:
|
448
401
|
A formatted list of event instances or an error message
|
449
402
|
"""
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
return result
|
535
|
-
elif response.status_code == 404:
|
536
|
-
return f"Error: Event with ID {event_id} not found or is not a recurring event."
|
537
|
-
else:
|
538
|
-
logger.error(f"Google Calendar API Error: {response.status_code} - {response.text}")
|
539
|
-
return f"Error retrieving event instances: {response.status_code} - {response.text}"
|
540
|
-
except NotAuthorizedError as e:
|
541
|
-
logger.warning(f"Google Calendar authorization required: {e.message}")
|
542
|
-
return e.message
|
543
|
-
except Exception as e:
|
544
|
-
logger.exception(f"Error retrieving event instances: {type(e).__name__} - {str(e)}")
|
545
|
-
return f"Error retrieving event instances: {type(e).__name__} - {str(e)}"
|
403
|
+
url = f"{self.base_api_url}/events/{event_id}/instances"
|
404
|
+
|
405
|
+
# Build query parameters
|
406
|
+
params = {
|
407
|
+
"maxResults": max_results,
|
408
|
+
"showDeleted": str(show_deleted).lower()
|
409
|
+
}
|
410
|
+
|
411
|
+
# Add optional parameters if provided
|
412
|
+
if time_min:
|
413
|
+
params["timeMin"] = time_min
|
414
|
+
|
415
|
+
if time_max:
|
416
|
+
params["timeMax"] = time_max
|
417
|
+
|
418
|
+
if time_zone:
|
419
|
+
params["timeZone"] = time_zone
|
420
|
+
|
421
|
+
if page_token:
|
422
|
+
params["pageToken"] = page_token
|
423
|
+
|
424
|
+
logger.info(f"Retrieving instances of recurring event with ID: {event_id}")
|
425
|
+
|
426
|
+
response = self._get(url, params=params)
|
427
|
+
response.raise_for_status()
|
428
|
+
|
429
|
+
data = response.json()
|
430
|
+
instances = data.get("items", [])
|
431
|
+
|
432
|
+
if not instances:
|
433
|
+
return f"No instances found for recurring event with ID: {event_id}"
|
434
|
+
|
435
|
+
# Extract event summary from the first instance
|
436
|
+
parent_summary = instances[0].get("summary", "Untitled recurring event")
|
437
|
+
|
438
|
+
result = f"Instances of recurring event: {parent_summary}\n\n"
|
439
|
+
|
440
|
+
# Process and format each instance
|
441
|
+
for i, instance in enumerate(instances, 1):
|
442
|
+
# Get instance ID and status
|
443
|
+
instance_id = instance.get("id", "No ID")
|
444
|
+
status = instance.get("status", "confirmed")
|
445
|
+
|
446
|
+
# Format status for display
|
447
|
+
status_display = ""
|
448
|
+
if status == "cancelled":
|
449
|
+
status_display = " [CANCELLED]"
|
450
|
+
elif status == "tentative":
|
451
|
+
status_display = " [TENTATIVE]"
|
452
|
+
|
453
|
+
# Get instance time
|
454
|
+
start = instance.get("start", {})
|
455
|
+
original_start_time = instance.get("originalStartTime", {})
|
456
|
+
|
457
|
+
# Determine if this is a modified instance
|
458
|
+
is_modified = original_start_time and "dateTime" in original_start_time
|
459
|
+
modified_indicator = " [MODIFIED]" if is_modified else ""
|
460
|
+
|
461
|
+
# Get the time information
|
462
|
+
start_time = start.get("dateTime", start.get("date", "Unknown"))
|
463
|
+
|
464
|
+
# Format the time using the helper function
|
465
|
+
formatted_time = self._format_datetime(start_time)
|
466
|
+
|
467
|
+
# Format the instance information
|
468
|
+
result += f"{i}. {formatted_time}{status_display}{modified_indicator}\n"
|
469
|
+
result += f" Instance ID: {instance_id}\n"
|
470
|
+
|
471
|
+
# Show original start time if modified
|
472
|
+
if is_modified:
|
473
|
+
orig_time = original_start_time.get("dateTime", original_start_time.get("date", "Unknown"))
|
474
|
+
orig_formatted = self._format_datetime(orig_time)
|
475
|
+
result += f" Original time: {orig_formatted}\n"
|
476
|
+
|
477
|
+
# Add a separator between instances
|
478
|
+
if i < len(instances):
|
479
|
+
result += "\n"
|
480
|
+
|
481
|
+
# Add pagination info if available
|
482
|
+
if "nextPageToken" in data:
|
483
|
+
next_token = data.get("nextPageToken")
|
484
|
+
result += f"\nMore instances available. Use page_token='{next_token}' to see more."
|
485
|
+
|
486
|
+
return result
|
546
487
|
|
547
488
|
def list_tools(self):
|
548
489
|
return [self.get_event, self.get_today_events, self.list_events, self.quick_add_event, self.get_event_instances]
|