I am attempting to retrieve trades from a service called 3Commas in Google Apps Script. I've worked with public endpoints before, but this is the first time I've attempted to work with signed endpoints. I'm currently receiving an error that states:
[19-01-09 16:46:24:592 EST] {"error":"signature_invalid","error_description":"Provided signature is invalid"}
I'm guessing this is a formatting issue on my part. I'm using jsSHA to build the HMAC part. I've tried following the example in the docs. But I haven't quite got it yet. Any suggestions on what it could be?
3Commas Docs: https://github.com/3commas-io/3commas-official-api-docs#signed--endpoint-security
function main() {
var key = 'apikey';
var secret = 'apisecret';
var baseUrl = "https://3commas.io/public/api";
var endPoint = "/ver1/smart_trades";
var pointParams = "?limit=10&offset=&account_id=&scope=&type="
//base url + end point + params
var queryString = baseUrl+endPoint+pointParams;
var message = queryString;
var secret = secret;
var shaObj = new jsSHA("SHA-256", "TEXT");
shaObj.setHMACKey(secret, "B64");
shaObj.update(message);
var signature = shaObj.getHMAC("B64");
//headers
var hparams = {
'method': 'get',
'headers': {'APIKEY': key,
'Signature': signature},
'muteHttpExceptions': true
};
//call
var data = UrlFetchApp.fetch(queryString , hparams).getContentText();
Logger.log(data)
}
How about this modification? From 3Commas Docs in your question, I propose the modification points as follows.
Modification points:
It seems that the value which is required to encrypt is after https://3commas.io.
You can encrypt the values using the method of computeHmacSha256Signature() in Class Utilities of GAS. In this case, jsSHA is not required to be used.
But when computeHmacSha256Signature() is used, the value becomes the bytes array of the signed hexadecimal. So it is required to convert it to the unsigned hexadecimal.
Modified script:
function main() {
var key = 'apikey';
var secret = 'apisecret';
var baseUrl = "https://3commas.io"; // Modified
var endPoint = "/public/api/ver1/smart_trades"; // Modified
var pointParams = "?limit=10&offset=&account_id=&scope=&type="; // or "?limit=10"
var queryString = endPoint + pointParams; // Modified
var signature = Utilities.computeHmacSha256Signature(queryString, secret); // Added
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join(""); // Added
//headers
var hparams = {
'method': 'get',
'headers': {'APIKEY': key,
'Signature': signature},
'muteHttpExceptions': true
};
//call
var data = UrlFetchApp.fetch(baseUrl + queryString , hparams).getContentText(); // Modified
Logger.log(data)
}
Note:
About var pointParams = "?limit=10&offset=&account_id=&scope=&type=", in the case of the endpoint you use, limit, offset, account_id, scope and type are no mandatory. So it might be var pointParams = "?limit=10". If the error occurs, please try it.
References:
computeHmacSha256Signature(value, key)
Public Rest API for 3commas.io (2018-10-26)
This document is more detail.
I cannot confirm whether this modified script works. I'm sorry for this situation. So if it didn't work, I apologize. At that time, can you provide the detail information of the situation?
Related
The user clicks on a button, which has main() assigned to it.
It runs with no errors, but the protections are not removed or applied as it seems to get stuck because of the error mentioned in the question.
Here are the functions borrowed and adapted from Stackoverflow:
// This is the main function. Please set this function to the run button on Spreadsheet.
function main() {
//DriveApp.getFiles(); // This is a dummy method for detecting a scope by the script editor.
const activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const sheetName = activeSheet.getName();
if (sheetName === 'Itens_para_Costurar') {
var token = ScriptApp.getOAuthToken()
var url = ScriptApp.getService().getUrl();
var options = {
'method': 'post',
'headers': { 'Authorization': 'Bearer ' + token },
muteHttpExceptions: true
};
UrlFetchApp.fetch(url + "&key=removeprotectListaCortadas", options); // Remove protected range
listaPecasCortadas();//Atualiza a lista de peças cortadas
SpreadsheetApp.flush(); // This is required to be here.
UrlFetchApp.fetch(url + "&key=addprotectListaCortadas", options); // Add protected range
}
}
function doGet(e) {
if (e.parameter.key == "removeprotectListaCortadas") {
var listaPecasCortadasSht = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Itens_para_Costurar'); // Please set here.
// Remove protected range.
var protections = listaPecasCortadasSht.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
console.log('Protection Name: ' + protections[i].getDescription())
protections[i].remove();
}
} else {
// Add protected range.
var ownersEmail = Session.getActiveUser().getEmail();
var protection = listaPecasCortadasSht.getRange('A8:J100').protect();
var editors = protection.getEditors();
for (var i = 0; i < editors.length; i++) {
var email = editors[i].getEmail();
if (email != ownersEmail) protection.removeEditor(email);
}
}
return ContentService.createTextOutput("ok");
}
I would appreciate any light towards the solution.
Modification points:
If you were using my answer, when I posted it, ScriptApp.getService().getUrl() returned the endpoint of the developer mode. But, in the current stage, it doesn't return the endpoint of the developer mode. And, in this case, the invalid endpoint is returned. Ref
In your request body, 'method': 'post', is used. But, in your script, doGet is used. I thought that this might be the reason for your current issue.
In your script, var url = ScriptApp.getService().getUrl(); is used. But, using url, you are using UrlFetchApp.fetch(url + "&key=addprotectListaCortadas", options);. In this case, the query parameter is not correct.
When these points are reflected in your script, how about the following modification?
From:
var token = ScriptApp.getOAuthToken()
var url = ScriptApp.getService().getUrl();
var options = {
'method': 'post',
'headers': { 'Authorization': 'Bearer ' + token },
muteHttpExceptions: true
};
UrlFetchApp.fetch(url + "&key=removeprotectListaCortadas", options); // Remove protected range
To:
var url = "###"; // Please manually set your Web Apps URL here.
var options = { muteHttpExceptions: true };
UrlFetchApp.fetch(url + "?key=removeprotectListaCortadas", options);
If your Web Apps is deployed as Execute the app as: Me and Who has access to the app: Anyone, when the Web Apps URL is used, the access token is not required to be used. If you want to use the developer mode, please use the access token.
Note:
This answer is for your current issue. So, I cannot check your all script. Please be careful about this.
When you modified the Google Apps Script of Web Apps, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful about this.
You can see the detail of this in my report "Redeploying Web Apps without Changing URL of Web Apps for new IDE (Author: me)".
I got a bit stuck.
Goal:
To have a chatbot that domain users can use to confirm shoppinglist that is kept in a google spreadsheets.
The sheet that holds the data is generated based on forms input and manual input.
I am looking for a chatbot because I wanted an interface that shows some basic in/output without sharing the whole spreadsheet.
What have I done:
I looked into service accounts and I am able to read data. I wanted a sheet object but I read that this is not possible. I get a JSON object. I will manage I think.
How do I write data to the sheet?
The code under here only reads it. What function sends the data to sheets?
/**
* Configures the spreadsheet service.
*/
function getSpreasheetService() {
return OAuth2.createService("spreadsheet")
// Set the endpoint URL.
.setTokenUrl("https://accounts.google.com/o/oauth2/token")
// Set the private key and issuer.
.setPrivateKey(SERVICE_ACCOUNT_PRIVATE_KEY)
.setIssuer(SERVICE_ACCOUNT_EMAIL)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope.
.setScope("https://www.googleapis.com/auth/spreadsheets");
}
function readSheet(){
var service = getSpreasheetService();
var ssId = 'sheetsid' ;
var range = 'Voorraad!A1:50';
var url = 'https://sheets.googleapis.com/v4/spreadsheets/' + ssId +'/values/' + range;
var response = UrlFetchApp.fetch(url, { headers: {Authorization: 'Bearer ' + service.getAccessToken() } }); //json data
var rep = JSON.parse(response.getContentText());
var values = rep.values;
console.log(response);
console.log(rep);
// Logger.log(values);
//now i can use these data to reply back to end user as message
}
Here we go I found a solution :)
/**
* Configures the spreadsheet service.
*/
function getSpreasheetService() {
return OAuth2.createService("spreadsheet")
// Set the endpoint URL.
.setTokenUrl("https://accounts.google.com/o/oauth2/token")
// Set the private key and issuer.
.setPrivateKey(SERVICE_ACCOUNT_PRIVATE_KEY)
.setIssuer(SERVICE_ACCOUNT_EMAIL)
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scope.
.setScope("https://www.googleapis.com/auth/spreadsheets");
}
function writeSheet(){
var service = getSpreasheetService();
var spreadsheetId = ' your spreadsheet id' ;
var range = 'Blad1!A3'; // range = '[sheetname]![starting cell]:[ending cell]';
var waardes = {values:[ ['A3 data' , 'B3 data', 'C3 data'] , [4, '', "C4"]] } ;
//no matter if it is one value or rows with multiple values, it must be a nested json object. {values: [['value']]}
// "" or '' work the same
// Method: spreadsheets.values.update
var url = "https://sheets.googleapis.com/v4/spreadsheets/" + spreadsheetId + "/values/" + range + "?valueInputOption=USER_ENTERED"; //valueInputOption is required
var params = {
method: "put",
headers: {Authorization: "Bearer " + service.getAccessToken()},
contentType: "application/json",
payload: JSON.stringify(waardes),
};
UrlFetchApp.fetch(url, params);
}
I used these pages to find my answers:
https://gist.github.com/tanaikech/d102c9600ba12a162c667287d2f20fe4
https://developers.google.com/google-ads/scripts/docs/features/third-party-apis
https://developers.google.com/sheets/api/reference/rest
i think the issue is with the signature, the request body is same as in the independentreserve api docs. i am using apps script to connect with api, i tried using python and it works fine, but i am new to javascript and google apps script.
this is my code. can someone help with this?
function myFunction() {
var key = 'api-key'
var secret = 'api-secret'
var url = 'https://api.independentreserve.com/Private/GetOpenOrders'
// initialize nonce to current unix time in milliseconds
nonce = (new Date()).getTime();
// Set custom User-Agent string
var headers = {"User-Agent": "Independent Reserve Javascript API Client"};
var nonce = nonce++;
console.info("hELLO")
var message = [url, 'apiKey=' + key, 'nonce=' + nonce].join(',') ;
//var signer = crypto.createHmac('sha256', Buffer(secret, 'utf8'));
var signer = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, secret);
var signature = signer
.map(function(byte) {
// Convert from 2's compliment
var v = (byte < 0) ? 256 + byte : byte;
// Convert byte to hexadecimal
return ("0" + v.toString(16)).slice(-2);
}).join(',');
var headers = {'Content-Type': 'application/json', 'muteHttpExceptions': true};
var options = {
"apiKey": key,
"nonce": nonce,
"signature": signature,
"primaryCurrencyCode": "Xbt",
"secondaryCurrencyCode": "Usd",
"pageIndex": 1,
"pageSize": 25
}
var r = UrlFetchApp.fetch(url, options)
console.info(r.getContentText())
}
I believe your goal is as follows.
In order to request to api.independentreserve.com, you want to convert the following sample script of python. Ref
import time
import requests
import hmac
import hashlib
import json
from collections import OrderedDict
url = 'https://api.independentreserve.com/Private/GetOpenOrders'
key = 'api_key'
secret = 'api_secret'
nonce = int(time.time())
# make sure that parameter order is correct as specified in method documentation
parameters = [
url,
'apiKey=' + key,
'nonce=' + str(nonce),
'primaryCurrencyCode=Xbt',
'secondaryCurrencyCode=Usd',
'pageIndex=1',
'pageSize=10'
]
message = ','.join(parameters)
signature = hmac.new(
secret.encode('utf-8'),
msg=message.encode('utf-8'),
digestmod=hashlib.sha256).hexdigest().upper()
# make sure this collection ordered in the same way as parameters
data = OrderedDict([
('apiKey', key),
('nonce', nonce),
('signature', str(signature)),
('primaryCurrencyCode', 'Xbt'),
('secondaryCurrencyCode', 'Usd'),
('pageIndex', 1),
('pageSize', 10)])
headers = {'Content-Type': 'application/json'}
r = requests.post(url, data=json.dumps(data, sort_keys=False), headers=headers)
print(r.content)
When I saw your script, I thought the following modification points:
Utilities.computeDigest will be Utilities.computeHmacSha256Signature.
(new Date()).getTime() cannot be directly used. In this case, I thought that it's (new Date()).getTime().toString().slice(0, 10).
muteHttpExceptions cannot be used in the request header.
'Content-Type': 'application/json' can be used as contentType: "application/json". And in this case, it is required to convert the JSON object to the string value.
When the above python script is converted to Google Apps Script by modifying these points, it becomes as follows.
Sample script:
function myFunction() {
var key = 'api_key';
var secret = 'api_secret';
var url = 'https://api.independentreserve.com/Private/GetOpenOrders';
var nonce = (new Date()).getTime().toString().slice(0, 10);
var parameters = [
url,
'apiKey=' + key,
'nonce=' + nonce.toString(),
'primaryCurrencyCode=Xbt',
'secondaryCurrencyCode=Usd',
'pageIndex=1',
'pageSize=10'
];
var message = parameters.join(",");
var signature = Utilities.computeHmacSha256Signature(message, secret, Utilities.Charset.UTF_8).map(b => ('0' + (b & "0xFF").toString(16)).slice(-2)).join('');
var payload = {
"apiKey": key,
"nonce": nonce,
"signature": signature,
"primaryCurrencyCode": "Xbt",
"secondaryCurrencyCode": "Usd",
"pageIndex": 1,
"pageSize": 25
};
var options = {
method: "post",
contentType: "application/json",
payload: JSON.stringify(payload),
muteHttpExceptions: true,
};
var r = UrlFetchApp.fetch(url, options);
console.log(r.getContentText());
}
Note:
I confirmed that this request of the above Google Apps Script is the same as the sample python script. So, when an error occurs, please confirm your key, secret, and the values of parameters, again.
References:
Airbridge API
computeHmacSha256Signature(value, key, charset)
fetch(url, params)
I'm going mad trying to send my first API call to Coinbase Pro using Google Apps Script. In node.js is pretty easy (https://docs.pro.coinbase.com/#signing-a-message) but doing the same with Google scripts is just returning again and again "Invalid Signature".
This is the code I'm using:
function GetMyAccounts () {
var globalvars_CB = {
'apikey' : 'f7d20a*******18c',
'secret' : '******pIIitRbWCv9N/mMWaR*****mGQMuI+m/vSbU1zuh5U6WFiFw==',
'passphrase' : 'ceacdsewfcsa',
'uri' : 'https://api.pro.coinbase.com'
}
var requestPath = '/accounts';
var timestamp = Math.floor(Date.now() / 1000);
var options = {
'method' : 'GET',
'muteHttpExceptions' : true,
'headers' : {
'Content-Type': 'application/json',
'CB-ACCESS-KEY' : globalvars_CB.apikey,
'CB-ACCESS-SIGN' : SignAPICall(globalvars_CB.secret, timestamp, 'GET', requestPath, ''),
'CB-ACCESS-TIMESTAMP' : timestamp,
'CB-ACCESS-PASSPHRASE' : globalvars_CB.passphrase,
}
}
var responseJson = UrlFetchApp.fetch(globalvars_CB.uri+requestPath, options);
Logger.log(responseJson);
}
function SignAPICall(secret, timestamp, method, requestPath, body) {
var what = (timestamp + method + requestPath + body);
var decodedsecret = Utilities.base64Decode(secret).toString();
var hmac = Utilities.computeHmacSha256Signature(what, decodedsecret);
hmac = Utilities.base64Encode(hmac);
return (hmac);
}
I really need help :-) - Thanks!
In your script, it supposes that your request headers except for the value of CB-ACCESS-SIGN and endpoint are correct. Please be careful this.
Modification point:
In the case of Utilities.base64Decode(secret).toString(), the array is converted to the string. I think that this might be the reason of your issue.
When above point is reflected, it becomes as follows.
Modified script:
In this case, the function SignAPICall is modified.
function SignAPICall(secret, timestamp, method, requestPath, body) {
var what = (timestamp + method + requestPath + body);
var decodedsecret = Utilities.base64Decode(secret); // Modified
var res = Utilities.computeHmacSha256Signature(Utilities.newBlob(what).getBytes(), decodedsecret); // Modified
hmac = Utilities.base64Encode(res);
return hmac;
}
In this case, value and key of computeHmacSha256Signature(value, key) are the byte array.
Note:
When I checked above modified script by comparing the sample scripts of the official document, I could confirm that the same result can be obtained.
Unfortunately, I cannot test the request to the API using above modified script while I can confirm that the same signature from the sample script at the official document is retrieved from the above modified script. So please test the request in you environment. When you requested to the API using above modified script, when an error occurs, please check the request headers, endpoint and secret, again.
References:
Signing a Message
base64Decode(encoded)
computeHmacSha256Signature(value, key)
I finally found the solution to it. It was a problem with the type of data I submitted to "Utilities.computeHmacSha256Signature". In the code you can find the function working well.
function SignAndCallAPI(method, requestPath, body) {
var timestamp = Math.floor(Date.now() / 1000).toString();
var what = Utilities.base64Decode(Utilities.base64Encode(timestamp + method + requestPath + body));
var decodedsecret = Utilities.base64Decode(globalvars_CB.secret);
var hmac = Utilities.base64Encode(Utilities.computeHmacSha256Signature(what, decodedsecret));
var options = {
'method' : method,
'muteHttpExceptions' : true,
'headers' : {
'Content-Type': 'application/json',
'CB-ACCESS-KEY' : globalvars_CB.apikey,
'CB-ACCESS-SIGN' : hmac,
'CB-ACCESS-TIMESTAMP' : timestamp,
'CB-ACCESS-PASSPHRASE' : globalvars_CB.passphrase,
}
}
var responseJson = UrlFetchApp.fetch(globalvars_CB.uri+requestPath, options);
Logger.log(responseJson);
return(responseJson);
}
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.