diff --git a/google/credential/tool.gpt b/google/credential/tool.gpt index 7130533f..f1b71ddd 100644 --- a/google/credential/tool.gpt +++ b/google/credential/tool.gpt @@ -6,7 +6,7 @@ Share Credential: ../../oauth2 as google openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/spreadsheets - https://www.googleapis.com/auth/documents.readonly + https://www.googleapis.com/auth/documents https://mail.google.com/ https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.compose" as scope diff --git a/google/docs/create_doc.py b/google/docs/create_doc.py new file mode 100644 index 00000000..aa23504e --- /dev/null +++ b/google/docs/create_doc.py @@ -0,0 +1,27 @@ +import sys +import os +import json +from googleapiclient.discovery import build +from auth import client + +def main(): + try: + # Get the title from the DOC_TITLE environment variable or default to 'Untitled Document' + title = os.getenv('DOC_TITLE', 'Untitled Document') + + # Authenticate and build the Docs API service + service = client('docs', 'v1') + + # Create a new Google Doc with the specified title + document = service.documents().create(body={"title": title}).execute() + + # Get the document ID + document_id = document.get('documentId') + + print(f"New document created with ID: {document_id}") + except Exception as err: + sys.stderr.write(f"Error: {err}\n") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/google/docs/id.py b/google/docs/id.py index 2a6562fc..b429c94f 100644 --- a/google/docs/id.py +++ b/google/docs/id.py @@ -13,7 +13,7 @@ def extract_file_id(ref): # If the input is already a document ID, return it as is if re.match(r"^[a-zA-Z0-9_-]{33,}$", ref): return ref - + # Regular expression to match Google Drive document links pattern = r"(?:https?://(?:drive|docs)\.google\.com/(?:file/d/|document/d/|open\?id=|uc\?id=))([a-zA-Z0-9_-]{33,})" @@ -21,6 +21,6 @@ def extract_file_id(ref): match = re.search(pattern, ref) if match: return match.group(1) - + # If the input doesn't match a known pattern, raise an error raise ValueError("Invalid Google document ID or link format") diff --git a/google/docs/read_doc.py b/google/docs/read_doc.py index a2f47a9b..e26c48a9 100644 --- a/google/docs/read_doc.py +++ b/google/docs/read_doc.py @@ -1,4 +1,3 @@ -import io import sys import os @@ -54,4 +53,4 @@ def parse_table(table): return md_table if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/google/docs/requirements.txt b/google/docs/requirements.txt index b572a393..ede8548f 100644 --- a/google/docs/requirements.txt +++ b/google/docs/requirements.txt @@ -1,3 +1,5 @@ google-api-python-client google-auth-httplib2 google-auth-oauthlib +beautifulsoup4 +markdown \ No newline at end of file diff --git a/google/docs/tool.gpt b/google/docs/tool.gpt index 434f0225..b1c7112b 100644 --- a/google/docs/tool.gpt +++ b/google/docs/tool.gpt @@ -1,18 +1,38 @@ --- -Name: Google Docs +Name: Google Docs Metadata: bundle: true Description: Tools for managing Google Docs -Share Tools: Read Doc +Share Tools: Read Google Doc, Create Google Doc, Update Google Doc --- -Name: Read Doc -Description: Returns the content of a Google Doc in markdown format +Name: Read Google Doc +Description: Returns the content of a Google Doc in markdown format. Share Context: Google Docs Context Credential: ../credential Param: doc_ref: Google Docs ID or share link of the document to read #!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/read_doc.py +--- +Name: Update Google Doc +Description: Updates a Google Doc +Share Tools: Create Google Doc +Share Context: Google Docs Context, Update Google Doc Context +Credential: ../credential +Param: doc_ref: Google Docs ID or share link of the document to read +Param: new_doc_content: Markdown formatted content to replace the existing content of the document with + +#!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/update_doc.py + +--- +Name: Create Google Doc +Description: Creates a blank Google Doc +Share Context: Google Docs Context +Credential: ../credential +Param: doc_title: The title of the document to create + +#!/usr/bin/env python3 ${GPTSCRIPT_TOOL_DIR}/create_doc.py + --- Name: Google Docs Context Type: context @@ -28,6 +48,21 @@ If the user does not provide a URL for the Google Doc they want to work with, as ## End of instructions for using Google Docs tools + +--- +Name: Update Google Doc Context +Type: context + +#!sys.echo + +## Instructions for using the Update Google Doc tool + +When calling the Update Google Doc tool, provide the entire modified document as a markdown formatted +string using the new_doc_content parameter. The entire document is replaced by the content of the new_doc_content +argument. + +## End of instructions for using the Update Google Doc tool + --- !metadata:*:category Google Docs diff --git a/google/docs/update_doc.py b/google/docs/update_doc.py new file mode 100644 index 00000000..232fc810 --- /dev/null +++ b/google/docs/update_doc.py @@ -0,0 +1,139 @@ +import sys +import os +import json + +import markdown +from bs4 import BeautifulSoup + +from auth import client +from id import extract_file_id + +def markdown_to_google_doc_requests(markdown_content): + # Convert markdown content to HTML + html_content = markdown.markdown(markdown_content) + + # Parse the HTML content using BeautifulSoup + soup = BeautifulSoup(html_content, 'html.parser') + + requests = [] + + # Track the current index for insertion + current_index = 1 + + # Helper to add text with styles + def add_text_request(text, bold=False, italic=False, underline=False, link=None): + nonlocal current_index + text_style = {} + # Add styles only if explicitly specified + if bold: + text_style['bold'] = True + if italic: + text_style['italic'] = True + if underline: + text_style['underline'] = True + if link: + text_style['link'] = {"url": link} + + # Add text insertion request + text_length = len(text) + requests.append({ + "insertText": { + "location": {"index": current_index}, + "text": text + } + }) + + # Add styling request only if any styles are present + if text_style: + requests.append({ + "updateTextStyle": { + "range": { + "startIndex": current_index, + "endIndex": current_index + text_length + }, + "textStyle": text_style, + "fields": ",".join(text_style.keys()) + } + }) + + # Update the current index to account for the added text + current_index += text_length + + # Process elements in the markdown + for element in soup.contents: + if element.name in ['p']: + add_text_request(element.get_text() + "\n") + elif element.name in ['h1', 'h2', 'h3']: + # Apply bold style for headers + add_text_request(element.get_text() + "\n", bold=True) + elif element.name in ['ul']: + for li in element.find_all('li'): + add_text_request("\u2022 " + li.get_text() + "\n") + elif element.name in ['ol']: + for i, li in enumerate(element.find_all('li'), start=1): + add_text_request(f"{i}. " + li.get_text() + "\n") + elif element.name == 'a': + # Add link + add_text_request(element.get_text(), link=element['href']) + elif element.name == 'table': + for row in element.find_all('tr'): + row_text = "\t".join([cell.get_text() for cell in row.find_all(['td', 'th'])]) + "\n" + add_text_request(row_text) + else: + # Default handling for unknown elements + add_text_request(element.get_text() + "\n") + + return requests + + +def main(): + try: + doc_ref = os.getenv('DOC_REF') + new_doc_content = os.getenv('NEW_DOC_CONTENT') + + if not doc_ref: + raise ValueError('DOC_REF environment variable is missing or empty') + + if not new_doc_content: + raise ValueError('NEW_DOC_CONTENT environment variable is missing or empty') + + try: + requests = markdown_to_google_doc_requests(new_doc_content) + except Exception as e: + raise ValueError(f"Failed to parse NEW_DOC_CONTENT: {e}") + + file_id = extract_file_id(doc_ref) + service = client('docs', 'v1') + + # Retrieve the document to determine its length + document = service.documents().get(documentId=file_id).execute() + content = document.get('body').get('content') + document_length = content[-1].get('endIndex') if content and 'endIndex' in content[-1] else 1 + + if document_length > 2: + # Prepare requests to clear existing document content + requests = [ + { + "deleteContentRange": { + "range": { + "startIndex": 1, + "endIndex": document_length - 1 + } + } + } + ] + requests + + # Issue a batch update request to clear and apply new content + response = service.documents().batchUpdate( + documentId=file_id, + body={"requests": requests} + ).execute() + + print(f"Document updated successfully: {file_id}") + + except Exception as err: + sys.stderr.write(f"Error: {err}\n") + sys.exit(1) + +if __name__ == "__main__": + main()