Google App Script oAuth2 to Questrade API Invalid Token - google-apps-script

I'm trying to used Google Apps Script to pull Questrade account information into a Google sheets spreadsheet. I've added the oAuth2 library from GitHub(https://github.com/googleworkspace/apps-script-oauth2) then mostly copy and pasted (with minor edits) from the example code.
The weird thing is this code has worked, exactly how it is, but a day later it no longer works and returns the following:
Exception: Request failed for https://api01.iq.questrade.com returned code 401. Truncated server response: {"code":1017,"message":"Access token is invalid"} (use muteHttpExceptions option to examine full response)
My Google Apps Script is posted below. I've only removed my Questrade Client_ID and Google Script Script_ID. I have three buttons in my spreadsheet which I've linked to functions in the script:
Button 1 - QT oAuth - calls showSidebar
Button 2 - Load Account Info - calls makeRequest
Button 3 - QT Logout - calls logout
Typically, I press the QT Logout button to reset 0Auth2 services then I press the QT oAuth button. This seems to successfully go through the authorization process. I then press the Load Account Info button and about 99 times out of 100 I get the invalid access token message. I don't know if it's relevant, but when I log into Questrades API hub I can see that the script is adding an authorization after the QT oAuth button is pressed but it seems to disappear after about a minute.
The script:
function getQTService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('QT')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://login.questrade.com/oauth2/authorize')
.setTokenUrl('https://login.questrade.com/oauth2/token')
// Set the client ID and secret.
.setClientId('Client_ID')
.setClientSecret(' ') //there is no client secret but oAuth2 requires one
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
// Set the scopes to request (space-separated for Google services).
.setScope('read_acc')
}
function showSidebar() {
var QTService = getQTService();
if (!QTService.hasAccess()) {
var authorizationUrl = QTService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
} else {
// ...
}
}
function authCallback(request) {
var QTService = getQTService();
var isAuthorized = QTService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
function makeRequest() {
var QTService = getQTService();
var token = QTService.getAccessToken();
var spreadsheet = SpreadsheetApp.openById("Script_ID");
// Get account number
var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts',{
headers: {
Authorization: 'Bearer ' + token
}
});
var json = response.getContentText();
var accountdata = JSON.parse(json);
var j = 0;
while(j < accountdata.accounts.length) {
var Account_num = accountdata.accounts[j].number;
var Account_type = accountdata.accounts[j].type;
var sheet = spreadsheet.getSheetByName(Account_type);
// GET CASH BALANCE
var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts/' + Account_num + '/balances',{
headers: {
Authorization: 'Bearer ' + token
}
});
json = response.getContentText();
var balancedata = JSON.parse(json);
var i = 0;
while(balancedata.perCurrencyBalances[i].currency != 'CAD') {
i=i+1;
}
//send cash value to spreadsheet
sheet.getRange("G1").setValue(balancedata.perCurrencyBalances[i].cash);
// GET POSITIONS
var response = UrlFetchApp.fetch('https://api01.iq.questrade.com/v1/accounts/' + Account_num + '/positions',{
headers: {
Authorization: 'Bearer ' + token
}
});
json = response.getContentText();
var positionsdata = JSON.parse(json);
var num_of_positions = positionsdata.positions.length;
var i = 0;
while(i < num_of_positions) { //this loop is not that smart assumes the positions are where I specify, fix later
if(positionsdata.positions[i].symbol == 'VCN.TO'){
sheet.getRange("D5").setValue(positionsdata.positions[i].openQuantity);
}
if(positionsdata.positions[i].symbol == 'VUN.TO') {
sheet.getRange("D6").setValue(positionsdata.positions[i].openQuantity);
}
if(positionsdata.positions[i].symbol == 'VIU.TO') {
sheet.getRange("D7").setValue(positionsdata.positions[i].openQuantity);
}
if(positionsdata.positions[i].symbol == 'VEE.TO') {
sheet.getRange("D8").setValue(positionsdata.positions[i].openQuantity);
}
i=i+1;
}
j=j+1;
}
//send cash value to spreadsheet
// sheet.getRange("G1").setValue(data.perCurrencyBalances[i].cash);
}
function logout() {
var service = getQTService();
service.reset();
}
Any advice on what might be going wrong here would be greatly appreciated.

