Building drive app from apps script - Whats wrong in the below code - google-apps-script

Below is the code taken from Arun Nagarajan's Example: I am tried the same code to check.. But Its not installing properly. (I removed my redirect url, client id and secret in the below). Please tell me what wrong in the below code.
var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth';
var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token';
var REDIRECT_URL = 'exec';
var tokenPropertyName = 'GOOGLE_OAUTH_TOKEN';
var CLIENT_ID = '';
var CLIENT_SECRET = '';
function doGet(e) {
var HTMLToOutput;
if(e.parameters.state){
var state = JSON.parse(e.parameters.state);
if(state.action === 'çreate'){
var meetingURL = createMeetingNotes();
HTMLToOutput = '<html><h1>Meeting notes document created!</h1> <click here to open</html>';
}
else if (state.ids){
var doc = DocsList.getFileById(state.ids[0]);
var url = doc.getContentAsString();
HTMLToOutput = '"<html><a href="' +url+'"</a></html>"';
}
else {
zipAndSend(state.ecportIds.Session.getEffectUser().getEmail());
HTMLToOutput = '"<html><h1>Email sent. Check your Inbox.</h1></html>"';
}
}
else if(e.parameters.code){
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>App is installed. You can close this window now or navigate to your </h1>Google Drive</html>';
}
else {
HTMLToOutput = '<html><h1>Install this App into your google drive </h1>Click here to start install</html>';
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
function getURLForAuthorization() {
return AUTHORIZE_URL + '?response_type=code&client_id=' + CLIENT_ID + '&redirect_uri=' + REDIRECT_URL + '&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.install+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email';
}
function getAndStoreAccessToken(code) {
var parameters = { method : 'post',
payload : 'client_id='+ CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&grant_type=authorization.code&redirect_uri=' + REDIRECT_URL};
var response = UrlFetchApp.fetch(TOKEN_URL.parameters).getContentText();
var tokenResponse = JSON.parse(response);
UserProperties.getProperty(tokenPropertyName, tokenResponse.access_token);
}
function getUrlFetchOptions() {
return {'contentType' : 'application/json',
'headers' : {'Authorization': 'Bearer ' + UserProperties.getProperty(tokenPropertyName),
'Accept' : 'application/json'}};
}
function IsTokenValid() {
return UserProperties.getProperty(tokenPropertyName);
}
The error showing is: Bad request:undefined
I think the error is inside the function called : getAndStoreAccessToken.
var parameters = { method : 'post',
payload : 'client_id='+ CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&grant_type=authorization.code&redirect_uri=' + REDIRECT_URL};
Please tell me the correct url format for payload.

The error seems in this line -
var response = UrlFetchApp.fetch(TOKEN_URL.parameters).getContentText();
I think you want TOKEN_URL , parameters (note the comma)

First, if you are trying to access Google Drive from within google apps script, what is the purpose of the authorization? Google drive is available w/o authorization. Are you trying to make your application utilize the gDrive of other users (or on behalf of other users)?
Second, instead of manually performing the authorization, which is very hard to troubleshoot, you can take advantage of Class OAuthConfig which simplifies the authorization/request process. The only disadvantage is that OAuthConfig currently uses OAuth1.0 (which is currently deprecated). Although it's particular use is Fusion Tables, and not drive, this library makes great use of OAuthConfig and .fetch and I have used it to model my own OAuth functions. My example below works great. The googleAuth() function sets up the authorization and then the rest of the application can make authorized requests using UrlFetchApp.fetch(url,options) while google does all the authorization stuff in the background.
function googleAuth(oAuthFields) {
var oAuthConfig = UrlFetchApp.addOAuthService(oAuthFields.service);
oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/"+
"OAuthGetRequestToken?scope=" + oAuthFields.scope);
oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig.setConsumerKey(oAuthFields.clientId);
oAuthConfig.setConsumerSecret(oAuthFields.clientSecret);
return {oAuthServiceName:oAuthFields.service, oAuthUseToken:"always"};
}
function fusionRequest(methodType, sql, oAuthFields, contentType) {
var fetchArgs = OAL.googleAuth(oAuthFields);
var fetchUrl = oAuthFields.queryUrl;
fetchArgs.method = methodType;
if( methodType == 'GET' ) {
fetchUrl += '?sql=' + sql;
fetchArgs.payload = null;
} else{
fetchArgs.payload = 'sql='+sql;
}
if(contentType != null) fetchArgs.contentType = contentType;
Logger.log(UrlFetchApp.getRequest(oAuthFields.queryUrl, fetchArgs));
var fetchResult = UrlFetchApp.fetch(oAuthFields.queryUrl, fetchArgs);
if( methodType == 'GET' ) return JSON.parse(fetchResult.getContentText());
else return fetchResult.getContentText();
}

Related

Twitter Media Upload OAuth1.0a auth error

I am trying to implement twitter media upload on google apps script via OAuth1.0a... as there has been no oauth2 for media uploads since 2 years. Following is the code. Still facing 401, 402, 403 , 400... all such return codes since last one week. Is this end point not working? anyone has any info? any ideas why its failing again and again.
using OAuth1 (https://github.com/googleworkspace/apps-script-oauth1/tree/3f3a6697d95a3ed9a91d09c65ffc34941136f587)
var url = 'https://upload.twitter.com/1.1/media/upload.json?media_category=tweet_image';
var baseUrl = 'https://upload.twitter.com/1.1/media/upload.json';
var params = {
'payload': {'media': imageBlob},
'method': 'POST',
'muteHttpExceptions' : true
};
var token = JSON.parse(PropertiesService.getUserProperties().getProperty("oauth1."+ account));
var oauth_token = token.oauth_token
var oauth_token_secret = token.oauth_token_secret
var oauth_consumer_key = PropertiesService.getUserProperties().getProperty("TWITTER_CONSUMER_KEY");
var oauth_consumer_secret = PropertiesService.getUserProperties().getProperty("TWITTER_CONSUMER_SECRET");
const method = params['method'] || 'post';
params['method'] = method;
const oauthParameters = {
oauth_version: "1.0",
oauth_token: oauth_token,
oauth_consumer_key: oauth_consumer_key,
oauth_signature_method: "HMAC-SHA1",
oauth_timestamp: (Math.floor((new Date()).getTime() / 1000)).toString(),
};
oauthParameters.oauth_nonce = oauthParameters.oauth_timestamp + Math.floor(Math.random() * 100000000);
const payload = params['payload'] || {};
const q = {"media_category": "tweet_image"} //parms from url
const queryKeys = Object.keys(oauthParameters).concat(Object.keys(payload)).concat(Object.keys(q)).sort();
const baseString = queryKeys.reduce(function(acc, key, idx) {
if (idx) acc += encodeURIComponent("&");
if (oauthParameters.hasOwnProperty(key))
acc += _encode(key + "=" + oauthParameters[key]);
else if (payload.hasOwnProperty(key))
acc += _encode(key + "=" + _encode(payload[key]));
return acc;
}, method.toUpperCase() + '&' + _encode(baseUrl) + '&');
oauthParameters.oauth_signature = Utilities.base64Encode(
Utilities.computeHmacSignature(
Utilities.MacAlgorithm.HMAC_SHA_1,
baseString, oauth_consumer_secret + '&' + oauth_token_secret
)
);
if (!params['headers']) params['headers'] = {};
params['headers']['authorization'] = "OAuth " + Object.keys(oauthParameters)
.sort().reduce(function(acc, key) {
acc.push(key + '="' + _encode(oauthParameters[key]) + '"');
return acc;
}, []).join(', ');
params['payload'] = Object.keys(payload).reduce(function(acc, key) {
acc.push(key + '=' + _encode(payload[key]));
return acc;
}, []).join('&');
console.log(params)
response = UrlFetchApp.fetch(url, params);
for info...other than this, I also tried this repo - https://github.com/airhadoken/twitter-lib
still facing similar issues.
EDIT: on postman it works.. somethings wrong with the code then.. :(

Workaround for 403 error when using URLFETCH with Google Apps Script (external website)

I've used sof for many years (I almost always found all my answers!) but I'm quite stuck for the current project so this is the first time I post here. :)
I want to get the product price from www.hermes.com using either the URL or the product ref.
ex: https://www.hermes.com/fr/fr/product/portefeuille-dogon-duo-H050896CK5E/
ref = H050896CK5E
The URLs and Refs are stored in a Spreadsheet.
As I called UrlFetchApp.fetch function in my script, I got 403 error.
If my understanding is correct, that means the hermes.com server is blocking me out.
I also tried =IMPORTXML and it says that the spreadsheet cannot access the URL.
Here are the workaround I found: use Google Custom Search API to search the URL and iterate until the result URL matches the query.
[Current issues]
If the object is out of stock or if the URL is not found, I am unable to get the price.
ex:
when I search https://www.hermes.com/it/it/product/cappello-alla-pescatora-eden-H221007NvA259/
it returns me nothing.
I know it can return
https://www.hermes.com/it/it/product/cappello-alla-pescatora-eden-H221007Nv0156/
but not the same colour (and sometimes the price does change between colours)
So my question was:
How would you do to bypass the 403 error ? (not bypass security of course but if you have any ideas how to retrieve the hermes.com prices, please let me know!)
I will paste the scripts below.
Thank you in advance.
→ What I used for hermes.com.
With the muteHttpExceptions = true, I get the captcha html
var response = UrlFetchApp.fetch("http://www.hermes.com/",
{
method: "get",
contentType: "application/json",
muteHttpExceptions: true,
});
→ Result of above (a captcha html, I think hermes.com knows I'm a bot)
<html><head><title>hermes.com</title><style>#cmsg{animation: A 1.5s;}#keyframes A{0%{opacity:0;}99%{opacity:0;}100%{opacity:1;}}</style></head><body style="margin:0"><p id="cmsg">Please enable JS and disable any ad blocker</p><script>var dd={'cid':'AHrlqAAAAAMAs2XwactPh88AInQWTw==','hsh':'2211F522B61E269B869FA6EAFFB5E1','t':'fe','s':13461,'host':'geo.captcha-delivery.com'}</script><script src="https://ct.captcha-delivery.com/c.js"></script></body></html>
→ What I'm using now (Google Custom Search)
for (var i = 0; i < 5; i++) {
var start = (i * 10) + 1;
var apiUrl = "https://www.googleapis.com/customsearch/v1?key=" + apiKey + "&cx=" + searchId + "&q=search " + query + "&start=" + start;
var apiOptions = {
method: 'get'
};
var responseApi = UrlFetchApp.fetch(apiUrl, apiOptions);
var responseJson = JSON.parse(responseApi.getContentText());
var checkDomain = "";
for (var v = 0; v < 10; v++) {
if (responseJson["items"] != null && responseJson["items"][v] != null) {
checkDomain = responseJson["items"][v]["link"];
if (checkDomain != null && checkDomain == query) {
productPrice = responseJson["items"][v]["pagemap"]["metatags"][0]["product:price:amount"];
currency = responseJson["items"][v]["pagemap"]["metatags"][0]["product:price:currency"];
break;
}
}
}
if (productPrice > 0) { break; }
}

Is there any way to see the names of a website's network requests with Google apps scripts?

So here's what I'm trying to do:
There's a website called Torah Anytime (https://www.torahanytime.com/) which publishes audio files (I guess you can call them podcasts, the website refers to them as shiurim, shiur being hebrew for song, or in this case, audio) on a daily basis. I would like to create a script that downloads the audio of specific speakers and then emails those files to me. The way I'm accomplishing this is with Google Apps Scripts. Torah Anytime allows you to follow specific speakers and to get email notifications when a speaker you're following puts out a new podcast. Here is the code that I have so far:
function main() {
var emails = getemails();
for (var i = 0; i < emails.length; i++) {
var email = emails[i].getMessages();
if (email[0].getFrom() == "TorahAnytime Following <following#torahanytime.com>"){
var title = getTitle(email);
var shiurID = getShiurID(email);
var downloadLink = "https://dl.torahanytime.com/audio/" + shiurID;
var shiur = downloadShiur(downloadLink);
shiur.setName(title);
var emailSent = emailShiur(shiur);
if (emailSent) {email[0].moveToTrash();
Logger.log("Email moved to Trash");}
}
}
}
function getemails() {
var label = GmailApp.getUserLabelByName("TA Speeches");
return label.getThreads();
}
function getTitle(email) {
body = email[0].getPlainBody();
var begIndex = body.indexOf("from") + 4;
var endIndex = body.indexOf("on ");
var title = body.substring(begIndex, endIndex).toLowerCase().replaceAll(" ", "-").replace(/(\r\n|\n|\r)/gm, "-");
begIndex = body.indexOf("called ") + 7;
endIndex = body.indexOf(" [");
title += "-" + body.substring(begIndex, endIndex).toLowerCase().replaceAll(" ", "-").replace(/(\r\n|\n|\r)/gm, "-") + ".mp3";
return title;
}
function getShiurID(email) {
body = email[0].getPlainBody();
var begIndex = body.indexOf("[") + 1;
var endIndex = body.indexOf("]");
var link = body.substring(begIndex, endIndex).replaceAll("?v", "?a");
console.log(link);
var mainLink = UrlFetchApp.fetch(link);
//here I somehow need to get the link being used to stream that particular audio file
}
function getIDName(email) {
body = email[0].getPlainBody();
var begIndex = body.indexOf("ID ") + 3;
var endIndex = body.indexOf(" and");
return body.substring(begIndex, endIndex);
}
function downloadShiur(downloadLink) {
var audio = UrlFetchApp.fetch(downloadLink);
return audio.getBlob().getAs('audio/mp3');
}
function emailShiur(shiur) {
const maxFileSize = 26214400;
if (shiur.getBytes().length <= maxFileSize) {
MailApp.sendEmail("[Email addressed removed]", "TA Shiur (File)", "Enjoy!", {
attachments: [shiur],
name: 'Automatic Emailer Script'
});
return true;
} else {
MailApp.sendEmail("[Email addressed removed]", "TA Shiur (File)", "Error, File too large to email", {
name: 'Automatic Emailer Script'
});
return false;
}
}
My issue is that the URL to download the file is not in the HTML, so I don't know how to get to it using GAS. If you use chrome's dev-tools, you can see the URL right there in the network tab Example of output I see that I want to get. Does anyone know of any way that I can get the information that I see in chrome's dev-tools network tab (the name's of the URLs being received) using GAS? Thank you!

How do I deploy a Google Script project so that all my Google Sheets can access it?

I have a simple VIN Decoder script that I built for my Vehicle DB Sheet. I want to allow other sheets to use the functions defined in the script without copying the code to the script containers for each spreadsheet. I guess I essentially want to have a private (to my account or domain) add-on. I have tried reading about how to deploy an add-on to Google Workplace but all the tutorials are either old or just provide sample code that doesn't answer how to do it. I am sure this is not a huge project to deploy this code as an add-on. Anyone?
Here is the code I am trying to deploy...
const nhtsaGateway = 'https://vpic.nhtsa.dot.gov/api/';
const nhtsaVINDecode = '/vehicles/DecodeVin/';
function decodeVIN(theVIN,theVariable) {
var response, jsonData, retValue, success;
success = false;
if (typeof(theVIN) === 'undefined') {
theVIN = 'WD4PF0CD3KP053982';
Logger.log('No VIN Submitted -- Assuming this is a test\nUsing Test VIN = [' + theVIN + ']');
}
response = UrlFetchApp.fetch(nhtsaGateway + nhtsaVINDecode + theVIN +'?format=JSON');
jsonData = JSON.parse(response.getContentText());
Logger.log(jsonData.Message);
if (typeof(theVariable) === 'undefined') {
Logger.log(jsonData);
return(jsonData);
}
jsonData.Results.every(function(element, index) {
Logger.log('<<<' + index + '>>>');
Logger.log(element.Value);
Logger.log(element.ValueId);
Logger.log(element.Variable);
Logger.log(element.VariableId);
if (element.Variable === theVariable) {
Logger.log('Found theVariable = ' + element.Variable);
retValue = element.Value;
success = true;
return (false);
} else {
return (true);
}
})
if (success) {
Logger.log(retValue);
return (retValue);
} else {
Logger.log('We should not be here --> ' + theVariable + ' <-- is not defined in the NHTSA response.');
}
}
function vinYear(theVIN) {return (decodeVIN(theVIN,'Model Year'))}
function vinMake (theVIN) {return (decodeVIN(theVIN,'Make'))}
function vinSeries (theVIN) {return (decodeVIN(theVIN,'Series'))}
function vinModel (theVIN) {return (decodeVIN(theVIN,'Model'))}
function vinGVWR (theVin) {return (decodeVIN('1FTYR2CM2KKB15306', 'Gross Vehicle Weight Rating From'))}
So the usage in the target spreadsheet would be this formula in a cell
=vinModel("1FTYR2CM2KKB15306")
you don't need to make an addon or an extension, just a library:
https://developers.google.com/apps-script/guides/libraries
Try adding the name of the library in front of your function after adding the library, e.g.
Mylibrary.decodeVIN()

How to get permissionId in Drive API v3?

I want to delete permission from the file.
In Drive API v2,
PermissionId permissionId = service.permissions().getIdForEmail(account).execute();
service.permissions().delete(fileId, permissionId.getId()).execute();
But According to document, permissions().getIdForEmail(String email) is removed and alternate is nothing.
https://developers.google.com/drive/v3/web/migration
I can't find how to get permissionId from specific Email address in drive API v3.
Do you have any idea?
I found a simple solution:
PermissionList permissions = service.permissions().list(sharedFolderId).setFields("nextPageToken, permissions(id,emailAddress)").execute();
for (Permission p : permissions.getPermissions()) {
if (p.getEmailAddress().equals(adresseEmail)) {
service.permissions().delete(sharedFolderId, p.getId()).execute();
}
}
Two years later, but your question was the first result I found when searching for a solution. I found a workaround and I hope this will help others with the same issue. This is what I did to get the permission id:
this.getPermissionId = function(emailAddress) {
return new Promise((resolve, reject) => {
const input = {
q: '"' + emailAddress + '" in writers or "' + emailAddress + '" in readers',
fields: 'files(permissions)',
pageSize: 1
};
const request = gapi.client.drive.files.list(input);
request.execute(result => {
if(result.error) {
reject(result.error);
} else if(result.files && result.files[0] && result.files[0].permissions && result.files[0].permissions[0]) {
const permissions = result.files[0].permissions;
let permissionId;
permissions.forEach(permission => {
if(permission.emailAddress == emailAddress) {
permissionId = permission.id;
}
});
if(permissionId) {
resolve(permissionId);
}
else {
reject('permissionIdUndefined');
}
}
});
})
};
a .NET version that solved my needs
public static string GetPermissionIdForEmail(DriveService service, string emailAddress)
{
string pageToken = null;
do
{
var request = service.Files.List();
request.Q = $"'{emailAddress}' in writers or '{emailAddress}' in readers or '{emailAddress}' in owners";
request.Spaces = "drive";
request.Fields = "nextPageToken, files(id, name, permissions)";
request.PageToken = pageToken;
var result = request.Execute();
foreach (var file in result.Files.Where(f => f.Permissions != null))
{
var permission = file.Permissions.SingleOrDefault(p => string.Equals(p.EmailAddress, emailAddress, StringComparison.InvariantCultureIgnoreCase));
if (permission != null)
return permission.Id;
}
pageToken = result.NextPageToken;
} while (pageToken != null);
return null;
}
I have done this code in .NET using C#.
I hope you have already created the drive service using user's access token.
After that this code can help you to get permission ID:
var permissionFile = driveService.About.Get();
permissionFile.Fields = "*";
var perm = permissionFile.Execute();
permissionId = perm.User.PermissionId;
The permissionId will give you the required ID.
I use UrlFetchApp with Google Apps Script to replace Drive API v2 and Advanced Drive Service (based on v2).
With a company domain service account, the section getService(userEmail) uses the library https://github.com/googleworkspace/apps-script-oauth2 to send the request on behalf of userEmail.
/**
* Get user permission Id.
*
* #param {String} userEmail - Email address for About query.
* https://developers.google.com/drive/api/v3/reference/about
*/
function TEST_getIdForEmailV3() { getIdForEmailV3('YourEmail#CompanyDomain.com') }
function getIdForEmailV3(userEmail) {
var service = getService(userEmail);
if (service.hasAccess()) {
var url = 'https://www.googleapis.com/drive/v3/about' + '?fields=user/permissionId';
var options = {
'method': 'get',
'contentType': 'application/json',
'headers': { Authorization: 'Bearer ' + service.getAccessToken() }
};
var response = UrlFetchApp.fetch(url, options);
var resultParsed = JSON.parse(response.getContentText());
return resultParsed.user.permissionId;
} else {
return 0;
};
}