I would like to use an API (url shortener) with a public google Add-on.
For the moment my code returns:
Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.
Is this possible?
If yes, Do I need an authentification Token?
If yes, what key type should I choose?
How can I implement the authorisation for this kind of use?
Do I need to pay for it?
If no, How could other add-ons use external APIs
Thanks a lot for your answers,
EDIT: The OP pointed out in the comments that this is a custom function. Custom function run with limited authorization. A complete list of what is available is at:
https://developers.google.com/apps-script/guides/sheets/functions#using_apps_script_services
Below uses the REST API to get the shortened url. This will work with custom functions. You will just need to enable the URL Shortener API and generate a Server API Key. Use the IPs at the following link for your server api key:
https://developers.google.com/apps-script/guides/jdbc#setup_for_google_cloud_sql
/**
* Returns a shortened URL of the input.
*
* #param {string} longUrl The long URL to shorten.
* #return The shortened url.
* #customfunction
*/
function getShortUrl(longUrl) {
var payLoad = {"longUrl": longUrl};
var apiKey = PropertiesService.getScriptProperties().getProperty("ServerApiKey");
var url = "https://www.googleapis.com/urlshortener/v1/url?key="+ apiKey;
var options = { method:"POST",
contentType:"application/json",
payload:JSON.stringify(payLoad),
muteHttpExceptions:true};
var response = UrlFetchApp.fetch(url, options);
if(response.getResponseCode() != 200){throw new Error("Unable to shorten url");}
return JSON.parse(response).id;
}
Original Post
Here is a quick primer on using the UrlShortener advanced service. You will need to turn on the service and activate the Url Shortener api in the developers console. This will give you a quota of 1,000,000 requests per day for your add-on.
function myFunction() {
var url = UrlShortener.newUrl();
url.longUrl = "http://www.example.org";
var short = UrlShortener.Url.insert(url);
Logger.log(short);
//list all users shortened urls
Logger.log(UrlShortener.Url.list());
}
Related
I'm trying to make a Google sheet that integrates with the YouTube Data and Analytics API. However, when trying to implement this, I came into a known issue with regards to allowing brand YouTube accounts/channels to be authenticated with a Google app which is explained here. https://issuetracker.google.com/issues/36764531
To get around this, the document mentions instructions from this link which I am now trying to implement myself https://mashe.hawksey.info/2017/09/identity-crisis-using-the-youtube-api-with-google-apps-script-and-scheduling-live-broadcasts-from-google-sheets/
From the instructions, I have:
Imported the necessary libraries into the script
Added the necessary Google Apps Script code which is at the bottom
Created my own OAuth 2.0 Client ID credentials in the Google Cloud Console
However, in the first link, a comment had also been added to say that while the instructions still worked, the script project now has to be associated with a cloud project and so that's what I did. As part of this, it created its own OAuth 2.0 Client ID which I believe it's now using rather than the credentials I had already generated myself. I have added a picture below to illustrate what I mean. I also then can't edit these new credentials meaning that I can't add any redirect URI.
Is there a way I can add the redirect URI to the automatically generated credentials? The problem now is that if I then run the setup function from the script as per the instructions, when I then try to open this link it gives me, I then get given the following message
Error 400: redirect_uri_mismatch
The redirect URI in the request, https://script.google.com/macros/d/12u2laknmO_9-zgxBbAX6wG9gJDUOvgJmYm5UquJsamShus9s5McrGBar/usercallback, does not match the ones authorized for the OAuth client. To update the authorized redirect URIs, visit: https://console.developers.google.com/apis/credentials/oauthclient/${your_client_id}?project=${your_project_number}
/**
* Authorizes and makes a request to the YouTube Data API.
*/
function setup() {
var service = getYouTubeService();
YouTube.setTokenService(function(){ return service.getAccessToken(); });
if (service.hasAccess()) {
var result = YouTube.channelsList("snippet", {mine:true});
Logger.log(JSON.stringify(result, null, 2));
throw "Open View > Logs to see result";
} else {
var authorizationUrl = service.getAuthorizationUrl();
Logger.log('Open the following URL and re-run the script: %s',
authorizationUrl);
throw "Open View > Logs to get authentication url";
}
}
/**
* Configures the service.
*/
function getYouTubeService() {
return OAuth2.createService('YouTube')
// Set the endpoint URLs.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret.
.setClientId(getStaticScriptProperty_('client_id'))
.setClientSecret(getStaticScriptProperty_('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
// you might want to switch to Script Properties if sharing access
.setPropertyStore(PropertiesService.getUserProperties())
// Set the scope and additional Google-specific parameters.
.setScope(["https://www.googleapis.com/auth/youtube",
"https://www.googleapis.com/auth/youtube.force-ssl",
"https://www.googleapis.com/auth/youtube.readonly",
"https://www.googleapis.com/auth/youtubepartner",
"https://www.googleapis.com/auth/youtubepartner-channel-audit"])
.setParam('access_type', 'offline');
}
/**
* Handles the OAuth callback.
*/
function authCallback(request) {
var service = getYouTubeService();
var authorized = service.handleCallback(request);
if (authorized) {
return HtmlService.createHtmlOutput('Success!');
} else {
return HtmlService.createHtmlOutput('Denied');
}
}
/**
* Logs the redirect URI to register in the Google Developers Console.
*/
function logRedirectUri() {
var service = getYouTubeService();
Logger.log(service.getRedirectUri());
throw "Open View > Logs to get redirect url";
}
/**
* Reset the authorization state, so that it can be re-tested.
*/
function reset() {
var service = getYouTubeService();
service.reset();
}
/**
* Gets a static script property, using long term caching.
* #param {string} key The property key.
* #returns {string} The property value.
*/
function getStaticScriptProperty_(key) {
var value = CacheService.getScriptCache().get(key);
if (!value) {
value = PropertiesService.getScriptProperties().getProperty(key);
CacheService.getScriptCache().put(key, value, 21600);
}
return value;
}
I hope this is clear but if not then I can answer any further questions. Or if there is a better way of being able to authenticate a brand YouTube account with the API, then please let me know.
In the end I had to make new credentials in the console. Once I had done this and bound it with the script, it then seemed to work as expected.
We want to get data from our API into Google Sheets. Our API requires authentication. Our tentative architecture has the user entering their credentials in a ModalDialog, then we hit our authentication endpoint which returns a session token which is saved. Subsequently the user makes customfunction calls in which we make calls to our API data end-point and include the authentication token.
My question -
How do I get the authentication token into the scope of the customfunction? It seems like PropertiesService.getUserProperties should do this but the values we set in the function that gets the credential token are not visible in the customfunction.
function saveCredentials(action){
var username = action['username'],
password = action['password'];
var response = UrFetchApp.fetch('https://www.foo.com/account/logon/, {method: 'post', payload: {email: action, password: password}});
var token = response.getAllHeaders()['Set-Cookie'];
PropertiesService.getUserProperties().setProperty('token', token);}
/**
* #param {parameter1}
* #param {parameter2}
* #customfunction
*/
function getDataFromFooAPI(parameter1, parameter2){
var token = PropertiesService.getUserProperties().getProperty('token') // expecting this to be the value set in saveCredentials but it is not.
var url = 'https://www.foo.com/api/data/' + parameter1 + '/' + parameter2;
var response = UrlFetchApp.fetch(url, {'headers' : {'token' : token}};
}
Does the context in which the customfunction executes references a different PropertiesService than the rest of the script? Are we trying to do the impossible?
There are some documented limitations with custom functions, notably that they don't have access to personal information. They can access script and document properties, but not user properties. Custom functions should behave consistently for all users who have access to the sheet and operate without authorization. Relying on user properties would make that virtually impossible.
If you can use document properties instead that should work.
I need to execute a GAS service on behalf of a user that is logged to my system. So I have her/his access token. I would like somehow to transfer the token to the web app and without having to authorize again the user to use it for some activities. Can this be accomplished? Thank you.
EDIT: I think I didn't explain right what I try to accomplish. Here is the work flow I try to achieve:
We authorize a user visiting our website using OAuth2 and Google;
We get hold of her/his access token that Google returns;
There is a Google Apps Script web app that is executed as the user running the web app;
We want to call this app (3) by providing the access token (2) so Google not to ask again for authorization;
Actually, we want to call this app (3) not by redirecting the user to it but by calling it as a web service.
Thanks
Martin's answer worked for me in the end, but when I was making a prototype there was a major hurdle.
I needed to add the following scope manually, as the "automatic scope detection system" of google apps script did not ask for it: "https://www.googleapis.com/auth/drive.readonly". This resulted in UrlFetchApp.fetch always giving 401 with additional information I did not understand. Logging this additional information would show html, including the following string
Sorry, unable to open the file at this time.</p><p> Please check the address and try again.
I still don't really understand why "https://www.googleapis.com/auth/drive.readonly" would be necessary. It may have to do with the fact that we can use the /dev url, but who may use the /dev url is managed is checked using the drive permissions of the script file.
That said, the following setup then works for me (it also works with doGet etc, but I chose doPost). I chose to list the minimally needed scopes explicitly in the manifest file, but you can also make sure the calling script will ask for permissions to access drive in different ways. We have two google apps script projects, Caller and WebApp.
In the manifest file of Caller, i.e. appsscript.json
{
...
"oauthScopes":
[
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/script.external_request"]
}
In Code.gs of Caller
function controlCallSimpleService(){
var webAppUrl ='https://script.google.com/a/DOMAIN/macros/s/id123123123/exec';
// var webAppUrl =
// 'https://script.google.com/a/DOMAIN/macros/s/id1212121212/dev'
var token = ScriptApp.getOAuthToken();
var options = {
'method' : 'post'
, 'headers': {'Authorization': 'Bearer '+ token}
, muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(response.getContentText());
}
In Code.gs of WebApp (the web app being called)
function doPost(event){
return ContentService.createTextOutput("Hello World");
}
The hard answer is NO you can't use the built-in services of Apps Script with a service token. But if you already have the token for a user generated by a service account, access to the users data is pretty similar to any other language. All calls would be to the REST interface of the service your token is scoped for.
Take this small script for example. It will build a list of all the user's folders and return them as JSON:
function doGet(e){
var token = e.parameter.token;
var folderArray = [];
var pageToken = "";
var query = encodeURIComponent("mimeType = 'application/vnd.google-apps.folder'");
var params = {method:"GET",
contentType:'application/json',
headers:{Authorization:"Bearer "+token},
muteHttpExceptions:true
};
var url = "https://www.googleapis.com/drive/v2/files?q="+query;
do{
var results = UrlFetchApp.fetch(url,params);
if(results.getResponseCode() != 200){
Logger.log(results);
break;
}
var folders = JSON.parse(results.getContentText());
url = "https://www.googleapis.com/drive/v2/files?q="+query;
for(var i in folders.items){
folderArray.push({"name":folders.items[i].title, "id":folders.items[i].id})
}
pageToken = folders.nextPageToken;
url += "&pageToken="+encodeURIComponent(pageToken);
}while(pageToken != undefined)
var folderObj = {};
folderObj["folders"] = folderArray;
return ContentService.createTextOutput(JSON.stringify(folderObj)).setMimeType(ContentService.MimeType.JSON);
}
You do miss out on a lot of the convenience that makes Apps Script so powerful, mainly the built in services, but all functionality is available through the Google REST APIs.
I found a way! Just include the following header in the request:
Authorization: Bearer <user's_access_token>
As far as I understand, in order to track our quota usage, we need to provide our API key to the Google App Service on the service we are planning to use.
In my case I have a spreadsheet with Origin and Destination and a Custom function to calculate the distance between.
I ran into the problem of meeting the quota from invoking .getDirections():
Error: Service invoked too many times for one day: route. (line **).
Sample of the code:
function getDirections_(origin, destination) {
var directionFinder = Maps.newDirectionFinder();
directionFinder.setOrigin(origin);
directionFinder.setDestination(destination);
var directions = directionFinder.getDirections();
return directions;
}
So I read that if I assign the API Key to my project I should be able to see the usage and how close to the free quota I am.
In the script editor, I did enable all of the APIs under Resources menu/ Advanced Google Services. Then I went to the Google Developers Console and there
I did not see any record of how many times my custom function called the Google Maps API or any API usage.
Logically I think that in my script I need to set my google API Key so my scripts start to call the API under my user name and count the number of time I used certain API. I guess right now I am using the Google Maps API as anonymous and since the whole company is assigned with the same IP, so we exhaust the permitted numbers to call this function.
Bottom line please reply if you know a way to connect my simple Spreadsheet function to the Public API access Key I have.
Thank you,
Paul
I also have been eager to find this answer for a long time and am happy to say that I've found it. It looks like Google might have just made this available around Oct 14, 2015 based on the date this page was updated.
You can leverage the UrlFetchApp to add your API key. The link I posted above should help with obtaining that key.
function directionsAPI(origin, destination) {
var Your_API_KEY = "Put Your API Key Here";
var serviceUrl = "https://maps.googleapis.com/maps/api/directions/json?origin="+origin+"&destination="+destination+
"&mode="+Maps.DirectionFinder.Mode.DRIVING+"&alternatives="+Boolean(1)+"&key="+Your_API_KEY;
var options={
muteHttpExceptions:true,
contentType: "application/json",
};
var response=UrlFetchApp.fetch(serviceUrl, options);
if(response.getResponseCode() == 200) {
var directions = JSON.parse(response.getContentText());
if (directions !== null){
return directions;
}
}
return false;
}
So walking through the code... first put in your API key. Then choose your parameters in the var serviceUrl. I've thrown in additional parameters (mode and alternatives) to show how you can add them. If you don't want them, remove them.
With UrlFetch you can add options. I've used muteHttpExceptions so that the fetch will not throw an exception if the response code indicates failure. That way we can choose a return type for the function instead of it throwing an exception. I'm using JSON for the content type so we can use the same format to send and retrieve the request. A response code of 200 means success, so directions will then parse and act like the object that getDirections() would return. The function will return false if the UrlFetch was not successful (a different response code) or if the object is null.
You will be able to see the queries in real time in your developer console when you look in the Google Maps Directions API. Be sure that billing is enabled, and you will be charged once you exceed the quotas.
1.) I added an API key from my console dashboard. Remember to select the correct project you are working on. https://console.developers.google.com/apis/credentials?project=
2.) In my Project (Scripts Editor) I setAuthentication to Maps using the API key and the Client ID from the console. I have included the script below:
function getDrivingDirections(startLoc, wayPoint, endLoc){
var key = "Your_API_Key";
var clientID = "Your_Client_ID";
Maps.setAuthentication(clientID, key);
var directions = Maps.newDirectionFinder()
.setOrigin(startLoc)
.addWaypoint(wayPoint)
.setDestination(endLoc)
.setMode(Maps.DirectionFinder.Mode.DRIVING)
.getDirections();
} return directions;
https://developers.google.com/apps-script/reference/maps/maps#setAuthentication(String,String)
As of 7/13/2017, I was able to get the API to function by enabling the Sheets API in both the "Advanced Google Services" menu (images 1 and 2), and in the Google Developer Console. If you're logged into Google Sheets with the same email address, no fetch function should be necessary.
[In the Resources menu, select Advanced Google Services.][1]
[In Advanced Google Services, make sure the Google Sheets API is turned on.][2]
[1]:
[2]:
Thank goodness for JP Carlin! Thank you for your answer above. JP's answer also explains his code. Just to share, without a code explanation (just go look above for JP Carlin's explanation), below is my version. You will see that I also have the departure_time parameter so that I will get distance and driving-minutes for a specific date-time. I also added a call to Log errors (to view under "View/Logs"):
Note: Google support told me that using your API-key for Google Maps (e.g. with "Directions API") with Google Sheets is not supported. The code below works, but is an unsupported work-around. As of 11/4/2018, Google has an internal ticket request to add support for Google Maps APIs within Google Sheets, but no timeline for adding that feature.
/********************************************************************************
* directionsAPI: get distance and time taking traffic into account, from Google Maps API
********************************************************************************/
function directionsAPI(origin, destination, customDate) {
var Your_API_KEY = "<put your APK key here between the quotes>";
var serviceUrl = "https://maps.googleapis.com/maps/api/directions/json?origin="+origin+"&destination="+destination+"&departure_time="+customDate.getTime()+"&mode="+Maps.DirectionFinder.Mode.DRIVING+"&key="+Your_API_KEY;
var options={
muteHttpExceptions:true,
contentType: "application/json",
};
var response=UrlFetchApp.fetch(serviceUrl, options);
if(response.getResponseCode() == 200) {
var directions = JSON.parse(response.getContentText());
if (directions !== null){
return directions;
}
}
Logger.log("Error: " + response.getResponseCode() + " From: " + origin + ", To: " + destination + ", customDate: " + customDate + ", customDate.getTime(): " + customDate.getTime() );
return false;
}
I have a Google Docs Spreadsheet that I'd like to use to update referenced cards in Trello. I've had some success with oauth and pulling data via their HTTP API, but am stuck with the following:
1) it seems Trello's code.js requires a window object, which the Google Doc script doesn't provide. So, I am stuck using their HTTP API.
2) authenticating via OAuth works, but only gives me read access. I cannot update cards with the token I am able to get.
function test() {
var oauthConfig = UrlFetchApp.addOAuthService("trello");
oauthConfig.setAccessTokenUrl("https://trello.com/1/OAuthGetAccessToken");
oauthConfig.setRequestTokenUrl("https://trello.com/1/OAuthGetRequestToken");
oauthConfig.setAuthorizationUrl("https://trello.com/1/authorize?key=" + consumerKey + "&name=trello&expiration=never&response_type=token&scope=read,write");
//oauthConfig.setAuthorizationUrl("https://trello.com/1/OAuthAuthorizeToken"); <-- this only gives read access. Cannot POST
oauthConfig.setConsumerKey(consumerKey);
oauthConfig.setConsumerSecret(consumerSecret);
var url = 'https://trello.com/1/cards/yOqEgvzb/actions/comments&text=Testing...';
var postOptions = {"method" : "post",
"oAuthServiceName": "trello",
"oAuthUseToken": "always"};
var response = UrlFetchApp.fetch(url, postOptions); // "Request failed for returned code 404. Truncated server response: Cannot POST"
Logger.log(response.getContentText());
}
I've found a number of related questions but no direct answers:
How to get a permanent user token for writes using the Trello API?
Trello API: vote on a card
Trello API: How to POST a Card from Google Apps Script (GAS)
Google apps script oauth connect doesn't work with trello
Many thanks ahead of time for any advice.
In order to get write access, you need to change the authorization url.
This example works for me
var oauthConfig = UrlFetchApp.addOAuthService("trello");
oauthConfig.setAccessTokenUrl("https://trello.com/1/OAuthGetAccessToken");
oauthConfig.setRequestTokenUrl("https://trello.com/1/OAuthGetRequestToken");
oauthConfig.setAuthorizationUrl("https://trello.com/1/OAuthAuthorizeToken?scope=read,write");
on 1) yes you cant use the library from server gas, its meant to be run from a browser.
on 2), Ive done it from GAS with write access without problems. You need to use the format:
https://api.trello.com/1/.../xxxx?key=yyyyyy&token=zzzzzzz&...
and when you get the token, you need to request permanent access (no expiration) and write access, as in:
https://trello.com/1/authorize?key="+key+"&name=xxxxxxx&expiration=never&response_type=token&scope=read,write"
As in:
function postNewCardCommentWorker(cardId, comment, key, token) {
var commentEncoded=encodeURIComponent(comment);
var url = "https://api.trello.com/1/cards/"+cardId+"/actions/comments?text="+commentEncoded+"&key="+key+"&token="+token;
var options =
{
"method" : "POST"
};
UrlFetchApp.fetch(url, options);
}