diff --git a/gcalendar/calendar_tools.py b/gcalendar/calendar_tools.py index 13da74c..60b366d 100644 --- a/gcalendar/calendar_tools.py +++ b/gcalendar/calendar_tools.py @@ -172,6 +172,21 @@ def _preserve_existing_fields( event_body[field_name] = new_value +def _get_meeting_link(item: Dict[str, Any]) -> str: + """Extract video meeting link from event conference data or hangoutLink.""" + conference_data = item.get("conferenceData") + if conference_data and "entryPoints" in conference_data: + for entry_point in conference_data["entryPoints"]: + if entry_point.get("entryPointType") == "video": + uri = entry_point.get("uri", "") + if uri: + return uri + hangout_link = item.get("hangoutLink", "") + if hangout_link: + return hangout_link + return "" + + def _format_attendee_details( attendees: List[Dict[str, Any]], indent: str = " " ) -> str: @@ -448,6 +463,8 @@ async def get_events( ) attendee_details_str = _format_attendee_details(attendees, indent=" ") + meeting_link = _get_meeting_link(item) + event_details = ( f"Event Details:\n" f"- Title: {summary}\n" @@ -456,6 +473,10 @@ async def get_events( f"- Description: {description}\n" f"- Location: {location}\n" f"- Color ID: {color_id}\n" + ) + if meeting_link: + event_details += f"- Meeting Link: {meeting_link}\n" + event_details += ( f"- Attendees: {attendee_emails}\n" f"- Attendee Details: {attendee_details_str}\n" ) @@ -494,10 +515,16 @@ async def get_events( ) attendee_details_str = _format_attendee_details(attendees, indent=" ") + meeting_link = _get_meeting_link(item) + event_detail_parts = ( f'- "{summary}" (Starts: {start_time}, Ends: {end_time})\n' f" Description: {description}\n" f" Location: {location}\n" + ) + if meeting_link: + event_detail_parts += f" Meeting Link: {meeting_link}\n" + event_detail_parts += ( f" Attendees: {attendee_emails}\n" f" Attendee Details: {attendee_details_str}\n" ) @@ -513,9 +540,12 @@ async def get_events( event_details_list.append(event_detail_parts) else: # Basic output format - event_details_list.append( - f'- "{summary}" (Starts: {start_time}, Ends: {end_time}) ID: {item_event_id} | Link: {link}' - ) + meeting_link = _get_meeting_link(item) + basic_line = f'- "{summary}" (Starts: {start_time}, Ends: {end_time})' + if meeting_link: + basic_line += f" Meeting: {meeting_link}" + basic_line += f" ID: {item_event_id} | Link: {link}" + event_details_list.append(basic_line) if event_id: # Single event basic output diff --git a/gdocs/docs_tools.py b/gdocs/docs_tools.py index c040c0b..3d7b316 100644 --- a/gdocs/docs_tools.py +++ b/gdocs/docs_tools.py @@ -1463,6 +1463,7 @@ async def update_paragraph_style( indent_end: float = None, space_above: float = None, space_below: float = None, + named_style_type: str = None, list_type: str = None, list_nesting_level: int = None, ) -> str: @@ -1488,6 +1489,8 @@ async def update_paragraph_style( indent_end: Right/end indent in points space_above: Space above paragraph in points (e.g., 12 for one line) space_below: Space below paragraph in points + named_style_type: Direct named style type - 'NORMAL_TEXT', 'TITLE', 'SUBTITLE', + 'HEADING_1' through 'HEADING_6'. Mutually exclusive with heading_level. list_type: Create a list from existing paragraphs ('UNORDERED' for bullets, 'ORDERED' for numbers) list_nesting_level: Nesting level for lists (0-8, where 0 is top level, default is 0) Use higher levels for nested/indented list items @@ -1546,12 +1549,30 @@ async def update_paragraph_style( if list_nesting_level < 0 or list_nesting_level > 8: return "Error: list_nesting_level must be between 0 and 8" + # Validate named_style_type + if named_style_type is not None and heading_level is not None: + return "Error: heading_level and named_style_type are mutually exclusive; provide only one" + + if named_style_type is not None: + valid_styles = [ + "NORMAL_TEXT", "TITLE", "SUBTITLE", + "HEADING_1", "HEADING_2", "HEADING_3", + "HEADING_4", "HEADING_5", "HEADING_6", + ] + if named_style_type not in valid_styles: + return f"Error: Invalid named_style_type '{named_style_type}'. Must be one of: {', '.join(valid_styles)}" + # Build paragraph style object paragraph_style = {} fields = [] + # Handle named_style_type (direct named style) + if named_style_type is not None: + paragraph_style["namedStyleType"] = named_style_type + fields.append("namedStyleType") + # Handle heading level (named style) - if heading_level is not None: + elif heading_level is not None: if heading_level < 0 or heading_level > 6: return "Error: heading_level must be between 0 (normal text) and 6" if heading_level == 0: diff --git a/gdocs/managers/batch_operation_manager.py b/gdocs/managers/batch_operation_manager.py index 97bd284..c0d5368 100644 --- a/gdocs/managers/batch_operation_manager.py +++ b/gdocs/managers/batch_operation_manager.py @@ -475,6 +475,7 @@ class BatchOperationManager: "indent_end", "space_above", "space_below", + "named_style_type", ], "description": "Apply paragraph-level styling (headings, alignment, spacing, indentation)", }, diff --git a/gdocs/managers/validation_manager.py b/gdocs/managers/validation_manager.py index e2d11b0..69ffd21 100644 --- a/gdocs/managers/validation_manager.py +++ b/gdocs/managers/validation_manager.py @@ -316,6 +316,12 @@ class ValidationManager: "At least one paragraph style parameter must be provided (heading_level, alignment, line_spacing, indent_first_line, indent_start, indent_end, space_above, space_below, or named_style_type)", ) + if heading_level is not None and named_style_type is not None: + return ( + False, + "heading_level and named_style_type are mutually exclusive; provide only one", + ) + if named_style_type is not None: valid_styles = [ "NORMAL_TEXT",