Send HEAD request in Google App Script - javascript

I am developing a Google App Script to determine the size of a remote resource without downloading it. The code is as follows
function getRemoteFileSize()
{
var params = { "method" : "head" };
var resp = UrlFetchApp.fetch("https://www.google.com/images/srpr/logo11w.png", params);
Logger.log("Remote File Size: " + resp.getAllHeaders()["Content-Length"]);
}
However, Google App Script does not seem to support head requests and the code above cannot be executed.
What can be a viable alternative other than issuing a GET request ?
I am open to all suggestions including usage of a third-party service which has an API

You can try to override the method by setting the "headers" advanced parameter:
var params = {"method" : "GET", "headers": {"X-HTTP-Method-Override": "HEAD"}};
I've never used "HEAD", so I can't tell you for sure that this will work, but I have used the method override for "PATCH", and had that work.

I found that for my circumstance, I was able to use the Range header to request 0 bytes and then inspect the response headers which held the file size:
var params = {
method: "GET",
headers: {
Range: "bytes=0-0",
},
};
var response = UrlFetchApp.fetch(fileUrl,params);
var headers = response.getHeaders();
var fileSizeString = headers['Content-Range']
var fileSize = +headers['Content-Range'].split("/")[1];
The response headers had Content-Range=bytes 0-0/139046553 as a value that I could then use to convert to an integer (139046553) number of bytes.

Related

How to retrieve google apps script library version number and note programmatically?

I want to get the Google apps script library details such as version number and note programmatically in a spreadsheet cell.
You can use the projects.versions.list method.
You need to specify the script ID of the library. If you have the link of the library you can find the script id in the url or in the project settings.
If they are not automatically added, you might have to add the scopes manually in the appsscript.json file. E.g. add this inside the brackets:
"oauthScopes": [
"https://www.googleapis.com/auth/script.projects",
"https://www.googleapis.com/auth/script.projects.readonly",
"https://www.googleapis.com/auth/script.external_request"
],
Example:
function getVersion(){
const AccessToken = ScriptApp.getOAuthToken();
const scriptID = "scriptID" // put the script id of the library
const url = "https://script.googleapis.com/v1/projects/" + scriptID + "/versions";
options = {
"method" : "GET",
"muteHttpExceptions": true,
"headers": {
'Authorization': 'Bearer ' + AccessToken
}
};
response = UrlFetchApp.fetch(url,options);
response = JSON.parse(response);
allVersions = response.versions; //get all versions
highestVersion = allVersions[0].versionNumber;
console.log(response);
console.log(highestVersion);
}

Nodejs not applying script id value passed to it

I am writing a program in nodejs using the request-promise-native module to create a startup script,receive the script ID then create a server and apply the script ID, however even after gathering the script ID and passing it to the request to create the server it does not get applied. I have made sure that the value of the variable I am passing for the scriptID is in the correct form of data type but it still seems not to work for some reason
var scriptRequest = require('request-promise-native');
var serverRequest = require('request-promise-native');
var headers = {
'API-Key': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
};
var scriptOptions = {
url: "https://api.vultr.com/v1/startupscript/create",
method: "POST",
headers: headers,
form: {
name: "test script",
script: "#!/bin/bash \napt-get update (not the actual script, just shortened it since the script itself is long and as of now the vultr server doesn't even make it this far)"}
};
var serverOptions = {
url: 'https://api.vultr.com/v1/server/create',
method: 'POST',
headers: headers,
form: {
DCID: '6',
VPSPLANID: '202',
OSID: '215',
SCRIPTID: scriptResponse
}
};
var scriptResponse;
async function main(){
const response = await scriptRequest({...scriptOptions, json:true})
const json = response.SCRIPTID
scriptResponse = json
await serverRequest(serverOptions)
}
main()
as you can see I have had to make the scriptRequest variable global, if I try to set it in the main function it gives me an undefined error in the serverOptions block
EDIT: just to clarify, the script ID I want to apply to the scriptResponse variable is returned upon making the scriptRequest post request

Login to web-page using a google sheets script