I don't think you can rely on using api01. I think you have to extract the api_server from the auth call that gives you a token (or at least I did this using the example on https://www.questrade.com/api/documentation/getting-started with refresh_token). My last 3 refresh_token auths for a bearer have yielded the api06 endpoint. I took your code and with the oauth authorizing and using api06 it works fine.
The magic sauce is var api_server = QTService.getToken().api_server;

Related

Google Ads API with Google Apps Script no access token received

I am trying to use Google Ads on Google Apps Script using this guide. I created client id and client secret on Google Console's API & Services.
Not too sure if this configuration is correct but the account is linked to Google Apps Script as I have pagespeed insights as well and there are some requests on the dashboard. I added https://www.googleapis.com/auth/drive as the scope. Again not too sure if I should add Google Ads to the scope. Lastly, got my refresh token from Google Auth playground. When I run the script above I got the following error:
Error: No access token received: {
"error": "invalid_client",
"error_description": "Unauthorized"
}
authenticate_ # test.gs:120
withRefreshToken # test.gs:144
initializeOAuthClient # test.gs:28
Honestly not too sure what I am doing wrong here so any help would be very much appreciated. Thank you.
Edit
Codes:
//From Google Console API & Services
var CLIENT_ID = '"MY_CLIENT_ID';
var CLIENT_SECRET = 'MY_CLIENT_SECRET';
//From Google Authplayground
var REFRESH_TOKEN = 'REFRESH_TOKEN';
// Enter scopes which should match scopes in File > Project properties
// For this project, e.g.: https://www.googleapis.com/auth/drive
var SCOPES = "https://www.googleapis.com/auth/adwords";
// Script ID taken from 'File > Project Properties'
var SCRIPT_ID = 'MY_SCRIPT_ID';
var authUrlFetch;
// Call this function just once, to initialize the OAuth client.
function initializeOAuthClient() {
if (typeof OAuth2 === 'undefined') {
var libUrl = 'https://developers.google.com/google-ads/scripts/docs/examples/oauth20-library';
throw Error('OAuth2 library not found. Please take a copy of the OAuth2 ' +
'library from ' + libUrl + ' and append to the bottom of this script.');
}
var tokenUrl = 'https://accounts.google.com/o/oauth2/token';
authUrlFetch = OAuth2.withRefreshToken(tokenUrl, CLIENT_ID, CLIENT_SECRET,
REFRESH_TOKEN, SCOPES);
}
/**
* Execute a remote function.
* #param {string} remoteFunctionName The name of the function to execute.
* #param {Object[]} functionParams An array of JSON objects to pass to the
* remote function.
* #return {?Object} The return value from the function.
*/
function executeRemoteFunction(remoteFunctionName, functionParams) {
var apiParams = {
'function': remoteFunctionName,
'parameters': functionParams
};
var httpOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
payload: JSON.stringify(apiParams)
};
var url = 'https://script.googleapis.com/v1/scripts/' + SCRIPT_ID + ':run';
var response = authUrlFetch.fetch(url, httpOptions);
var data = JSON.parse(response.getContentText());
// Retrieve the value that has been returned from the execution.
if (data.error) {
throw Error('There was an error: ' + response.getContentText());
}
return data.response.result;
}
// Paste in OAuth2 library here, from:
// https://developers.google.com/google-ads/scripts/docs/examples/oauth20-library
I have pasted the oauth2.0 library under the codes above.
Edit 2
I fixed the part of function initializeOAuthClient. It now shows execution complete, but when I try to run function executeRemoteFunction I am getting TypeError: Cannot read property 'fetch' of undefined. I am guessing I have to input remoteFunctionName and functionParams but where do I find them?

Using Google Apps Script to configure OAuth

