Related
Im using Google Apps Scripts to modify an existing Waterfall graph inside of a sheet. I'm able to change the color of the series dynamically, but when i try to set the colors for "Positive" and "Negative" options im unable to get the right options to get my desired result.
I believe that i'm just not expanding the JSON correctly and I cannot find the documentation of how each option in the visual/GUI builder is transcribed into text.
exampleChart = exampleChart.modify()
.setOption('series.0.color', 'red')
.setOption('series.0.positive.color', 'black')
.build();
sheet.updateChart(exampleChart);
Resulting change in chart options of the code snippet
In my experience, when I searched for the parameter for changing the positive and negative colors of the Waterfall chart, unfortunately, I couldn't find it. So, at that case, I used Sheets API. When Sheets API is used for changing the colors of the Waterfall chart, the sample script is as follows.
Sample script:
Before you use this script, please enable Sheets API at Advanced Google services.
In this sample script, the 1st chart in "Sheet1" is used. So, please modify this for your actual situation. Each color is from your showing script.
function myFunction() {
var sheetName = "Sheet1"; // Please set your sheet name.
var positiveColor = { red: 1, green: 0, blue: 0 }; // red
var negativeColor = { red: 0, green: 0, blue: 0 }; // black
var subtotalColor = { red: 0.5, green: 0.5, blue: 0.5 }; // gray
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var sheet = ss.getSheetByName(sheetName);
var chart = sheet.getCharts()[0]; // In this sample, the 1st chart in the sheet is used.
var chartId = chart.getChartId();
var chartObj = Sheets.Spreadsheets.get(ssId, { ranges: [sheetName] }).sheets[0].charts.find(e => e.chartId == chartId);
delete chartObj.position;
var obj = [{ k: "positiveColumnsStyle", v: positiveColor }, { k: "negativeColumnsStyle", v: negativeColor }, { k: "subtotalColumnsStyle", v: subtotalColor }];
obj.forEach(({ k, v }) => {
if (k && v) {
var t = chartObj.spec.waterfallChart.series[0];
if (!t[k]) {
t[k] = { colorStyle: { rgbColor: v } };
} else {
t[k].colorStyle = { rgbColor: v };
}
}
});
var requests = [{ updateChartSpec: { spec: chartObj.spec, chartId } }];
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
}
In this case, subtotalColumnsStyle is also set. If you don't want to use this, please remove it from the script.
Testing:
When this script is run, the following sample situation is obtained.
Before:
After:
References:
Method: spreadsheets.batchUpdate
UpdateChartSpecRequest
ColorStyle
Removing empty last page space, tab from Google document using Google Apps Script
hello, I am new to Script
I often have documents containing several pages with spaces and tabs after copy / paste
I'm trying to erase everything behind the last character entered to print without having to select the number of pages
I only need the first page to print
Can you give me the lead?
The tanaikech script does not work for my use ... Too bad
https://tanaikech.github.io/2020/01/17/deleting-last-empty-page-of-google-document-using-google-apps-script/
Tanaike's script removes only one last paragraph and it removes it without of checking if the paragraph is empty.
If you want to remove all empty paragraphs at the end of a doc you can do it this way (beware, it makes call to API for every empty paragraph, see my updated version):
function main() {
var doc = DocumentApp.getActiveDocument();
var id = doc.getId();
var text = doc.getBody().getText();
var pgfs = text.split('\n');
while(pgfs.pop().replace(/\s+/,"") == "") remove_last_pgf(id);
}
// original Tanaike's script goes here
function remove_last_pgf(docId) {
var c = Docs.Documents.get(docId, {
fields: "body.content"
}).body.content.pop();
Docs.Documents.batchUpdate(
{
requests: [
{
deleteContentRange: {
range: { startIndex: c.startIndex - 1, endIndex: c.endIndex - 1 }
}
}
]
},
docId
);
}
Don't forget to add Google Docs API in Services of Script Editor:
Update
I've refined the script a little bit further:
function main() {
var doc = DocumentApp.getActiveDocument();
var docId = doc.getId();
var empty_tail = doc.getBody().getText().search(/\s+$/) + 1;
if (empty_tail == 0) return; // prevent the error for empty docs
var content = Docs.Documents.get(docId,{fields: "body.content"}).body.content.pop();
var range = { startIndex: empty_tail, endIndex: content.endIndex-1 };
var req = { deleteContentRange: { range } };
Docs.Documents.batchUpdate( {requests: [req] }, docId );
}
Now it should work faster since it doesn't call API for every empty line. It gets the position where the empty characters \s+ start and removes them all with one call to API.
I Obtain this error message when there is nothing to erase.
how to pass through this problem ?
function main() {
var doc = DocumentApp.getActiveDocument();
var docId = doc.getId();
var empty_tail = doc.getBody().getText().search(/\s+$/) + 1;
var content = Docs.Documents.get(docId,{fields:
"body.content"}).body.content.pop();
if (empty_tail == 0) return;
var range = { startIndex: empty_tail, endIndex: content.endIndex-1 };
var req = { deleteContentRange: { range } };
Docs.Documents.batchUpdate( {requests: [req] }, docId );
}
Good day. Please tell me how I can convert this script to use Google sheets api v4
and reduce the cost of the request. Understand correctly that I need to dig to the side:
https://developers.google.com/sheets/api/samples/conditional-formatting?hl=en#add_a_conditional_formatting_rule_to_a_set_of_ranges
?
Sample code below
while (folders.hasNext()) {
var folder = folders.next().getId();
var sheet1 = SpreadsheetApp.openById(folder);
var sheet = sheet1.getActiveSheet();
var r1 = sheet.getRange('Q4:Q');var r2 = sheet.getRange('S4:S');
var rule = SpreadsheetApp.newConditionalFormatRule()
.setGradientMaxpoint("#06ff00")
.setGradientMidpointWithValue("#ffef00", SpreadsheetApp.InterpolationType.PERCENTILE, "50")
.setGradientMinpoint("#ff0000")
.setRanges([r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,
r11,r12,r13,r14,r15,r16,r17,r18,r19,r20,
r21,r22,r23,r24,r25,r26,r27,r28,r29,r30,
r31,r32,r33,r34,r35,r36,r37,r38,r39,r40,
r41,r42,r43,r44,r45,r46,r47,r48,r49,r50,
r51,r52,r53,r54,r55,r56,r57,r58,r59,r60,
r61,r62,r63,r64,r65])
.build()
var rules = sheet.getConditionalFormatRules();
rules.push(rule);
sheet.setConditionalFormatRules(rules);
}
I will be grateful for any help
Answer
I understand that you want to use Sheet API v4 instead of Spreadsheet Service to reduce the cost of the request. I don't know how much the cost will be reduced using that way, but I will explain to you how to do it.
How to apply a Conditional Format Rule in Sheets API v4
Use the method batchUpdate. It takes a request body where you can define the Conditional Format Rule and the spreadsheetId. You can easily construct the request body using the section Try this API, it helps you to put and define all the parameters that you need.
Define the request body with a AddConditionalFormatRuleRequest object. It has two fields, the rule that describes the conditional format and the index that defines where the rule should be inserted.
Define the rule field with a ConditionalFormatRule object. It takes two fields, the ranges and the gradientRule or the boolearnRule (you can only choose one).
Define the range with a GridRange object.
Define the gradientRule with its three fields: minpoint, midpoint and maxpoint. Each of these is defined by an InterpolationPoint object.
Finally your code will look similar to the following:
function main(){
// start here
var folders = // your definition
const gridRangeList = createGridRange() // create the GridRange object
while (folders.hasNext()) {
var spreadsheetId = folders.next().getId();
applyConditionalFormating(spreadsheetId, gridRangeList) // apply the conditional format
}
}
function createGridRange(){
const ranges = ["Q4:Q", "S4:S"]
const temp = SpreadsheetApp.create("temp")
const rangeList = temp.getSheets()[0].getRangeList(ranges).getRanges()
const gridRangeList = rangeList.map(r => ({startRowIndex: r.getRow() - 1, startColumnIndex: r.getColumn() - 1, endColumnIndex: r.getColumn() + r.getNumColumns() - 1}))
DriveApp.getFileById(temp.getId()).setTrashed(true) // move the file to the trash
return gridRangeList
}
function applyConditionalFormating(spreadsheetId, gridRangeList){
const request = {
"requests": [
{
"addConditionalFormatRule": {
"rule": {
"gradientRule": {
"maxpoint": {
"type": "MAX",
"color": {red:6/255,green:255/255,blue:0}
},
"midpoint": {
"type": "PERCENTILE",
"value": "50",
"color": {red:255/255,green:239/255,blue:0}
},
"minpoint": {
"type": "MIN",
"color":{red:255/255,green:0,blue:0}
}
},
"ranges": [gridRangeList]
},
"index": 0
}
}
]
}
Sheets.Spreadsheets.batchUpdate(request,spreadsheetId)
}
Reference
Sheet API v4
Spreadsheet Service
Conditional Format Rule
Method: spreadsheets.batchUpdate
AddConditionalFormatRuleRequest
ConditionalFormatRule
GridRange
gradientRule
InterpolationPoint
I believe your goal as follows.
You want to reduce the process cost of your script.
Modification points:
When I saw your script, it seems that a conditional format rule with multiple ranges is added to a sheet in a Google Spreadsheet by one call. In this case, even when this script is converted to Sheets API instead of Spreadsheet service, the process cost might not be the large change. So please test the following modified script.
As the modification point, I would like to propose as follows.
In your script, the ranges are declared in the loop. When this is converted to Sheets API, the ranges can be created at outside of the loop.
When the file list is retrieved using Drive API, the process cost will be reduced a little.
When above points are reflected to your script, it becomes as follows.
Modified script:
Before you use this script, please enable Sheets API and Drive API at Advanced Google services. And, please set the variables of topFolderId and ranges. ranges is from your script. When you want to more ranges, please add them to the array.
function myFunction() {
var topFolderId = "###"; // Please set the top folder ID of the folder including the Spreadsheet.
// Retrieve file list using Drive API v3.
const headers = {authorization: `Bearer ${ScriptApp.getOAuthToken()}`};
const q = `'${topFolderId}' in parents and mimeType='${MimeType.GOOGLE_SHEETS}' and trashed=false`;
const url = `https://www.googleapis.com/drive/v3/files?pageSize=1000&q=${q}&fields=${encodeURIComponent("nextPageToken,files(id)")}`;
let pageToken = "";
let files = [];
do {
const res = UrlFetchApp.fetch(url + "&pageToken=" + pageToken, {headers: headers, muteHttpExceptions: true});
if (res.getResponseCode() != 200) throw new Error(res.getContentText());
const obj = JSON.parse(res.getContentText());
files = files.concat(obj.files);
pageToken = obj.nextPageToken || "";
} while(pageToken);
// Create range list.
const ranges = ["Q4:Q", "S4:S"]; // Please set the ranges as A1Notation. These ranges are used for addConditionalFormatRule.
const temp = SpreadsheetApp.create("temp");
const rangeList = temp.getSheets()[0].getRangeList(ranges).getRanges();
const gridRangeList = rangeList.map(r => ({startRowIndex: r.getRow() - 1, startColumnIndex: r.getColumn() - 1, endColumnIndex: r.getColumn() + r.getNumColumns() - 1}));
DriveApp.getFileById(temp.getId()).setTrashed(true);
// Request Sheets API for a sheet in each Spreadsheet.
files.forEach(({id}) => {
const sheet1 = SpreadsheetApp.openById(id);
const gr = gridRangeList.map(({startRowIndex, startColumnIndex, endColumnIndex}) => ({sheetId: sheet1.getSheetId(), startRowIndex, startColumnIndex, endColumnIndex}))
const requests = {addConditionalFormatRule:{rule:{gradientRule:{maxpoint:{color:{red:6/255,green:255/255,blue:0},type:"MAX"},midpoint:{color:{red:255/255,green:239/255,blue:0},type:"PERCENTILE",value:"50"},minpoint:{color:{red:255/255,green:0,blue:0},type:"MIN"}},ranges:[gr]},index:0}};
Sheets.Spreadsheets.batchUpdate({requests: requests}, id);
});
}
References:
Method: spreadsheets.batchUpdate
AddConditionalFormatRuleRequest
I really tried to figure this out on my own...
I am trying to load photo metadata from google photos into a sheet using the Google Photos API and google apps script.
I was able to make some progress after a lot of help on a previous question
Is it possible to load google photos metadata into google sheets?
I now have two functions.
function photoAPI_ListPhotos() - Uses Method: mediaItems.list and gives me all my photos that are not archived
function photoAPI_ListAlbums() - Uses Method: albums.list and gives me all my albums
What I want to do is retrieve all photos from a specific album. Method: mediaItems.search should do this but it uses the POST protocol and the previous working examples I found only use GET. Looking at the examples available on that page, there is a javascript portion but it does not work in apps script.
The documentation for UrlFetchApp tells me how to format a POST request but not how to add the parameters for authentication.
The external APIs also is not giving me the examples I am looking for.
I feel like I'm missing some essential tiny piece of info and I hope I'm not wasting everyone's time asking it here. Just a solid example of how to use POST with oauth in apps script should get me where I need to go.
Here is my working function for listing all non-archived photos.
function photoAPI_ListPhotos() {
/*
This function retrieves all photos from your personal google photos account and lists each one with the Filename, Caption, Create time (formatted for Sheet), Width, Height, and URL in a new sheet.
it will not include archived photos which can be confusing if you happen to have a large chunk of archived photos some pages may return only a next page token with no media items.
Requires Oauth scopes. Add the below line to appsscript.json
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/photoslibrary", "https://www.googleapis.com/auth/photoslibrary.readonly", "https://www.googleapis.com/auth/script.external_request"]
Also requires a standard GCP project with the appropriate Photo APIs enabled.
https://developers.google.com/apps-script/guides/cloud-platform-projects
*/
//Get the spreadsheet object
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Check for presence of target sheet, if it does not exist, create one.
var photos_sh = ss.getSheetByName("photos") || ss.insertSheet("photos", ss.getSheets().length);
//Make sure the target sheet is empty
photos_sh.clear();
var narray = [];
//Build the request string. Max page size is 100. set to max for speed.
var api = "https://photoslibrary.googleapis.com/v1/mediaItems?pageSize=100";
var headers = { "Authorization": "Bearer " + ScriptApp.getOAuthToken() };
var options = { "headers": headers, "method" : "GET", "muteHttpExceptions": true };
//This variable is used if you want to resume the scrape at some page other than the start. This is needed if you have more than 40,000 photos.
//Uncomment the line below and add the next page token for where you want to start in the quotes.
//var nexttoken="";
var param= "", nexttoken;
//Start counting how many pages have been processed.
var pagecount=0;
//Make the first row a title row
var data = [
"Filename",
"description",
"Create Time",
"Width",
"Height",
"ID",
"URL",
"NextPage"
];
narray.push(data);
//Loop through JSON results until a nextPageToken is not returned indicating end of data
do {
//If there is a nextpagetoken, add it to the end of the request string
if (nexttoken)
param = "&pageToken=" + nexttoken;
//Get data and load it into a JSON object
var response = UrlFetchApp.fetch(api + param, options);
var json = JSON.parse(response.getContentText());
//Check if there are mediaItems to process.
if (typeof json.mediaItems === 'undefined') {
//If there are no mediaItems, Add a blank line in the sheet with the returned nextpagetoken
//var data = ["","","","","","","",json.nextPageToken];
//narray.push(data);
} else {
//Loop through the JSON object adding desired data to the spreadsheet.
json.mediaItems.forEach(function (MediaItem) {
//Check if the mediaitem has a description (caption) and make that cell blank if it is not present.
if(typeof MediaItem.description === 'undefined') {
var description = "";
} else {
var description = MediaItem.description;
}
//Format the create date as appropriate for spreadsheets.
var d = new Date(MediaItem.mediaMetadata.creationTime);
var data = [
MediaItem.filename,
"'"+description, //The prepended apostrophe makes captions that are dates or numbers save in the sheet as a string.
d,
MediaItem.mediaMetadata.width,
MediaItem.mediaMetadata.height,
MediaItem.id,
MediaItem.productUrl,
json.nextPageToken
];
narray.push(data);
});
}
//Get the nextPageToken
nexttoken = json.nextPageToken;
pagecount++;
//Continue if the nextPageToaken is not null
//Also stop if you reach 400 pages processed, this prevents the script from timing out. You will need to resume manually using the nexttoken variable above.
} while (pagecount<4 && nexttoken);
//Continue if the nextPageToaken is not null (This is commented out as an alternative and can be used if you have a small enough collection it will not time out.)
//} while (nexttoken);
//Save all the data to the spreadsheet.
photos_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}
You want to retrieve all photos of the specific album using Google Photo API.
You want to know how to use the method of mediaItems.search using Google Apps Script.
You have already been able to retrieve the data using Google Photo API.
If my understanding is correct, how about this sample script? Please think of this as just one of several answers.
Sample script 1:
var albumId = "###"; // Please set the album ID.
var headers = {"Authorization": "Bearer " + ScriptApp.getOAuthToken()};
var url = "https://photoslibrary.googleapis.com/v1/mediaItems:search";
var mediaItems = [];
var pageToken = "";
do {
var params = {
method: "post",
headers: headers,
contentType: "application/json",
payload: JSON.stringify({albumId: albumId, pageSize: 100, pageToken: pageToken}),
}
var res = UrlFetchApp.fetch(url, params);
var obj = JSON.parse(res.getContentText());
Array.prototype.push.apply(mediaItems, obj.mediaItems);
pageToken = obj.nextPageToken || "";
} while (pageToken);
Logger.log(mediaItems)
At the method of mediaItems.search, albumId, pageSize and pageToken are included in the payload, and the values are sent as the content type of application/json.
Sample script 2:
When your script is modified, how about the following modified script?
function photoAPI_ListPhotos() {
var albumId = "###"; // Please set the album ID.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var photos_sh = ss.getSheetByName("photos") || ss.insertSheet("photos", ss.getSheets().length);
photos_sh.clear();
var narray = [];
var api = "https://photoslibrary.googleapis.com/v1/mediaItems:search";
var headers = { "Authorization": "Bearer " + ScriptApp.getOAuthToken() };
var nexttoken = "";
var pagecount = 0;
var data = ["Filename","description","Create Time","Width","Height","ID","URL","NextPage"];
narray.push(data);
do {
var options = {
method: "post",
headers: headers,
contentType: "application/json",
payload: JSON.stringify({albumId: albumId, pageSize: 100, pageToken: nexttoken}),
}
var response = UrlFetchApp.fetch(api, options);
var json = JSON.parse(response.getContentText());
if (typeof json.mediaItems === 'undefined') {
//If there are no mediaItems, Add a blank line in the sheet with the returned nextpagetoken
//var data = ["","","","","","","",json.nextPageToken];
//narray.push(data);
} else {
json.mediaItems.forEach(function (MediaItem) {
if(typeof MediaItem.description === 'undefined') {
var description = "";
} else {
var description = MediaItem.description;
}
var d = new Date(MediaItem.mediaMetadata.creationTime);
var data = [
MediaItem.filename,
"'"+description,
d,
MediaItem.mediaMetadata.width,
MediaItem.mediaMetadata.height,
MediaItem.id,
MediaItem.productUrl,
json.nextPageToken
];
narray.push(data);
});
}
nexttoken = json.nextPageToken || "";
pagecount++;
} while (pagecount<4 && nexttoken);
photos_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}
Note:
This script supposes as follows.
Google Photo API is enabed.
The scope of https://www.googleapis.com/auth/photoslibrary.readonly or https://www.googleapis.com/auth/photoslibrary are included in the scopes.
Reference:
Method: mediaItems.search
If I misunderstood your question and this was not the result you want, I apologize.
As the title suggests, I'm looking for a way to set the alt title of an image in a slideshow.
Currently this is what i have tried, but for some reason it doesn't seem to update:
var resource = {"requests": [
{"updatePageElementAltText": {
"objectId": id,
"description": "",
"title": elementTitle
}
}]};
Slides.Presentations.batchUpdate(resource, presentationId);
It might be worth noting that the script is running in the Script Editor of a google sheet. The variables id, elementTitle and presentationId are all defined earlier in the script and I've checked that they are correct.
Can anyone spot the issue with this or suggest an easier way to do it?
Edit: Tanaike helped me make this specific part of the script work, but it isn't working in the larger picture, hence this edit.
What the script is supposed to do, is basically do a find/replace on all Image elements in the slideshow.
Based on keys in a sheet in Column A it should replace the Image URL in with the corresponding URL in column B. The script then cycles through all elements in the slideshow, finds the images, and then cycles through them to check if any of the titles have the 'key' as the title. The image URL should then be replaced with the URL on the same row in sheet. This part of the script is tested and works, but the key is removed from the object when the URL is updated. This shouldn't be happening as the Image should be able to be replaced again later.
For this reason, I tried to save the title before updating the URL and the put it back with the above-mentioned batchUpdate, but for some reason, it isn't working properly.
Here is the full script:
function imageReplacer() {
var newPresentationSlides = SlidesApp.openByUrl(myslidesurl).getSlides();
var imageTitles = SpreadsheetApp.openByUrl(mysheeturl).getRange("'Image Replace List'!A2:A").getValues();
var imageURLs = SpreadsheetApp.openByUrl(mysheeturl).getRange("'Image Replace List'!B2:B").getValues();
var presentationId = 'myslidesid';
for (y = 0; y < newPresentationSlides.length; y++) {
var pageElements = newPresentationSlides[y].getPageElements();
for (x = 0; x < pageElements.length; x++) {
for (a = 0; a < imageTitles.filter(String).length; a++) {
if (pageElements[x].getPageElementType() == "IMAGE") {
if(pageElements[x].asImage().getTitle() == imageTitles[a]) {
var elementTitle = pageElements[x].asImage().getTitle();
var id = pageElements[x].getObjectId();
pageElements[x].asImage().replace(imageURLs[a]);
var id = pageElements[x].getObjectId();
var resource = {"requests": [
{"updatePageElementAltText": {
"objectId": id,
"description": "Sample description",
"title": elementTitle
}
}]};
Slides.Presentations.batchUpdate(resource, presentationId);
}
}
}
}
}
}
As you can see the middle part of the script is exactly the same as tanaike suggested, but it's just not working properly (I even tested that specific part as a stand-alone script and it worked fine.).
Second edit:
Examples:
Sheet: https://docs.google.com/spreadsheets/d/1npWyONio_seI3bRibFWxiqzHxLZ-ie2wbszgROkLduE/edit#gid=0
Slides: https://docs.google.com/presentation/d/1rfT7TLD-O7dBbwV5V3UbugN1OLOnBI2-CZN2GPnmANM/edit#slide=id.p
I think that your script works. You can confirm the updated result on the slide.
But if you want to retrieve the title and description using Slides services like getTitle() and getDescription() after the title and description are updated using Slides API, it seems that those results are not updated. The updated results couldn't be retrieved even if saveAndClose() is used. And also, unfortunately, in the current stage, I couldn't find the methods like setTitle() and setDescription() in my environment. So how about this workaround? In this workaround, the title and description are updated by Slides API and those are retrieved by Slides API.
Sample script:
var presentationId = "###"; // Please set this.
var objectId = "###"; // Please set this.
// Update title and description
var resource = {"requests": [
{"updatePageElementAltText": {
"objectId": objectId,
"description": "Sample description",
"title": "Sample title"
}
}]};
Slides.Presentations.batchUpdate(resource, presentationId);
// Retrieve updated title and description
var res = Slides.Presentations.get(presentationId);
var slides = res.slides;
for (var i = 0; i < slides.length; i++) {
var pe = slides[i].pageElements;
for (var j = 0; j < pe.length; j++) {
if (pe[j].objectId == objectId) {
Logger.log(pe[j].title)
Logger.log(pe[j].description)
break;
}
}
}
Note:
If you use this script, please enable Slides API at Advanced Google Services and API console.
References:
presentations.batchUpdate
presentations.get
If I misunderstand what you want, I'm sorry.
Edit:
You want to replace all images in Slides.
At this time, you want to search the title of each image and replace the image from URL using the title.
When the images are replaced, you don't want to change the title (key) of each image.
If my understanding is correct, how about this modification?
Modification points:
It seems that when the image is replaced, the title of image is cleared.
In order to avoid this, when the image is replaced, it also puts the title. For this situation, batchUpdate of Slides API is used.
From the viewpoint of the process cost, at first, it creates the request body and requests the request body. By this, this situation can be achieved by only one API call.
Modified script:
function imageReplacer() {
var spreadsheetId = "### spreadsheetId ###"; // Please modify this.
var sheetName = "Image Replace List";
var presentationId = "### presentationId ###"; // Please modify this.
var sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
var values = sheet.getRange(2, 1, sheet.getLastRow(), 2).getValues().filter(function(e) {return e[0] && e[1]});
var s = SlidesApp.openById(presentationId);
var slides = s.getSlides();
var requests = slides.reduce(function(reqs, slide) {
var r = slide.getPageElements().reduce(function(ar, e) {
if (e.getPageElementType() == "IMAGE") {
var key = values.filter(function(v) {return v[0] == e.getTitle()});
if (key.length == 1) {
var id = e.getObjectId();
var rq = [
{"replaceImage":{"imageObjectId":id, "url": key[0][1]}},
{"updatePageElementAltText":{"objectId":id, "title": key[0][0]}}
];
Array.prototype.push.apply(ar, rq);
}
}
return ar;
}, []);
if (r.length > 0) Array.prototype.push.apply(reqs, r);
return reqs;
}, []);
Slides.Presentations.batchUpdate({requests: requests}, presentationId);
}
Note:
I'm not sure about the maximum number of requests for one API call. So if you want to replace a lot of images, if the error due to this occurs, please modify above script.