@piotr-agier/google-drive-mcp 2.0.1 → 2.1.0
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.
- package/dist/index.js +364 -65
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2555,9 +2555,62 @@ __export(docs_exports, {
|
|
|
2555
2555
|
toolDefinitions: () => toolDefinitions2
|
|
2556
2556
|
});
|
|
2557
2557
|
import { z as z2 } from "zod";
|
|
2558
|
-
import { createReadStream as createReadStream2, existsSync as existsSync3 } from "fs";
|
|
2559
|
-
import { basename as basename3, extname as extname3 } from "path";
|
|
2560
2558
|
import JSZip from "jszip";
|
|
2559
|
+
|
|
2560
|
+
// src/utils/driveImageUpload.ts
|
|
2561
|
+
import { existsSync as existsSync3, createReadStream as createReadStream2 } from "fs";
|
|
2562
|
+
import { basename as basename3, extname as extname3 } from "path";
|
|
2563
|
+
var MIME_BY_EXT = {
|
|
2564
|
+
".jpg": "image/jpeg",
|
|
2565
|
+
".jpeg": "image/jpeg",
|
|
2566
|
+
".png": "image/png",
|
|
2567
|
+
".gif": "image/gif",
|
|
2568
|
+
".bmp": "image/bmp",
|
|
2569
|
+
".webp": "image/webp",
|
|
2570
|
+
".svg": "image/svg+xml"
|
|
2571
|
+
};
|
|
2572
|
+
async function uploadImageToDrive(ctx, localFilePath, options = {}) {
|
|
2573
|
+
const { parentFolderId, makePublic = false } = options;
|
|
2574
|
+
if (!existsSync3(localFilePath)) {
|
|
2575
|
+
throw new Error(`Image file not found: ${localFilePath}`);
|
|
2576
|
+
}
|
|
2577
|
+
const fileName = basename3(localFilePath);
|
|
2578
|
+
const ext = extname3(localFilePath).toLowerCase();
|
|
2579
|
+
const mimeType = MIME_BY_EXT[ext] || "application/octet-stream";
|
|
2580
|
+
const requestBody = {
|
|
2581
|
+
name: fileName,
|
|
2582
|
+
mimeType
|
|
2583
|
+
};
|
|
2584
|
+
if (parentFolderId) requestBody.parents = [parentFolderId];
|
|
2585
|
+
const drive = ctx.getDrive();
|
|
2586
|
+
const uploadResponse = await drive.files.create({
|
|
2587
|
+
requestBody,
|
|
2588
|
+
media: { mimeType, body: createReadStream2(localFilePath) },
|
|
2589
|
+
fields: "id,webViewLink,webContentLink",
|
|
2590
|
+
supportsAllDrives: true
|
|
2591
|
+
});
|
|
2592
|
+
const fileId = uploadResponse.data.id;
|
|
2593
|
+
if (!fileId) throw new Error("Failed to upload image to Drive - no file ID returned");
|
|
2594
|
+
if (makePublic) {
|
|
2595
|
+
await drive.permissions.create({
|
|
2596
|
+
fileId,
|
|
2597
|
+
requestBody: { role: "reader", type: "anyone" }
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
const fileInfo = await drive.files.get({
|
|
2601
|
+
fileId,
|
|
2602
|
+
fields: "webContentLink",
|
|
2603
|
+
supportsAllDrives: true
|
|
2604
|
+
});
|
|
2605
|
+
const webContentLink = fileInfo.data.webContentLink;
|
|
2606
|
+
if (!webContentLink) throw new Error("Failed to get web content link for uploaded image");
|
|
2607
|
+
return { fileId, webContentLink };
|
|
2608
|
+
}
|
|
2609
|
+
async function deleteDriveFile(ctx, fileId) {
|
|
2610
|
+
await ctx.getDrive().files.delete({ fileId, supportsAllDrives: true });
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// src/tools/docs.ts
|
|
2561
2614
|
function hexToRgbColor(hex) {
|
|
2562
2615
|
if (!hex) return null;
|
|
2563
2616
|
let hexClean = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
@@ -2853,64 +2906,6 @@ async function insertInlineImageHelper(ctx, documentId, imageUrl, index, width,
|
|
|
2853
2906
|
}
|
|
2854
2907
|
return executeBatchUpdate(ctx, documentId, [request]);
|
|
2855
2908
|
}
|
|
2856
|
-
async function uploadImageToDriveHelper(ctx, localFilePath, parentFolderId, makePublic = false) {
|
|
2857
|
-
if (!existsSync3(localFilePath)) {
|
|
2858
|
-
throw new Error(`Image file not found: ${localFilePath}`);
|
|
2859
|
-
}
|
|
2860
|
-
const fileName = basename3(localFilePath);
|
|
2861
|
-
const mimeTypeMap = {
|
|
2862
|
-
".jpg": "image/jpeg",
|
|
2863
|
-
".jpeg": "image/jpeg",
|
|
2864
|
-
".png": "image/png",
|
|
2865
|
-
".gif": "image/gif",
|
|
2866
|
-
".bmp": "image/bmp",
|
|
2867
|
-
".webp": "image/webp",
|
|
2868
|
-
".svg": "image/svg+xml"
|
|
2869
|
-
};
|
|
2870
|
-
const ext = extname3(localFilePath).toLowerCase();
|
|
2871
|
-
const mimeType = mimeTypeMap[ext] || "application/octet-stream";
|
|
2872
|
-
const fileMetadata = {
|
|
2873
|
-
name: fileName,
|
|
2874
|
-
mimeType
|
|
2875
|
-
};
|
|
2876
|
-
if (parentFolderId) {
|
|
2877
|
-
fileMetadata.parents = [parentFolderId];
|
|
2878
|
-
}
|
|
2879
|
-
const media = {
|
|
2880
|
-
mimeType,
|
|
2881
|
-
body: createReadStream2(localFilePath)
|
|
2882
|
-
};
|
|
2883
|
-
const drive = ctx.getDrive();
|
|
2884
|
-
const uploadResponse = await drive.files.create({
|
|
2885
|
-
requestBody: fileMetadata,
|
|
2886
|
-
media,
|
|
2887
|
-
fields: "id,webViewLink,webContentLink",
|
|
2888
|
-
supportsAllDrives: true
|
|
2889
|
-
});
|
|
2890
|
-
const fileId = uploadResponse.data.id;
|
|
2891
|
-
if (!fileId) {
|
|
2892
|
-
throw new Error("Failed to upload image to Drive - no file ID returned");
|
|
2893
|
-
}
|
|
2894
|
-
if (makePublic) {
|
|
2895
|
-
await drive.permissions.create({
|
|
2896
|
-
fileId,
|
|
2897
|
-
requestBody: {
|
|
2898
|
-
role: "reader",
|
|
2899
|
-
type: "anyone"
|
|
2900
|
-
}
|
|
2901
|
-
});
|
|
2902
|
-
}
|
|
2903
|
-
const fileInfo = await drive.files.get({
|
|
2904
|
-
fileId,
|
|
2905
|
-
fields: "webContentLink",
|
|
2906
|
-
supportsAllDrives: true
|
|
2907
|
-
});
|
|
2908
|
-
const webContentLink = fileInfo.data.webContentLink;
|
|
2909
|
-
if (!webContentLink) {
|
|
2910
|
-
throw new Error("Failed to get web content link for uploaded image");
|
|
2911
|
-
}
|
|
2912
|
-
return webContentLink;
|
|
2913
|
-
}
|
|
2914
2909
|
var MAX_ROW_XML_DISTANCE = 1e5;
|
|
2915
2910
|
var MAX_PARAGRAPH_XML_DISTANCE = 5e4;
|
|
2916
2911
|
var MAX_PARAGRAPH_CONTEXT_LENGTH = 300;
|
|
@@ -5008,7 +5003,10 @@ ${index + 1}. ${replyAuthor} (${replyDate})
|
|
|
5008
5003
|
});
|
|
5009
5004
|
parentFolderId = fileInfo.data.parents?.[0];
|
|
5010
5005
|
}
|
|
5011
|
-
const imageUrl = await
|
|
5006
|
+
const { webContentLink: imageUrl } = await uploadImageToDrive(ctx, a.localImagePath, {
|
|
5007
|
+
parentFolderId,
|
|
5008
|
+
makePublic: a.makePublic
|
|
5009
|
+
});
|
|
5012
5010
|
await insertInlineImageHelper(ctx, a.documentId, imageUrl, a.index, a.width, a.height);
|
|
5013
5011
|
return {
|
|
5014
5012
|
content: [{ type: "text", text: `Successfully uploaded and inserted local image at index ${a.index}
|
|
@@ -5121,8 +5119,8 @@ Image URL: ${imageUrl}` }],
|
|
|
5121
5119
|
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
5122
5120
|
await docs.documents.batchUpdate({
|
|
5123
5121
|
documentId: a.documentId,
|
|
5124
|
-
//
|
|
5125
|
-
requestBody: { requests: [{
|
|
5122
|
+
// addDocumentTab is not yet in the googleapis TypeScript types — cast required
|
|
5123
|
+
requestBody: { requests: [{ addDocumentTab: { tabProperties: { title: a.title } } }] }
|
|
5126
5124
|
});
|
|
5127
5125
|
return { content: [{ type: "text", text: `Requested creation of tab "${a.title}" in document ${a.documentId}.` }], isError: false };
|
|
5128
5126
|
}
|
|
@@ -5133,8 +5131,8 @@ Image URL: ${imageUrl}` }],
|
|
|
5133
5131
|
const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
|
|
5134
5132
|
await docs.documents.batchUpdate({
|
|
5135
5133
|
documentId: a.documentId,
|
|
5136
|
-
//
|
|
5137
|
-
requestBody: { requests: [{
|
|
5134
|
+
// updateDocumentTabProperties is not yet in the googleapis TypeScript types — cast required
|
|
5135
|
+
requestBody: { requests: [{ updateDocumentTabProperties: { tabId: a.tabId, tabProperties: { title: a.title }, fields: "title" } }] }
|
|
5138
5136
|
});
|
|
5139
5137
|
return { content: [{ type: "text", text: `Renamed tab ${a.tabId} to "${a.title}".` }], isError: false };
|
|
5140
5138
|
}
|
|
@@ -6629,6 +6627,72 @@ var ExportSlideThumbnailSchema = z4.object({
|
|
|
6629
6627
|
mimeType: z4.enum(["PNG", "JPEG"]).optional().default("PNG"),
|
|
6630
6628
|
size: z4.enum(["SMALL", "MEDIUM", "LARGE"]).optional().default("LARGE")
|
|
6631
6629
|
});
|
|
6630
|
+
var InsertSlidesImageFromUrlSchema = z4.object({
|
|
6631
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6632
|
+
pageObjectId: z4.string().min(1, "Slide/page object ID is required"),
|
|
6633
|
+
imageUrl: z4.string().url("A valid image URL is required"),
|
|
6634
|
+
x: z4.number().optional().default(0),
|
|
6635
|
+
y: z4.number().optional().default(0),
|
|
6636
|
+
width: z4.number().optional(),
|
|
6637
|
+
height: z4.number().optional()
|
|
6638
|
+
});
|
|
6639
|
+
var MoveSlideElementSchema = z4.object({
|
|
6640
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6641
|
+
objectId: z4.string().min(1, "Element object ID is required"),
|
|
6642
|
+
x: z4.number().optional(),
|
|
6643
|
+
y: z4.number().optional(),
|
|
6644
|
+
width: z4.number().optional(),
|
|
6645
|
+
height: z4.number().optional()
|
|
6646
|
+
});
|
|
6647
|
+
var DeleteSlideElementSchema = z4.object({
|
|
6648
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6649
|
+
objectId: z4.string().min(1, "Element object ID is required")
|
|
6650
|
+
});
|
|
6651
|
+
var GetSlideElementInfoSchema = z4.object({
|
|
6652
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6653
|
+
slideObjectId: z4.string().optional()
|
|
6654
|
+
});
|
|
6655
|
+
var InsertSlidesLocalImageSchema = z4.object({
|
|
6656
|
+
presentationId: z4.string().min(1, "Presentation ID is required"),
|
|
6657
|
+
pageObjectId: z4.string().min(1, "Slide/page object ID is required"),
|
|
6658
|
+
localImagePath: z4.string().min(1, "Local image path is required"),
|
|
6659
|
+
x: z4.number().optional().default(0),
|
|
6660
|
+
y: z4.number().optional().default(0),
|
|
6661
|
+
width: z4.number().optional(),
|
|
6662
|
+
height: z4.number().optional()
|
|
6663
|
+
});
|
|
6664
|
+
async function insertImageIntoSlide(ctx, presentationId, pageObjectId, imageUrl, x, y, width, height) {
|
|
6665
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
6666
|
+
const objectId = `img_${uuidv4().substring(0, 8)}`;
|
|
6667
|
+
const elementProperties = {
|
|
6668
|
+
pageObjectId,
|
|
6669
|
+
transform: {
|
|
6670
|
+
scaleX: 1,
|
|
6671
|
+
scaleY: 1,
|
|
6672
|
+
translateX: x,
|
|
6673
|
+
translateY: y,
|
|
6674
|
+
unit: "EMU"
|
|
6675
|
+
}
|
|
6676
|
+
};
|
|
6677
|
+
if (width != null && height != null) {
|
|
6678
|
+
elementProperties.size = {
|
|
6679
|
+
width: { magnitude: width, unit: "EMU" },
|
|
6680
|
+
height: { magnitude: height, unit: "EMU" }
|
|
6681
|
+
};
|
|
6682
|
+
}
|
|
6683
|
+
await slidesService.presentations.batchUpdate({
|
|
6684
|
+
presentationId,
|
|
6685
|
+
requestBody: {
|
|
6686
|
+
requests: [{
|
|
6687
|
+
createImage: { objectId, url: imageUrl, elementProperties }
|
|
6688
|
+
}]
|
|
6689
|
+
}
|
|
6690
|
+
});
|
|
6691
|
+
return {
|
|
6692
|
+
content: [{ type: "text", text: `Inserted image into slide ${pageObjectId} (objectId: ${objectId})` }],
|
|
6693
|
+
isError: false
|
|
6694
|
+
};
|
|
6695
|
+
}
|
|
6632
6696
|
var toolDefinitions4 = [
|
|
6633
6697
|
{
|
|
6634
6698
|
name: "createGoogleSlides",
|
|
@@ -6942,6 +7006,80 @@ var toolDefinitions4 = [
|
|
|
6942
7006
|
},
|
|
6943
7007
|
required: ["presentationId", "slideObjectId"]
|
|
6944
7008
|
}
|
|
7009
|
+
},
|
|
7010
|
+
{
|
|
7011
|
+
name: "insertSlidesImageFromUrl",
|
|
7012
|
+
description: "Insert an image into a Google Slides slide from a publicly accessible URL",
|
|
7013
|
+
inputSchema: {
|
|
7014
|
+
type: "object",
|
|
7015
|
+
properties: {
|
|
7016
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7017
|
+
pageObjectId: { type: "string", description: "Slide/page object ID to insert the image into" },
|
|
7018
|
+
imageUrl: { type: "string", description: "Publicly accessible URL of the image" },
|
|
7019
|
+
x: { type: "number", description: "X position in EMU (default: 0)" },
|
|
7020
|
+
y: { type: "number", description: "Y position in EMU (default: 0)" },
|
|
7021
|
+
width: { type: "number", description: "Width in EMU (omit to auto-size)" },
|
|
7022
|
+
height: { type: "number", description: "Height in EMU (omit to auto-size)" }
|
|
7023
|
+
},
|
|
7024
|
+
required: ["presentationId", "pageObjectId", "imageUrl"]
|
|
7025
|
+
}
|
|
7026
|
+
},
|
|
7027
|
+
{
|
|
7028
|
+
name: "getSlideElementInfo",
|
|
7029
|
+
description: "Get position, size, and transform of all elements on a slide. Returns actual rendered bounds.",
|
|
7030
|
+
inputSchema: {
|
|
7031
|
+
type: "object",
|
|
7032
|
+
properties: {
|
|
7033
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7034
|
+
slideObjectId: { type: "string", description: "Slide object ID (omit to get all slides)" }
|
|
7035
|
+
},
|
|
7036
|
+
required: ["presentationId"]
|
|
7037
|
+
}
|
|
7038
|
+
},
|
|
7039
|
+
{
|
|
7040
|
+
name: "moveSlideElement",
|
|
7041
|
+
description: "Move and/or resize an element (image, text box, shape) on a Google Slides slide",
|
|
7042
|
+
inputSchema: {
|
|
7043
|
+
type: "object",
|
|
7044
|
+
properties: {
|
|
7045
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7046
|
+
objectId: { type: "string", description: "Element object ID to move/resize" },
|
|
7047
|
+
x: { type: "number", description: "New X position in EMU" },
|
|
7048
|
+
y: { type: "number", description: "New Y position in EMU" },
|
|
7049
|
+
width: { type: "number", description: "New width in EMU" },
|
|
7050
|
+
height: { type: "number", description: "New height in EMU" }
|
|
7051
|
+
},
|
|
7052
|
+
required: ["presentationId", "objectId"]
|
|
7053
|
+
}
|
|
7054
|
+
},
|
|
7055
|
+
{
|
|
7056
|
+
name: "deleteSlideElement",
|
|
7057
|
+
description: "Delete an element (image, text box, shape) from a Google Slides slide",
|
|
7058
|
+
inputSchema: {
|
|
7059
|
+
type: "object",
|
|
7060
|
+
properties: {
|
|
7061
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7062
|
+
objectId: { type: "string", description: "Element object ID to delete" }
|
|
7063
|
+
},
|
|
7064
|
+
required: ["presentationId", "objectId"]
|
|
7065
|
+
}
|
|
7066
|
+
},
|
|
7067
|
+
{
|
|
7068
|
+
name: "insertSlidesLocalImage",
|
|
7069
|
+
description: "Upload a local image file to Google Drive and insert it into a Google Slides slide",
|
|
7070
|
+
inputSchema: {
|
|
7071
|
+
type: "object",
|
|
7072
|
+
properties: {
|
|
7073
|
+
presentationId: { type: "string", description: "Presentation ID" },
|
|
7074
|
+
pageObjectId: { type: "string", description: "Slide/page object ID to insert the image into" },
|
|
7075
|
+
localImagePath: { type: "string", description: "Absolute path to the local image file" },
|
|
7076
|
+
x: { type: "number", description: "X position in EMU (default: 0)" },
|
|
7077
|
+
y: { type: "number", description: "Y position in EMU (default: 0)" },
|
|
7078
|
+
width: { type: "number", description: "Width in EMU (omit to auto-size)" },
|
|
7079
|
+
height: { type: "number", description: "Height in EMU (omit to auto-size)" }
|
|
7080
|
+
},
|
|
7081
|
+
required: ["presentationId", "pageObjectId", "localImagePath"]
|
|
7082
|
+
}
|
|
6945
7083
|
}
|
|
6946
7084
|
];
|
|
6947
7085
|
async function handleTool4(toolName, args, ctx) {
|
|
@@ -7779,6 +7917,167 @@ Slide ${a.slideIndex ?? index} (ID: ${slide.objectId}):
|
|
|
7779
7917
|
isError: false
|
|
7780
7918
|
};
|
|
7781
7919
|
}
|
|
7920
|
+
case "getSlideElementInfo": {
|
|
7921
|
+
const validation = GetSlideElementInfoSchema.safeParse(args);
|
|
7922
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
7923
|
+
const a = validation.data;
|
|
7924
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
7925
|
+
const sizeOnly = await slidesService.presentations.get({
|
|
7926
|
+
presentationId: a.presentationId,
|
|
7927
|
+
fields: "pageSize"
|
|
7928
|
+
});
|
|
7929
|
+
const slideWidth = sizeOnly.data.pageSize?.width?.magnitude || 9144e3;
|
|
7930
|
+
const slideHeight = sizeOnly.data.pageSize?.height?.magnitude || 6858e3;
|
|
7931
|
+
let slides = [];
|
|
7932
|
+
if (a.slideObjectId) {
|
|
7933
|
+
const page = await slidesService.presentations.pages.get({
|
|
7934
|
+
presentationId: a.presentationId,
|
|
7935
|
+
pageObjectId: a.slideObjectId,
|
|
7936
|
+
fields: "objectId,pageElements(objectId,transform,size,shape/shapeType,image)"
|
|
7937
|
+
});
|
|
7938
|
+
slides = [page.data];
|
|
7939
|
+
} else {
|
|
7940
|
+
const withSlides = await slidesService.presentations.get({
|
|
7941
|
+
presentationId: a.presentationId,
|
|
7942
|
+
fields: "slides(objectId,pageElements(objectId,transform,size,shape/shapeType,image))"
|
|
7943
|
+
});
|
|
7944
|
+
slides = withSlides.data.slides || [];
|
|
7945
|
+
}
|
|
7946
|
+
const lines = [`Slide dimensions: ${slideWidth} x ${slideHeight} EMU`];
|
|
7947
|
+
for (const slide of slides) {
|
|
7948
|
+
lines.push(`
|
|
7949
|
+
--- Slide: ${slide.objectId} ---`);
|
|
7950
|
+
for (const el of slide.pageElements || []) {
|
|
7951
|
+
const t = el.transform || {};
|
|
7952
|
+
const s = el.size || {};
|
|
7953
|
+
const intrW = s.width?.magnitude || 0;
|
|
7954
|
+
const intrH = s.height?.magnitude || 0;
|
|
7955
|
+
const scX = t.scaleX || 1;
|
|
7956
|
+
const scY = t.scaleY || 1;
|
|
7957
|
+
const tx = t.translateX || 0;
|
|
7958
|
+
const ty = t.translateY || 0;
|
|
7959
|
+
const renderedW = intrW * scX;
|
|
7960
|
+
const renderedH = intrH * scY;
|
|
7961
|
+
const right = tx + renderedW;
|
|
7962
|
+
const bottom = ty + renderedH;
|
|
7963
|
+
const offPage = tx < 0 || ty < 0 || right > slideWidth || bottom > slideHeight ? " *** OFF PAGE ***" : "";
|
|
7964
|
+
lines.push(` ${el.objectId} (${el.shape ? "shape:" + el.shape.shapeType : el.image ? "image" : "other"})`);
|
|
7965
|
+
lines.push(` intrinsic: ${intrW} x ${intrH}, scale: ${scX} x ${scY}`);
|
|
7966
|
+
lines.push(` rendered: ${Math.round(renderedW)} x ${Math.round(renderedH)} at (${tx}, ${ty})`);
|
|
7967
|
+
lines.push(` bounds: right=${Math.round(right)}, bottom=${Math.round(bottom)}${offPage}`);
|
|
7968
|
+
}
|
|
7969
|
+
}
|
|
7970
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: false };
|
|
7971
|
+
}
|
|
7972
|
+
case "moveSlideElement": {
|
|
7973
|
+
const validation = MoveSlideElementSchema.safeParse(args);
|
|
7974
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
7975
|
+
const a = validation.data;
|
|
7976
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
7977
|
+
const pres = await slidesService.presentations.get({
|
|
7978
|
+
presentationId: a.presentationId,
|
|
7979
|
+
fields: "slides(pageElements(objectId,transform,size))"
|
|
7980
|
+
});
|
|
7981
|
+
let currentTransform = null;
|
|
7982
|
+
let currentSize = null;
|
|
7983
|
+
for (const slide of pres.data.slides || []) {
|
|
7984
|
+
for (const el of slide.pageElements || []) {
|
|
7985
|
+
if (el.objectId === a.objectId) {
|
|
7986
|
+
currentTransform = el.transform || null;
|
|
7987
|
+
currentSize = el.size || null;
|
|
7988
|
+
break;
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
if (currentTransform) break;
|
|
7992
|
+
}
|
|
7993
|
+
if (!currentTransform) {
|
|
7994
|
+
return errorResponse(`Element ${a.objectId} not found in presentation`);
|
|
7995
|
+
}
|
|
7996
|
+
const origWidth = currentSize?.width?.magnitude || 3e6;
|
|
7997
|
+
const origHeight = currentSize?.height?.magnitude || 3e6;
|
|
7998
|
+
const curScaleX = currentTransform.scaleX || 1;
|
|
7999
|
+
const curScaleY = currentTransform.scaleY || 1;
|
|
8000
|
+
const newScaleX = a.width !== void 0 ? a.width / origWidth : curScaleX;
|
|
8001
|
+
const newScaleY = a.height !== void 0 ? a.height / origHeight : curScaleY;
|
|
8002
|
+
const newX = a.x ?? (currentTransform.translateX || 0);
|
|
8003
|
+
const newY = a.y ?? (currentTransform.translateY || 0);
|
|
8004
|
+
const newTransform = {
|
|
8005
|
+
scaleX: newScaleX,
|
|
8006
|
+
scaleY: newScaleY,
|
|
8007
|
+
translateX: newX,
|
|
8008
|
+
translateY: newY,
|
|
8009
|
+
shearX: currentTransform.shearX || 0,
|
|
8010
|
+
shearY: currentTransform.shearY || 0,
|
|
8011
|
+
unit: "EMU"
|
|
8012
|
+
};
|
|
8013
|
+
const requests = [{
|
|
8014
|
+
updatePageElementTransform: {
|
|
8015
|
+
objectId: a.objectId,
|
|
8016
|
+
applyMode: "ABSOLUTE",
|
|
8017
|
+
transform: newTransform
|
|
8018
|
+
}
|
|
8019
|
+
}];
|
|
8020
|
+
await slidesService.presentations.batchUpdate({
|
|
8021
|
+
presentationId: a.presentationId,
|
|
8022
|
+
requestBody: { requests }
|
|
8023
|
+
});
|
|
8024
|
+
const didResize = a.width !== void 0 || a.height !== void 0;
|
|
8025
|
+
const action = didResize ? "Moved/resized" : "Moved";
|
|
8026
|
+
return {
|
|
8027
|
+
content: [{ type: "text", text: `${action} element ${a.objectId} to (${newX}, ${newY})` }],
|
|
8028
|
+
isError: false
|
|
8029
|
+
};
|
|
8030
|
+
}
|
|
8031
|
+
case "deleteSlideElement": {
|
|
8032
|
+
const validation = DeleteSlideElementSchema.safeParse(args);
|
|
8033
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8034
|
+
const a = validation.data;
|
|
8035
|
+
const slidesService = ctx.google.slides({ version: "v1", auth: ctx.authClient });
|
|
8036
|
+
await slidesService.presentations.batchUpdate({
|
|
8037
|
+
presentationId: a.presentationId,
|
|
8038
|
+
requestBody: {
|
|
8039
|
+
requests: [{ deleteObject: { objectId: a.objectId } }]
|
|
8040
|
+
}
|
|
8041
|
+
});
|
|
8042
|
+
return {
|
|
8043
|
+
content: [{ type: "text", text: `Deleted element ${a.objectId}` }],
|
|
8044
|
+
isError: false
|
|
8045
|
+
};
|
|
8046
|
+
}
|
|
8047
|
+
case "insertSlidesImageFromUrl": {
|
|
8048
|
+
const validation = InsertSlidesImageFromUrlSchema.safeParse(args);
|
|
8049
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8050
|
+
const a = validation.data;
|
|
8051
|
+
return insertImageIntoSlide(ctx, a.presentationId, a.pageObjectId, a.imageUrl, a.x, a.y, a.width, a.height);
|
|
8052
|
+
}
|
|
8053
|
+
case "insertSlidesLocalImage": {
|
|
8054
|
+
const validation = InsertSlidesLocalImageSchema.safeParse(args);
|
|
8055
|
+
if (!validation.success) return errorResponse(validation.error.errors[0].message);
|
|
8056
|
+
const a = validation.data;
|
|
8057
|
+
const { fileId, webContentLink } = await uploadImageToDrive(ctx, a.localImagePath, {
|
|
8058
|
+
makePublic: true
|
|
8059
|
+
});
|
|
8060
|
+
try {
|
|
8061
|
+
const result = await insertImageIntoSlide(
|
|
8062
|
+
ctx,
|
|
8063
|
+
a.presentationId,
|
|
8064
|
+
a.pageObjectId,
|
|
8065
|
+
webContentLink,
|
|
8066
|
+
a.x,
|
|
8067
|
+
a.y,
|
|
8068
|
+
a.width,
|
|
8069
|
+
a.height
|
|
8070
|
+
);
|
|
8071
|
+
await deleteDriveFile(ctx, fileId).catch(
|
|
8072
|
+
(err) => ctx.log(`insertSlidesLocalImage: failed to delete intermediary Drive file ${fileId}`, err)
|
|
8073
|
+
);
|
|
8074
|
+
return result;
|
|
8075
|
+
} catch (err) {
|
|
8076
|
+
await deleteDriveFile(ctx, fileId).catch(() => {
|
|
8077
|
+
});
|
|
8078
|
+
throw err;
|
|
8079
|
+
}
|
|
8080
|
+
}
|
|
7782
8081
|
default:
|
|
7783
8082
|
return null;
|
|
7784
8083
|
}
|