I'm having some trouble configuring my Google Apps Script to properly handle the token that comes from the API I'm reaching out to. Everything from what I can tell is compatible.
I am using the Apps Script oAuth2 here.
When I run the below scripts I am able to get to the oAuth screen where i validate on the app, and when it passes the credentials back to google scripts on usercallback i get the below error.
Error: Error retrieving token: {"id":"401","name":"unauthorized","detail":"Unauthorized"} (line 541, file "Service")
My oAuth Script is below:
var CLIENT_ID = '...1';
var CLIENT_SECRET = '...2';
// configure the service
function getYNABService() {
return OAuth2.createService('YNAB')
.setAuthorizationBaseUrl('https://app.youneedabudget.com/oauth/authorize')
.setTokenUrl('https://api.youneedabudget.com/v1/budgets?access_token')
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('read-only')
.setGrantType('authorization_code');
}
// Logs the redict URI to register
// can also get this from File > Project Properties
function logRedirectUri() {
var service = getService();
Logger.log(service.getRedirectUri());
}
// handle the callback
function authCallback (request) {
var YNABService = getYNABService();
var isAuthorized = YNABService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
My Google Sheets script is below
// add custom menu
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('YNAB for Sheets')
.addItem('Authorize','showSidebar')
.addItem('Fetch Budget','FetchBudgets')
.addItem('Reset','reset')
.addToUi();
}
/***************************************/
// Show sidebar for Authorization
function showSidebar() {
var YNABService = getYNABService();
if (!YNABService.hasAccess()) {
var authorizationUrl = YNABService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
SpreadsheetApp.getUi().showSidebar(page);
} else {
// ...
}
}
function reset() {
getYNABService().reset();
}
function FetchBudgets() {
var YNABService = getYNABService();
var response = UrlFetchApp.fetch('https://api.youneedabudget.com/v1/budgets/default/accounts', {
headers: {
Authorization: 'Bearer ' + YNABService.getAccessToken()
}
});
// ...
}
Upon deeper investigation on this, it seems that the problem is on my fault. I mixed the URLs for client/server side auth.
https://app.youneedabudget.com/oauth/token? - this is the correct token URL.

Data from Patreon into Google Sheets

I'm trying to pipe data from a specific Patreon Creator Page into a Google Spreadsheet such as: Member first name, member last name, tier, etc. etc.
I've read the Patreon Developers Documentation here. https://docs.patreon.com
I referenced Ben Collin's tutorial on integrating Google Sheets with the Strava API here: https://www.benlcollins.com/spreadsheets/strava-api-with-google-sheets/
I've successfully connected Patreon to my Google App and have granted access but still no data. I am getting the following error when I run the script:
TypeError: Cannot find function forEach in object [object Object].
Any ideas as to how I can get the data I'm after?
var CLIENT_ID = 'HIDDEN';
var CLIENT_SECRET = 'HIDDEN';
var redirectURL = 'https://script.google.com/macros/d/HIDDEN:scriptID/usercallback'
// configure the service
function getPatreonService() {
return OAuth2.createService('Patreon')
.setAuthorizationBaseUrl('https://www.patreon.com/oauth2/authorize')
.setTokenUrl('https://api.patreon.com/oauth2/token')
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope('included:read_all');
}
// handle the callback
function authCallback(request) {
var patreonService = getPatreonService();
var isAuthorized = patreonService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
// custom menu
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Patreon App')
.addItem('Get data', 'getPatreonPledgeData')
.addToUi();
}
// Get pledge data
function getPatreonPledgeData() {
// get the sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
// call the Patreon API to retrieve data
var data = callPatreonAPI();
// empty array to hold pledge data
var patreonData = [];
// loop over pledge data and add to patreonData array for Sheet
data.forEach(function(pledge) {
var arr = [];
arr.push(
pledge.first_name,
pledge.full_name,
pledge.email,
pledge.last_name
);
patreonData.push(arr);
});
// paste the values into the Sheet
sheet.getRange(sheet.getLastRow() + 1, 1, patreonData.length, patreonData[0].length).setValues(patreonData);
}
// call the Patreon API
function callPatreonAPI() {
// set up the service
var service = getPatreonService();
if (service.hasAccess()) {
Logger.log('App has access.');
var endpoint = 'https://www.patreon.com/api/oauth2/api/campaigns/<HIDDEN:CampaignID>/pledges';
var params = '?include=patron.null';
var headers = {
Authorization: 'Bearer ' + service.getAccessToken()
};
var options = {
headers: headers,
method : 'GET',
muteHttpExceptions: true
};
var response = JSON.parse(UrlFetchApp.fetch(endpoint + params, options));
return response;
}
else {
Logger.log("App has no access yet.");
// open this url to gain authorization
var authorizationUrl = service.getAuthorizationUrl();
Logger.log("Open the following URL and re-run the script: %s",
authorizationUrl);
}
}
The error comes because the Oauth flow is not being completely executed (It may be that it's not presenting the consent screen, you're not logging in with your account, etc).
As stated in the Oauth2 library documentation for Apps Script that you're using [1] in the "Connecting to a Google API" part, a more simple way to obtain the access token is using ScriptApp.getOAuthToken() function [2] instead of service.getAccessToken(), and set the needed scopes in the manifest file [3].
In any case, I hardly believe that could give you access to the Patreon API [4]. Also, I don't see the url you're using in the Patreon API documentation [5].
[1] https://github.com/gsuitedevs/apps-script-oauth2
[2] https://developers.google.com/apps-script/reference/script/script-app#getoauthtoken
[3] https://developers.google.com/apps-script/concepts/manifests#editing_a_manifest
[4] https://docs.patreon.com/#oauth
[5] https://docs.patreon.com/?javascript#api-endpoints