I'm trying to log into a web page to retrieve some data
I am attempting to use the following code to log in to a web page. I used google chrome to determine what information was needed for the payload.
function login(strUsername, strPassword) {
var loginPage = UrlFetchApp.fetch("https://login.webpageurl.com");
var sessionDetails = loginPage.getContentText()
var testsearch = sessionDetails.indexOf('"authState":"');
var newStringRight = sessionDetails.substring(testsearch+13, testsearch+6000);
var authState = newStringRight.substring(0, newStringRight.indexOf('"'));
var payload =
{"authState":authState,
"username":strUsername,
"password":strPassword
};
//Logger.log(payload);
var options =
{
"method" : "post",
"payload" : payload,
"followRedirects" : true,
muteHttpExceptions: true
};
var login = UrlFetchApp.fetch("https://login.churchofjesuschrist.org/api/authenticate/credentials", options);
//var sessionDetails = login.getAllHeaders()['Set-Cookie'];
Logger.log(login);
}
I get the following response in my log file
{"status":400,"statusText":"authState, username and password are required"}
Any ideas on how I could submit the form properly?
Thanks
The reason your code does not work is because payload used for the UrlFetchApp.fetch() method is a JavaScript object and therefore it will be interpreted as form data.
So what you actually need to do is to make the POST request with a JSON payload.
Therefore, the thing that needs to be changed is the options you provided:
var options =
{
"method" : "post",
"payload" : JSON.stringify(payload),
"followRedirects" : true,
"muteHttpExceptions": true,
"contentType": "application/json"
};
Furthermore, I suggest you read more about the UrlFetchApp.fetch() method on the Class UrlFetchApp documentation.

How to get the file URL from file name in Google Sheets with correct Authorization via custom function/script

