arcade-google-slides 0.1.0__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.
@@ -0,0 +1,99 @@
1
+ from typing import Annotated
2
+
3
+ from arcade_tdk import ToolContext, tool
4
+ from arcade_tdk.auth import Google
5
+
6
+ from arcade_google_slides.utils import build_drive_service
7
+
8
+
9
+ @tool(
10
+ requires_auth=Google(
11
+ scopes=[
12
+ "https://www.googleapis.com/auth/drive.file",
13
+ ],
14
+ )
15
+ )
16
+ async def comment_on_presentation(
17
+ context: ToolContext,
18
+ presentation_id: Annotated[str, "The ID of the presentation to comment on"],
19
+ comment_text: Annotated[str, "The comment to add to the slide"],
20
+ ) -> Annotated[dict, "The comment's ID, presentationId, and slideNumber in a dictionary"]:
21
+ """
22
+ Comment on a specific slide by its index in a Google Slides presentation.
23
+ """
24
+ drive_service = build_drive_service(context.get_auth_token_or_empty())
25
+
26
+ # Get the presentation
27
+ response = (
28
+ drive_service.comments()
29
+ .create(
30
+ fileId=presentation_id,
31
+ body={
32
+ "content": comment_text,
33
+ },
34
+ fields="id",
35
+ )
36
+ .execute()
37
+ )
38
+
39
+ return {
40
+ "comment_id": response["id"],
41
+ "presentation_url": f"https://docs.google.com/presentation/d/{presentation_id}",
42
+ }
43
+
44
+
45
+ @tool(
46
+ requires_auth=Google(
47
+ scopes=[
48
+ "https://www.googleapis.com/auth/drive.file",
49
+ ],
50
+ )
51
+ )
52
+ async def list_presentation_comments(
53
+ context: ToolContext,
54
+ presentation_id: Annotated[str, "The ID of the presentation to list comments for"],
55
+ include_deleted: Annotated[
56
+ bool,
57
+ "Whether to include deleted comments in the results. Defaults to False.",
58
+ ] = False,
59
+ ) -> Annotated[
60
+ dict,
61
+ "A dictionary containing the comments",
62
+ ]:
63
+ """
64
+ List all comments on the specified Google Slides presentation.
65
+ """
66
+ drive_service = build_drive_service(context.get_auth_token_or_empty())
67
+
68
+ comments: list[dict] = []
69
+ params: dict = {
70
+ "fileId": presentation_id,
71
+ "pageSize": 100,
72
+ "fields": (
73
+ "nextPageToken,comments(id,content,createdTime,modifiedTime,deleted,"
74
+ "author(displayName,emailAddress),replies(id,content,createdTime,modifiedTime,deleted,author(displayName,emailAddress)))"
75
+ ),
76
+ }
77
+ if include_deleted:
78
+ params["includeDeleted"] = True
79
+
80
+ while True:
81
+ results = drive_service.comments().list(**params).execute()
82
+ batch = results.get("comments", [])
83
+ comments.extend(batch)
84
+ next_page_token = results.get("nextPageToken")
85
+ if not next_page_token:
86
+ break
87
+ params["pageToken"] = next_page_token
88
+
89
+ reply_count = 0
90
+ for comment in comments:
91
+ reply_count += len(comment.get("replies", []))
92
+
93
+ return {
94
+ "comments_count": len(comments),
95
+ "replies_count": reply_count,
96
+ "total_discussion_count": len(comments) + reply_count,
97
+ "comments": comments,
98
+ "presentation_url": f"https://docs.google.com/presentation/d/{presentation_id}",
99
+ }
@@ -0,0 +1,122 @@
1
+ import uuid
2
+ from typing import Annotated
3
+
4
+ from arcade_tdk import ToolContext, tool
5
+ from arcade_tdk.auth import Google
6
+
7
+ from arcade_google_slides.enum import PlaceholderType
8
+ from arcade_google_slides.types import (
9
+ Page,
10
+ PredefinedLayout,
11
+ Presentation,
12
+ Request,
13
+ )
14
+ from arcade_google_slides.utils import build_slides_service, create_blank_presentation
15
+
16
+
17
+ @tool(
18
+ requires_auth=Google(
19
+ scopes=[
20
+ "https://www.googleapis.com/auth/drive.file",
21
+ ],
22
+ )
23
+ )
24
+ async def create_presentation(
25
+ context: ToolContext,
26
+ title: Annotated[str, "The title of the presentation to create"],
27
+ subtitle: Annotated[str | None, "The subtitle of the presentation to create"] = None,
28
+ ) -> Annotated[dict, "The created presentation's title, presentationId, and presentationUrl"]:
29
+ """
30
+ Create a new Google Slides presentation
31
+ The first slide will be populated with the specified title and subtitle.
32
+ """
33
+ service = build_slides_service(context.get_auth_token_or_empty())
34
+
35
+ presentation: Presentation = create_blank_presentation(service, title)
36
+
37
+ slide: Page = presentation["slides"][0]
38
+ title_text_box_id = slide["pageElements"][0]["objectId"]
39
+ subtitle_text_box_id = slide["pageElements"][1]["objectId"]
40
+
41
+ requests: list[Request] = [
42
+ {
43
+ "insertText": {
44
+ "objectId": title_text_box_id,
45
+ "text": title,
46
+ }
47
+ },
48
+ {
49
+ "insertText": {
50
+ "objectId": subtitle_text_box_id,
51
+ "text": subtitle if subtitle else "",
52
+ }
53
+ },
54
+ ]
55
+
56
+ service.presentations().batchUpdate(
57
+ presentationId=presentation["presentationId"], body={"requests": requests}
58
+ ).execute()
59
+
60
+ return {
61
+ "presentation_title": presentation["title"],
62
+ "presentation_id": presentation["presentationId"],
63
+ "presentation_url": f"https://docs.google.com/presentation/d/{presentation['presentationId']}/edit",
64
+ }
65
+
66
+
67
+ @tool(
68
+ requires_auth=Google(
69
+ scopes=[
70
+ "https://www.googleapis.com/auth/drive.file",
71
+ ],
72
+ )
73
+ )
74
+ async def create_slide(
75
+ context: ToolContext,
76
+ presentation_id: Annotated[str, "The ID of the presentation to create the slide in"],
77
+ slide_title: Annotated[str, "The title of the slide to create"],
78
+ slide_body: Annotated[str, "The body (text) of the slide to create"],
79
+ ) -> Annotated[dict, "A URL to the created slide"]:
80
+ """Create a new slide at the end of the specified presentation"""
81
+ service = build_slides_service(context.get_auth_token_or_empty())
82
+
83
+ title_id = str(uuid.uuid4())[:8]
84
+ body_id = str(uuid.uuid4())[:8]
85
+
86
+ create_slide_request: Request = {
87
+ "createSlide": {
88
+ "objectId": "", # NOTE: We can utilize creating custom IDs for easier lookup/edits
89
+ "slideLayoutReference": {
90
+ "predefinedLayout": PredefinedLayout.TITLE_AND_BODY,
91
+ },
92
+ "placeholderIdMappings": [
93
+ {
94
+ "layoutPlaceholder": {"type": PlaceholderType.TITLE, "index": 0},
95
+ "objectId": title_id,
96
+ },
97
+ {
98
+ "layoutPlaceholder": {"type": PlaceholderType.BODY, "index": 0},
99
+ "objectId": body_id,
100
+ },
101
+ ],
102
+ },
103
+ }
104
+
105
+ insert_text_requests: list[Request] = [
106
+ {"insertText": {"objectId": title_id, "text": slide_title}},
107
+ {"insertText": {"objectId": body_id, "text": slide_body}},
108
+ ]
109
+
110
+ response = (
111
+ service.presentations()
112
+ .batchUpdate(
113
+ presentationId=presentation_id,
114
+ body={"requests": [create_slide_request, *insert_text_requests]},
115
+ )
116
+ .execute()
117
+ )
118
+
119
+ slide_id = response["replies"][0]["createSlide"]["objectId"]
120
+ return {
121
+ "slide_url": f"https://docs.google.com/presentation/d/{presentation_id}/edit?slide=id.{slide_id}"
122
+ }
@@ -0,0 +1,33 @@
1
+ from typing import Annotated, cast
2
+
3
+ from arcade_tdk import ToolContext, ToolMetadataKey, tool
4
+ from arcade_tdk.auth import Google
5
+
6
+ from arcade_google_slides.decorators import with_filepicker_fallback
7
+ from arcade_google_slides.types import Presentation
8
+ from arcade_google_slides.utils import build_slides_service, convert_presentation_to_markdown
9
+
10
+
11
+ @tool(
12
+ requires_auth=Google(
13
+ scopes=[
14
+ "https://www.googleapis.com/auth/drive.file",
15
+ ],
16
+ ),
17
+ requires_metadata=[ToolMetadataKey.CLIENT_ID, ToolMetadataKey.COORDINATOR_URL],
18
+ )
19
+ @with_filepicker_fallback
20
+ async def get_presentation_as_markdown(
21
+ context: ToolContext,
22
+ presentation_id: Annotated[str, "The ID of the presentation to retrieve."],
23
+ ) -> Annotated[str, "The presentation textual content as markdown"]:
24
+ """
25
+ Get the specified Google Slides presentation and convert it to markdown.
26
+
27
+ Only retrieves the text content of the presentation and formats it as markdown.
28
+ """
29
+ service = build_slides_service(context.get_auth_token_or_empty())
30
+
31
+ response = service.presentations().get(presentationId=presentation_id).execute()
32
+
33
+ return convert_presentation_to_markdown(cast(Presentation, response))
@@ -0,0 +1,125 @@
1
+ from typing import Annotated, Any
2
+
3
+ from arcade_tdk import ToolContext, ToolMetadataKey, tool
4
+ from arcade_tdk.auth import Google
5
+
6
+ from arcade_google_slides.enum import OrderBy
7
+ from arcade_google_slides.file_picker import generate_google_file_picker_url
8
+ from arcade_google_slides.templates import optional_file_picker_instructions_template
9
+ from arcade_google_slides.utils import (
10
+ build_drive_service,
11
+ build_files_list_params,
12
+ )
13
+
14
+
15
+ # Implements: https://googleapis.github.io/google-api-python-client/docs/dyn/drive_v3.files.html#list
16
+ # Example `arcade chat` query: `list my 5 most recently modified presentations`
17
+ # TODO: Support query with natural language. Currently, the tool expects a fully formed query
18
+ # string as input with the syntax defined here: https://developers.google.com/drive/api/guides/search-files
19
+ @tool(
20
+ requires_auth=Google(
21
+ scopes=["https://www.googleapis.com/auth/drive.file"],
22
+ ),
23
+ requires_metadata=[ToolMetadataKey.CLIENT_ID, ToolMetadataKey.COORDINATOR_URL],
24
+ )
25
+ async def search_presentations(
26
+ context: ToolContext,
27
+ presentation_contains: Annotated[
28
+ list[str] | None,
29
+ "Keywords or phrases that must be in the presentation title or content. Provide a list of "
30
+ "keywords or phrases if needed.",
31
+ ] = None,
32
+ presentation_not_contains: Annotated[
33
+ list[str] | None,
34
+ "Keywords or phrases that must NOT be in the presentation title or content. "
35
+ "Provide a list of keywords or phrases if needed.",
36
+ ] = None,
37
+ search_only_in_shared_drive_id: Annotated[
38
+ str | None,
39
+ "The ID of the shared drive to restrict the search to. If provided, the search will only "
40
+ "return presentations from this drive. Defaults to None, which searches across all drives.",
41
+ ] = None,
42
+ include_shared_drives: Annotated[
43
+ bool,
44
+ "Whether to include presentations from shared drives. Defaults to False (searches only in "
45
+ "the user's 'My Drive').",
46
+ ] = False,
47
+ include_organization_domain_presentations: Annotated[
48
+ bool,
49
+ "Whether to include presentations from the organization's domain. "
50
+ "This is applicable to admin users who have permissions to view "
51
+ "organization-wide presentations in a Google Workspace "
52
+ "account. Defaults to False.",
53
+ ] = False,
54
+ order_by: Annotated[
55
+ list[OrderBy] | None,
56
+ "Sort order. Defaults to listing the most recently modified presentations first",
57
+ ] = None,
58
+ limit: Annotated[int, "The number of presentations to list"] = 50,
59
+ pagination_token: Annotated[
60
+ str | None, "The pagination token to continue a previous request"
61
+ ] = None,
62
+ ) -> Annotated[
63
+ dict,
64
+ "A dictionary containing 'presentations_count' (number of presentations returned) "
65
+ "and 'presentations' (a list of presentation details including 'kind', 'mimeType', "
66
+ "'id', and 'name' for each presentation)",
67
+ ]:
68
+ """
69
+ Searches for presentations in the user's Google Drive.
70
+ Excludes presentations that are in the trash.
71
+ """
72
+ if order_by is None:
73
+ order_by = [OrderBy.MODIFIED_TIME_DESC]
74
+ elif isinstance(order_by, OrderBy):
75
+ order_by = [order_by]
76
+
77
+ page_size = min(10, limit)
78
+ files: list[dict[str, Any]] = []
79
+
80
+ service = build_drive_service(context.get_auth_token_or_empty())
81
+
82
+ params = build_files_list_params(
83
+ mime_type="application/vnd.google-apps.presentation",
84
+ document_contains=presentation_contains,
85
+ document_not_contains=presentation_not_contains,
86
+ page_size=page_size,
87
+ order_by=order_by,
88
+ pagination_token=pagination_token,
89
+ include_shared_drives=include_shared_drives,
90
+ search_only_in_shared_drive_id=search_only_in_shared_drive_id,
91
+ include_organization_domain_documents=include_organization_domain_presentations,
92
+ )
93
+
94
+ while len(files) < limit:
95
+ if pagination_token:
96
+ params["pageToken"] = pagination_token
97
+ else:
98
+ params.pop("pageToken", None)
99
+
100
+ results = service.files().list(**params).execute()
101
+ batch = results.get("files", [])
102
+ files.extend(batch[: limit - len(files)])
103
+
104
+ pagination_token = results.get("nextPageToken")
105
+ if not pagination_token or len(batch) < page_size:
106
+ break
107
+
108
+ file_picker_response = generate_google_file_picker_url(
109
+ context,
110
+ )
111
+
112
+ return {
113
+ "presentations_count": len(files),
114
+ "presentations": files,
115
+ "file_picker": {
116
+ "url": file_picker_response["url"],
117
+ "llm_instructions": optional_file_picker_instructions_template.format(
118
+ url=file_picker_response["url"]
119
+ ),
120
+ },
121
+ "llm_instructions": (
122
+ "If the results were not satisfactory, then inform the user that "
123
+ "you can also search across all of their shared drives."
124
+ ),
125
+ }
@@ -0,0 +1,223 @@
1
+ from typing import TypedDict
2
+
3
+ from arcade_google_slides.enum import PlaceholderType, PredefinedLayout, ShapeType
4
+
5
+
6
+ class Bullet(TypedDict):
7
+ """Partial implementation of the REST Resource Bullet.
8
+
9
+ Represents a bullet in a TextElement.
10
+
11
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.Bullet
12
+ """
13
+
14
+ nestingLevel: int # The nesting level of this paragraph in the list.
15
+
16
+
17
+ class ParagraphMarker(TypedDict):
18
+ """Partial implementation of the REST Resource ParagraphMarker.
19
+
20
+ Represents a paragraph marker in a TextElement.
21
+
22
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.ParagraphMarker
23
+ """
24
+
25
+ bullet: Bullet
26
+
27
+
28
+ class Link(TypedDict):
29
+ """Partial implementation of the REST Resource Link.
30
+
31
+ Represents a hypertext link.
32
+
33
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/other#Page.Link
34
+ """
35
+
36
+ url: str
37
+
38
+
39
+ class TextStyle(TypedDict):
40
+ """Partial implementation of the REST Resource TextStyle.
41
+
42
+ Represents the styling that can be applied to a TextRun.
43
+
44
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.TextStyle
45
+ """
46
+
47
+ bold: bool
48
+ italic: bool
49
+ strikethrough: bool
50
+ underline: bool
51
+ link: Link
52
+
53
+
54
+ class TextRun(TypedDict):
55
+ """Partial implementation of the REST Resource TextRun.
56
+
57
+ A TextElement kind that represents a run of text that all has the same styling.
58
+
59
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.TextRun
60
+ """
61
+
62
+ content: str # The text of this run.
63
+ style: TextStyle
64
+
65
+
66
+ class TextElementWithParagraphMarker(TypedDict):
67
+ """Partial implementation of the REST Resource TextElement.
68
+
69
+ TextElement containing a paragraph marker.
70
+
71
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.TextElement
72
+ """
73
+
74
+ paragraphMarker: ParagraphMarker
75
+
76
+
77
+ class TextElementWithTextRun(TypedDict):
78
+ """Partial implementation of the REST Resource TextElement.
79
+
80
+ TextElement containing a text run.
81
+
82
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.TextElement
83
+ """
84
+
85
+ textRun: TextRun
86
+
87
+
88
+ # https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.TextElement
89
+ TextElement = TextElementWithParagraphMarker | TextElementWithTextRun
90
+
91
+
92
+ class TextContent(TypedDict):
93
+ """Partial implementation of the REST Resource TextContent.
94
+
95
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/text#Page.TextContent
96
+ """
97
+
98
+ textElements: list[TextElement]
99
+
100
+
101
+ class Shape(TypedDict):
102
+ """Partial implementation of the REST Resource Shape.
103
+
104
+
105
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/shapes#Page.Shape
106
+ """
107
+
108
+ shapeType: ShapeType
109
+ text: TextContent
110
+
111
+
112
+ class PageElement(TypedDict):
113
+ """Partial implementation of the REST Resource PageElement.
114
+
115
+ A visual element on a page.
116
+
117
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages#Page.PageElement
118
+ """
119
+
120
+ objectId: str
121
+ shape: Shape
122
+
123
+
124
+ class SlideProperties(TypedDict):
125
+ """Partial implementation of the REST Resource SlideProperties.
126
+
127
+ The properties of Page that are only relevant for pages with pageType SLIDE.
128
+
129
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages#Page.SlideProperties
130
+ """
131
+
132
+ # Whether the slide is skipped (hidden) in the presentation mode. Defaults to false.
133
+ isSkipped: bool
134
+
135
+
136
+ class Page(TypedDict):
137
+ """Partial implementation of the REST Resource Page.
138
+
139
+ A page is a single slide in a presentation.
140
+
141
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages#Page
142
+ """
143
+
144
+ objectId: str
145
+ pageElements: list[PageElement]
146
+ slideProperties: SlideProperties
147
+
148
+
149
+ class Presentation(TypedDict):
150
+ """Partial implementation of the REST Resource Presentation.
151
+
152
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations#Presentation
153
+ """
154
+
155
+ presentationId: str | None # None when creating a new presentation
156
+ title: str
157
+ slides: list[Page]
158
+
159
+
160
+ class InsertTextRequest(TypedDict):
161
+ """Partial implementation of the REST Resource InsertTextRequest.
162
+
163
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#InsertTextRequest
164
+ """
165
+
166
+ objectId: str
167
+ text: str
168
+
169
+
170
+ class Placeholder(TypedDict):
171
+ """Partial implementation of the REST Resource Placeholder.
172
+
173
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations.pages/other#Page.Placeholder
174
+ """
175
+
176
+ type: PlaceholderType
177
+ index: int
178
+
179
+
180
+ class LayoutPlaceholderIdMapping(TypedDict):
181
+ """Partial implementation of the REST Resource LayoutPlaceholderIdMapping.
182
+
183
+ The user-specified ID mapping for a placeholder that will be
184
+ created on a slide from a specified layout.
185
+
186
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#LayoutPlaceholderIdMapping
187
+ """
188
+
189
+ objectId: str
190
+ layoutPlaceholder: Placeholder
191
+
192
+
193
+ class LayoutReference(TypedDict):
194
+ """Partial implementation of the REST Resource LayoutReference.
195
+
196
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#LayoutReference
197
+ """
198
+
199
+ predefinedLayout: PredefinedLayout
200
+
201
+
202
+ class CreateSlideRequest(TypedDict, total=False):
203
+ """Partial implementation of the REST Resource CreateSlideRequest.
204
+
205
+ insertionIndex is not required
206
+
207
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#CreateSlideRequest
208
+ """
209
+
210
+ objectId: str
211
+ insertionIndex: int # The zero-based index where the new slide should be inserted. If not provided, the slide will be added to the end of the presentation. # noqa: E501
212
+ slideLayoutReference: LayoutReference
213
+ placeholderIdMappings: list[LayoutPlaceholderIdMapping]
214
+
215
+
216
+ class Request(TypedDict, total=False):
217
+ """Partial implementation of the REST Resource Request.
218
+
219
+ Reference: https://developers.google.com/workspace/slides/api/reference/rest/v1/presentations/request#Request
220
+ """
221
+
222
+ createSlide: CreateSlideRequest
223
+ insertText: InsertTextRequest