How to generate redirect URI for Smartsheet to pass data to Apps Script

Screenshot of smartsheet and google setup screens
When attempting to get data out of Smartsheet, I'm encountering an error that says the redirect URI is missing or invalid when I follow the link that was logged by my apps script project.
I've generated a client ID and client secret on both google and smartsheet but I don't know what to do next.
Google Credentials:
I'm not sure what to put in the redirect Url section or the authorized Javascript origins at the link below.
https://console.developers.google.com/apis/credentials/oauthclient/########################2d.apps.googleusercontent.com?project=project-id-##############
Smartsheet Credentials:
I have activated my Smartsheet Developer profile and generated a client ID and client secret for my app that I've called 'Google Sheets'
Shown below is the code that I have right now which I found on gitHub.
var CLIENT_ID = '...'; // what do I put here?
var CLIENT_SECRET = '...'; // what do I put here?
/**
* Authorizes and makes a request to the Smartsheet API.
*/
function run()
{
var service = getService();
if (service.hasAccess())
{
var url = 'https://api.smartsheet.com/2.0/users/me';
var response = UrlFetchApp.fetch(url,
{
headers:
{
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
}
else
{
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s', authorizationUrl);
}
}
/**
* Reset the authorization state, so that it can be re-tested.
*/
function reset()
{
getService().reset();
}
/**
* Configures the service.
*/
function getService()
{
return OAuth2.createService('Smartsheet')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://app.smartsheet.com/b/authorize')
.setTokenUrl('https://api.smartsheet.com/2.0/token')
// Set the client ID and secret.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function that should be invoked to
// complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Scopes to request
.setScope('READ_SHEETS')
// Set the handler for adding Smartsheet's required SHA hash parameter to
// the payload:
.setTokenPayloadHandler(smartsheetTokenHandler);
}
/**
* Handles the OAuth callback.
*/
function authCallback(request)
{
var service = getService();
var authorized = service.handleCallback(request);
if (authorized)
{
return HtmlService.createHtmlOutput('Success!');
}
else
{
return HtmlService.createHtmlOutput('Denied.');
}
}
/**
* Adds the Smartsheet API's required SHA256 hash parameter to the access token
* request payload.
*/
function smartsheetTokenHandler(payload)
{
var codeOrRefreshToken = payload.code ? payload.code : payload.refresh_token;
var input = CLIENT_SECRET + '|' + codeOrRefreshToken;
var hash = Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8);
hash = hash.map(function(val)
{
// Google appears to treat these as signed bytes, but we need them
// unsigned.
if (val < 0)
{
val += 256;
}
var str = val.toString(16);
// pad to two hex digits:
if (str.length == 1)
{
str = '0' + str;
}
return str;
});
payload.hash = hash.join('');
// The Smartsheet API doesn't need the client secret sent (secret is verified
// by the hash).
if (payload.client_secret)
{
delete payload.client_secret;
}
return payload;
}
/**
* Logs the redict URI to register.
*/
function logRedirectUri()
{
Logger.log(OAuth2.getRedirectUri());
}
function dataHandler(thing)
{
thing = getData2();
var rowTemp = thing.split(','), i, j, chunk = 7, rows = [];
for (i=0,j=rowTemp.length; i<j; i+=chunk)
{
for(var k = 0; k<2; k++)
{
rowTemp[k+2] = new Date(rowTemp[k+2])
}
rows.push(rowTemp.slice(i,i+chunk));
}
Logger.log(rows);
}
var CLIENT_ID = 'SmartSheet Client ID'; // I'm not sure if this is
// supposed to come from google
// or smartsheet
var CLIENT_SECRET = 'Smartsheet Client Secret'; // Same here
/**
* Authorizes and makes a request to the Smartsheet API.
*/
function run() {
var service = getService();
if (service.hasAccess()) {
var url = 'https://api.smartsheet.com/2.0/users/me';
var response = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + service.getAccessToken()
}
});
var result = JSON.parse(response.getContentText());
Logger.log(JSON.stringify(result, null, 2));
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s',
authorizationUrl);
}
}
/**
* Reset the authorization state, so that it can be re-tested.
*/
function reset() {
getService().reset();
}
/**
* Configures the service.
*/
function getService()
{
return OAuth2.createService('Smartsheet')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://app.smartsheet.com/b/authorize')
.setTokenUrl('https://api.smartsheet.com/2.0/token')
// Set the client ID and secret.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function that should be invoked to
// complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Scopes to request
.setScope('READ_SHEETS')
// Set the handler for adding Smartsheet's required SHA hash parameter to
// the payload:
.setTokenPayloadHandler(smartsheetTokenHandler);
}
/**
* Handles the OAuth callback.
*/
function authCallback(request)
{
var service = getService();
var authorized = service.handleCallback(request);
if (authorized)
{
return HtmlService.createHtmlOutput('Success!');
}
else
{
return HtmlService.createHtmlOutput('Denied.');
}
}
/**
* Adds the Smartsheet API's required SHA256 hash parameter to the access token
* request payload.
*/
function smartsheetTokenHandler(payload)
{
var codeOrRefreshToken = payload.code ? payload.code : payload.refresh_token;
var input = CLIENT_SECRET + '|' + codeOrRefreshToken;
var hash = Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8);
hash = hash.map(function(val)
{
// Google appears to treat these as signed bytes, but we need them
// unsigned.
if (val < 0)
{
val += 256;
}
var str = val.toString(16);
// pad to two hex digits:
if (str.length == 1)
{
str = '0' + str;
}
return str;
});
payload.hash = hash.join('');
// The Smartsheet API doesn't need the client secret sent (secret is verified
// by the hash).
if (payload.client_secret)
{
delete payload.client_secret;
}
return payload;
}
/**
* Logs the redict URI to register.
*/
function logRedirectUri()
{
Logger.log(OAuth2.getRedirectUri());
}
I don't know much about Apps Script or the library that you are using, but you need to find the actual callback URI used by Apps Script and register that as the App Redirect URL in Smartsheet. It looks like the callback should be in the form https://script.google.com/macros/d/{SCRIPT ID}/usercallback (at least according to the library docs). That should issue the redirect which will eventually call your library authCallback with the authorization code for the token.
Here's another useful document of the process (but uses Node). https://developers.smartsheet.com/blog/creating-a-smartsheet-o-auth-flow-in-node-js
This is a complicated process that I have documented here: https://smartsheet-platform.github.io/api-docs/#third-party-app-development
If you still have questions after looking at this documentation/tutorial section, please keep asking. I'm here to help.