I would like to create a custom function that pulls a Drive URL from a file name in Google Sheets.
So, using the code below:
If I have a valid file name in cell A1
The function =getFile(A1) would return the URL
When I run my script from within the script editor, the return value works.
When I run the function getFile() from within my sheet, I get the error below.
My code:
function getFile(cell) {
var filename = encodeURI(cell);
var url = "https://www.googleapis.com/drive/v3/files?fields=files(id,name)&q=name+contains+'" + filename + "' and trashed=false";
var params = {
method: "GET",
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
var res = UrlFetchApp.fetch(url, params).getContentText();
var json = JSON.parse(res);
return res; // outputs response below
if(json){
var objFiles = json.files[0];
var fileID = objFiles.id
var resURL = "https://docs.google.com/spreadsheets/d/" + fileID;
Logger.log(resURL);
//return resURL; // only works when run within script editor
}
}
Error:
"{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}
"
I'm guessing something's wrong with my Auth token. Can someone direct me to resolving this? Thanks in advance!
Custom functions runs as if run by a anonymous animal(user). ScriptApp.getOAuthToken will return a anonymous token without the required scopes. What you're attempting is not possible, unless the file in question is public.
References:
Custom functions permissions
Custom functions access services
This may be a solution for some needs.
My particular need was: loop through a column of file names and pull the Google Docs URL at a set interval. The code below just loops through filenames in "Column A" of "My Sheet" and returns the value into the adjacent cell of "Column B" (starting at row 2 because I had column headers). I'm not concerned about security because I'm only referencing internal organization files.
To get the code below to work you need to:
Google Sheet Doc Nav > Tools > Script Editor
Create a .gs file & input code below (Referencing The Respective Sheet
Within Script Editor > Edit > Current Project’s Triggers > Name Your Project
Within Script Editor > Edit > Current Project’s Triggers > Click on modal link “No triggers set up. Click here to add one now” > set your time-based trigger (reference replaceFileColumn in the select field within that modal)
My mistake was: thinking that I needed to use a custom function in each cell to do so. (I still don't fully understand the auth reasons why this wouldn't work, so if anyone could explain in lay-man's terms that would be fabulous; my solution is just a workaround for expediency's sake).
In my spreadsheet I have a time-driven trigger calling replaceFileColumn()
Hope this helps someone!
function getMyFile(cell) {
var filename = encodeURI(cell);
var files = DriveApp.getFilesByName(cell);
while (files.hasNext()) {
var file = files.next();
if(file){
var fileValue = file.getUrl();
return(fileValue);
};
};
}
function replaceFileColumn() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('My Sheet');
var range = spreadsheet.getRange("A2:A");
var range_update = spreadsheet.getRange("B2:B");
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
var fileName = values[i];
var getFileUrl = getMyFile(fileName);
values[i][0] = getFileUrl;
}
range_update.setValues(values);
}
#I'-'I's answer is correct. Although I'm not sure whether this is what you want, how about this workaround? I have also experienced the same issue. At that time, I had used the following workaround.
Set and get access token using PropertiesService.
The flow is as follows.
Flow:
Set access token every 1 hour by the time-driven trigger.
By this, the access token is updated every 1 hour. Because the expiration time of access token is 1 hour.
When the custom function is run, it gets the access token using PropertiesService.
By this, the access token can be used.
Modified script:
Please install this function as the time-driven trigger. Of course, you can run manually this function.
function setAccessToken() {
PropertiesService.getScriptProperties().setProperty("accessToken", ScriptApp.getOAuthToken());
}
In your script, please modify as follows.
From:
var params = {
method: "GET",
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
To:
var params = {
method: "GET",
headers: {"Authorization": "Bearer " + PropertiesService.getScriptProperties().getProperty("accessToken")},
muteHttpExceptions: true
};
Note:
In this case, the owner of access token is the owner of project.
I think that this can be also used by CacheService.
Reference:
PropertiesService

Lync UCWA - Create application gives a HTTP 409: Conflict error

I have been trying for the past couple of days to develop an application for our Lync service at work using the UCWA API from Microsoft (a REST API). To get an application working: I first have to submit it to the API using a POST request to a certain URL. First, I have to authenticate with the server, and I do that by posting a username and a password credential to the API. I then get an access token back which I can use to make further requests to the API by posting the token inside the header of each request. I have been able to get an access token working, but when I try to register the application by posting a HTTP request to https://lyncextws.company.com/ucwa/oauth/v1/applications: Things will start to go wrong.
All this is done through one JavaScript file working with iframes to bypass the Same-origin policy.
This is what my code currently looks like:
<!DOCTYPE html>
<html lang="no">
<head>
<meta charset="UTF-8" />
<title>PresInfoDisp</title>
</head>
<body>
<iframe src="https://lyncextws.company.com/Autodiscover/XFrame/XFrame.html" id="xFrame" style="display: none;"></iframe>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
var access_token;
var stage = 0;
// CONNECT AND AUTHENTICATE WITH LYNC UCWA SERVICE
function connectAndAuthenticate() {
stage = 1;
var request = {
accepts: 'application/json',
type: 'POST',
url: 'https://lyncextws.company.com/WebTicket/oauthtoken',
data: 'grant_type=password&username=alexander#domain.company.com&password=somePassword'
};
document.getElementById('xFrame').contentWindow.postMessage(JSON.stringify(request), 'https://lyncextws.company.com/WebTicket/oauthtoken');
}
// REQUEST A USER RESOURCE
function getUserResourceAuthRequest() {
stage = 0;
var request = {
accepts: 'application/json',
type: 'GET',
url: 'https://lyncextws.company.com/Autodiscover/AutodiscoverService.svc/root/oauth/user?originalDomain=company.com'
};
document.getElementById('xFrame').contentWindow.postMessage(JSON.stringify(request), 'https://lyncextws.company.com/Autodiscover/AutodiscoverService.svc/root/oauth/user?originalDomain=company.com');
}
function getUserResource() {
stage = 2;
var request = {
accepts: 'application/json',
type: 'GET',
url: 'https://lyncextws.company.com/Autodiscover/AutodiscoverService.svc/root/oauth/user?originalDomain=company.com',
headers: {Authorization: "Bearer "+access_token}
};
document.getElementById('xFrame').contentWindow.postMessage(JSON.stringify(request), 'https://lyncextws.company.com/Autodiscover/AutodiscoverService.svc/root/oauth/user?originalDomain=company.com');
}
// REGISTER APPLICATION RESOURCE
function registerApplication() {
stage = 3;
var request = {
accepts: 'application/json',
type: 'POST',
url: 'https://lyncextws.company.com/ucwa/oauth/v1/applications',
headers: {Authorization: "Bearer "+access_token},
data: {'userAgent': 'InfoDisp1', 'endpointId' : '2d9dc28d-4673-4035-825c-feb64be28e4e', 'culture': 'en-US'}
};
document.getElementById('xFrame').contentWindow.postMessage(JSON.stringify(request), 'https://lyncextws.company.com/ucwa/oauth/v1/applications');
}
// GRAB A LIST OF CONTACTS
function listContacts() {
stage = 4;
var request = {
accepts: 'application/json',
type: 'GET',
url: 'https://lyncextws.company.com/ucwa/oauth/v1/applications',
headers: {Authorization: "Bearer "+access_token}
};
document.getElementById('xFrame').contentWindow.postMessage(JSON.stringify(request), 'https://lyncextws.company.com/ucwa/v1/applications');
}
this.receiveMessage = function(message) {
switch(stage) {
case 1:
var beforeReplace = message.data.replace("/\\/g", "");
var json = jQuery.parseJSON(beforeReplace);
var json2 = jQuery.parseJSON(json.responseText);
access_token = json2.access_token;
console.log(json2.access_token);
console.log(message);
getUserResource();
break;
case 0:
console.log(message);
connectAndAuthenticate();
break;
case 2:
var beforeReplace = message.data.replace("/\\/g", "");
var json = jQuery.parseJSON(beforeReplace);
var json2 = jQuery.parseJSON(json.responseText);
console.log(json2._links.applications.href);
window.setTimeout(function(){registerApplication()}, 5000);
break;
case 3:
console.log(message);
break;
case 4:
break;
}
};
window.addEventListener('message', this.receiveMessage, false);
$(window).load(function() {
getUserResourceAuthRequest();
//console.log(access_token);
});
</script>
</body>
</html>
When I run this code: The last ajax query returns the error 409: Conflict, when it should be returning 201: Created
This is what my browser (Google Chrome) outputs:
The 401: Unauthorized error is supposed to happen, but the 409 Conflict, should not happen. So here is my question:
Can anyone spot why I get this 409 error instead of the 201 I should be getting?
The example code from Microsoft seems to work fine, but I want to avoid using that as it will take me a very long time to familiarize myself with it.
If there is missing data you need to spot the issue: Let me know in the comments, and i'll provide it!
If you replace
data: {'userAgent': 'InfoDisp1', 'endpointId' : '2d9dc28d-4673-4035-825c-feb64be28e4e', 'culture': 'en-US'}
with a string of that data instead I.E.
data: "{'userAgent': 'InfoDisp1', 'endpointId' : '2d9dc28d-4673-4035-825c-feb64be28e4e', 'culture': 'en-US'}"
it seems that data expects a string and in your example you are passing it a JSON object. Doing that makes your example work for me.
The problem seems to be with your static endpointId.
In their original helper libraries they have a method called generateUUID() which is in GeneralHelper. The best idea would be to use that method, however, if you feel like creating ayour own, go for it. The main point is that each of your application must have different endpointId.
Are you omitting the autodiscovery process for brevity only, or are you really skipping the autodiscovery in your code and assuming the URI where to post the 'create application'?
It seems more the second case to me, and this isn't right:
the URI where to create the application needs to be retrieved from the response of the user resource request (within getUserResource in the code you posted).
You have a link called applications there; its value contains the correct URI where to create the application.
http://ucwa.lync.com/documentation/KeyTasks-CreateApplication
P.S. I post here as well about the endpointId, seen I can't comment above
It is allowed to use the same application's endpointId on different clients. It is absolutely not to be assumed anyway that applications on different clients using the same endpointId will result in the same base application resource URI
I was getting this same problem using curl to experiment with this API, and failed at this same point until I figured out that in that case I needed to set content-type header to json:
curl -v --data "{'userAgent': 'test', 'endpointId': '54321', 'culture':'en-US', 'instanceID':'testinstance'}" --header 'Content-Type: application/json' --header 'Authorization: Bearer cwt=AAEBHAEFAAAAAAA..' 'https://lyncserver.com/ucwa/oauth/v1/applications'
That did the trick!

Categories