0Auth2 problem to get my google photos libraries in a google sheet of mine

I am trying to follow documentation but never get the 0Auth2 to connect.
Btw I'm running the script manually from the google sheets scripting page, where should I get prompting for allowing access?
(I don't understand all this 0Auth2 scheme and I have already gave authorization to run the script and got client id and secret)...
See below my log and script routines (the first get to photo is still minimalistic as I didn't yet get through the 0Auth2 step ;-) .
Thanks in advance for any hint. I thought it would be trivial as it is my own sheet and google photo account...
Log:
[19-01-06 17:50:05:809 CET] starting
[19-01-06 17:50:05:810 CET] getPhotoService
[19-01-06 17:50:05:849 CET] false
[19-01-06 17:50:05:850 CET] redirectURI=https://script.google.com/macros/d/[REMOVED]/usercallback
[19-01-06 17:50:05:864 CET] Open the following URL and re-run the script: https://accounts.google.com/o/oauth2/auth?client_id=[removed].apps.googleusercontent.com&response_type=code&redirect_uri=https%3A%2F%2Fscript.google.com%2Fmacros%2Fd%2F[removed]%2Fusercallback&state=[removed]&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fphotoslibrary.readonly&login_hint=[removed]&access_type=offline&approval_prompt=force
Script:
function getPhotoService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
Logger.log('getPhotoService');
return OAuth2.createService('gPHOTOfj')
// enable caching on the service, so as to not exhaust script's PropertiesService quotas
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId(CLIENT_ID)
.setClientSecret(CLIENT_SECRET)
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
//.setCallbackFunction('https://script.google.com/macros/d/'+SCRIPT_ID+'/authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/photoslibrary.readonly')
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
function authCallback(request) {
Logger.log('Called back!');
var photoService = getPhotoService();
var isAuthorized = photoService.handleCallback(request);
if (isAuthorized) {
Logger.log('Authorisation Success!');
} else {
Logger.log('Authorisation Denied...!');
}
}
// Modified from http://ctrlq.org/code/20068-blogger-api-with-google-apps-script
function photoAPI() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var albums_sh = ss.getSheetByName("albums") || ss.insertSheet("albums", ss.getSheets().length);
albums_sh.clear(); var nrow = new Array(); var narray = new Array();
Logger.log("starting");
var service = getPhotoService();
Logger.log(service.hasAccess());
Logger.log('redirectURI='+service.getRedirectUri());
if (service.hasAccess()) {
var api = "https://photoslibrary.googleapis.com/v1/albums";
var headers = {
"Authorization": "Bearer " + service.getAccessToken()
};
var options = {
"headers": headers,
"method" : "GET",
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch(api, options);
var json = JSON.parse(response.getContentText());
for (var i in json.items) {
nrow = []; nrow.push(json.items[i].id); nrow.push(json.items[i].name); nrow.push(json.items[i].url); narray.push(nrow);
}
albums_sh.getRange(1,1,narray.length,narray[0].length).setValues(narray);
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log("Open the following URL and re-run the script: " + authorizationUrl);
}
}
So it works, if others want to use it. But it is quite slow (I have 500 albums...):
function photoAPI() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var albums_sh = ss.getSheetByName("albums") || ss.insertSheet("albums", ss.getSheets().length);
albums_sh.clear();
var narray = [];
var api = "https://photoslibrary.googleapis.com/v1/albums";
var headers = { "Authorization": "Bearer " + ScriptApp.getOAuthToken() };
var options = { "headers": headers, "method" : "GET", "muteHttpExceptions": true };
var param= "", nexttoken;
do {
if (nexttoken)
param = "?pageToken=" + nexttoken;
var response = UrlFetchApp.fetch(api + param, options);
var json = JSON.parse(response.getContentText());
json.albums.forEach(function (album) {
var data = [
album.title,
album.mediaItemsCount,
album.productUrl
];
narray.push(data);
});
nexttoken = json.nextPageToken;
} while (nexttoken);
albums_sh.getRange(1, 1, narray.length, narray[0].length).setValues(narray);
}