I'm using apps script to create an interaction between a spreadsheet and a website using its API.
I must first authenticate with Oauth 2.0, here is the documentation:
Authentication - OAuth 2.0
Authentication is required before any other API call.
POST
/oauth/token
Body :
grant_type=client_credentials
Header :
Champ
Type
Description
Authorization
String
Autorization method "Basic" followed by your public_key and private_key in your settings > developer > API combined into a string "public_key:private_key" and encoded using Base64
Content-Type
String
Must be : application/x-www-form-urlencoded
Header (example) :
```
Authorization: Basic dGVzdGNsaWVudDp0ZXN0cGFzcw==
Content-Type: application/x-www-form-urlencoded
```
I'm completely new to API requests, and I don't understand how to format the request, I found this post:
Send POST request in Google Apps Script with Headers and Body
And as I understand, application/x-www-form-urlencoded is by default with UrlFetchApp, so I tried:
function authentication() {
const ENDPOINT = 'api url'
const CLIENT_ID = 'public key'
const CLIENT_SECRET = 'secret key'
const TOKEN_URL = ENDPOINT + '/oauth/token'
const HEADERS = {
'Authorization' : 'Basic ' + CLIENT_ID + ':' + CLIENT_SECRET
}
const BODY = 'grant_type=client_credentials'
const OPTIONS = {
'method' : 'post',
'headers' : HEADERS
}
let response = UrlFetchApp.fetch(TOKEN_URL + "?" + BODY,OPTIONS)
Logger.log(response.getContentText());
}
But I get a 404 error and know an unknown error.
I guess I'm doing something wrong at least with the body but I don't understand how to format properly the request.
Can someone help me?
Thanks
I would suggest that you refer to this documentation, Apps script has a library that allows you to use Oauth2 you can find it here.
Here is an example:
function accessProtectedResource(url, method_opt, headers_opt) {
var service = getOAuthService();
var maybeAuthorized = service.hasAccess();
if (maybeAuthorized) {
var accessToken = service.getAccessToken();
var method = method_opt || 'get';
var headers = headers_opt || {};
headers['Authorization'] =
Utilities.formatString('Bearer %s', accessToken);
var resp = UrlFetchApp.fetch(url, {
'headers': headers,
'method' : method,
'muteHttpExceptions': true, // Prevents thrown HTTP exceptions.
});
var code = resp.getResponseCode();
if (code >= 200 && code < 300) {
return resp.getContentText("utf-8"); // Success
} else if (code == 401 || code == 403) {
// Not fully authorized for this action.
maybeAuthorized = false;
} else {
// Handle other response codes by logging them and throwing an
// exception.
console.error("Backend server error (%s): %s", code.toString(),
resp.getContentText("utf-8"));
throw ("Backend server error: " + code);
}
}
if (!maybeAuthorized) {
// Invoke the authorization flow using the default authorization
// prompt card.
CardService.newAuthorizationException()
.setAuthorizationUrl(service.getAuthorizationUrl())
.setResourceDisplayName("Display name to show to the user")
.throwException();
}
}
function getOAuthService() {
return OAuth2.createService('SERVICE_NAME')
.setAuthorizationBaseUrl('SERVICE_AUTH_URL')
.setTokenUrl('SERVICE_AUTH_TOKEN_URL')
.setClientId('CLIENT_ID')
.setClientSecret('CLIENT_SECRET')
.setScope('SERVICE_SCOPE_REQUESTS')
.setCallbackFunction('authCallback')
.setCache(CacheService.getUserCache())
.setPropertyStore(PropertiesService.getUserProperties());
}
function authCallback(callbackRequest) {
var authorized = getOAuthService().handleCallback(callbackRequest);
if (authorized) {
return HtmlService.createHtmlOutput(
'Success! <script>setTimeout(function() { top.window.close() }, 1);</script>');
} else {
return HtmlService.createHtmlOutput('Denied');
}
}
/**
* Unauthorizes the non-Google service. This is useful for OAuth
* development/testing. Run this method (Run > resetOAuth in the script
* editor) to reset OAuth to re-prompt the user for OAuth.
*/
function resetOAuth() {
getOAuthService().reset();
}
Hello Stackoverfllow,
The context,
> nodejs: v16.17.0
> NPM: 8.15.0
> OS: Ubuntu 20.04.5 LTS
> Axiom:0.27.2
I Developing an Application using Twitter API. I use Account Activity API to get a webhook event from it. Then I will have a media endpoint from which I can download an image.
The most important thing is that I'm trying to /GET da images from the Twitter Media API. The response ( from Twitter ) gives me the image encoded under res.data
data: '����\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00��\x00C\x00\x06\x04\x05\x06\x05\x04\x06\x06\x05\x06\x07\x07\x06\b\n' +
'\x10\n' +
'\n' +
'\t\t\n' +
"\x14\x0E\x0F\f\x10\x17\x14\x18\x18\x17\x14\x16\x16\x1A\x1D%\x1F\x1A\x1B#\x1C\x16\x16 , #&')*)\x19\x1F-0-(0%()(��\x00C\x01\x07\x07\x07\n" +
'\b\n' +
'\x13\n' +
'\n' +
'\x13(\x1A\x16\x1A((((((((((((((((((((((((((((((((((((((((((((((((((��\x00\x11\b\x06#\x03�\x03\x01"\x00\x02\x11\x01\x03\x11\x01��\x00\x1B\x00\x00\x02\x03\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07��\x00\x19\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05��\x00\f\x03\x01\x00\x02\x10\x03\x10\x00\x00\x01�x\x7Fe��\x00������P\x14\x00�\x00\x02�%\n' +
'Q�_K:�*�]�R�%!�(�I�%\x19\x12#�RI�/O\x06�l4#BMP\f\x06#\x0
(I dont know what type of encoding ( I received ) and their doc does not mention it ). its been days now, and I had try to encode with Binary, base64 and UTC-8, which failed in all these attempted
I visited a lot of tutorials, but I could not see any solution that worked for me. , will link a few 1 , 2 , 3
Moreover, I could see the image from the postman client, but I didn't find any way to download this image as a jpg in the local drive.
Below is my snippet for the request
const nconf = require('nconf')
nconf.file({ file: 'config.json' }).env()
const axios = require('axios');
const crypto = require('crypto');
const oauth1a = require('oauth-1.0a');
const CONSUMERKEY = nconf.get('TWITTER_CONSUMER_KEY');
const CONSUMERSECRET = nconf.get('TWITTER_CONSUMER_SECRET');
const TOKENKEY = nconf.get('TWITTER_ACCESS_TOKEN');
const TOKENSECRET = nconf.get('TWITTER_ACCESS_TOKEN_SECRET');
function getAuthHeaderForRequest(request) {
const oauth = oauth1a({
consumer: { key: CONSUMERKEY, secret: CONSUMERSECRET },
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto
.createHmac('sha1', key)
.update(base_string)
.digest('base64')
},
})
const authorization = oauth.authorize(request, {
key: TOKENKEY,
secret: TOKENSECRET,
});
return oauth.toHeader(authorization);
}
const request = {
url: 'https://ton.twitter.com/1.1/ton/data/dm/1564581847908098052/1564581845014126592/56XDEw1t.jpg',
method: 'GET',
};
const authHeader = getAuthHeaderForRequest(request);
/**
* Media in direct messages must be retrieved via an authenticated app-user GET request.
* It is advised that applications store user's access tokens to use for direct message media retrieval.
*
* #param {string} url - the url for the media i wanna retrive
* #return {Promise}
*/
function retrieveMediaApi (url){
// message_request_options.url = 'https://ton.twitter.com/1.1/ton/data/dm/1564581847908098052/1564581845014126592/56XDEw1t.jpg' //url.toString(); //should be the media url
return axios.get(
request.url,
{ headers: authHeader,
AcceptEncoding: 'gzip, deflate, br' });
}
module.exports= retrieveMediaApi;
Here how I try to save the date to the local drive
retrieveMediaApi(x.message_create.message_data.attachment.media.media_url_https).then(async newUrl => {
//newevent.message.payload[0].url = newUrl;
//await decode(newUrl, { fname: 'example', ext: 'jpg' });
console.log(newUrl);
console.log('start')
require('fs').writeFile('outout.jpg',newUrl.data, // to use with writeFile
{ encoding: "binary" }, // to use with writeFile ***************WORKING
(err) => {
if (err) {
console.log("An error ocurred while writing the media file.");
return console.log(err);
}
}
);
console.log('!')
}).catch(x=>{
logger('🚀 ~ file: convert_formate.js ~ line 56 ~ retrieveMediaApi ~ x ', x);
I'm trying to use the Nest Device API, however I am unable to get an Access Token as it throws
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
I've downloaded my credentials.json from GCP and I open a new tab with the AUTH_URL below:
const credentials = require('../../../credentials.json');
const PROJECT_ID = <NEST DEVICE API PROJECT ID>;
const REDIRECT_URL = 'http://localhost:3000/connect/callback';
const AUTH_URL =
`https://nestservices.google.com/partnerconnections/${PROJECT_ID}/auth?` +
`redirect_uri=${REDIRECT_URL}&access_type=offline&prompt=consent&client_id=${credentials.web.client_id}&` +
`response_type=code&scope=https://www.googleapis.com/auth/sdm.service`;
From that, I have my callback page that gets the authcode.
const credentials = require('../../../credentials.json');
const { code } = router.query; // Auth Code
try {
const url =
`https://www.googleapis.com/oauth2/v4/token?client_id=${credentials.web.client_id}` +
`&client_secret=${credentials.web.client_secret}&code=${code}&grant_type=authorization_code&` +
`redirect_uri=${REDIRECT_URL}`;
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
});
console.log(response);
} catch (e) {
console.error(e);
}
This is where the API returns the above error. I have also tried taking this URL and doing curl -L -X POST <url> however I get exactly the same results.
Any ideas?
I am encrypting an object in the frontend and sending an HTTP POST request with the encrypted data. In the backend I am trying to decrypt this object but it fails.
The tests are passing but when integrated to the actual project this decrypt method fails with error:
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt at Error (native) at Decipher.final (crypto.js:158:26) at Object.exports.decrypt.error [as decrypt]
Here is the relevant code:
export const decrypt = text => {
if (!text)
throw Error('Decrypt: Text may not be blank');
const decipher = crypto.createDecipher(encryptAlgorithm,
encryptionKey)
let decrypted = decipher.update(text, textEncodingHex,
textEncodingUtf8)
decrypted += decipher.final(textEncodingUtf8)
return decrypted
}
And this is how I am using it
authSignInWeb(): any {
return async (request: any, reply: any) => {
try {
let decrytedRequestPayload = request.payload;
if (process.env.REACT_APP_ENCRYPT) {
decrytedRequestPayload = JSON.parse(cryptoHelper.decrypt(request.payload))
}
...
} catch (error) {
reply(error);
...
}
};
};
After going through the Documentation and other online resources I managed to solve this problem. What made this fail is that HapiJs takes the incoming payload, parses it, and pass it down to the authSignInWeb() as a Javascript object with the actual payload as the key in that object it makes on my behalf.
To solve this I had to, in the frontend, encrypt the data, manually create an object and assign the encrypted information. And then in the backend access the payload's key of the object.
In code:
The frontend is like so:
let encryptedData = {};
if (process.env.REACT_APP_ENCRYPT) {
encryptedData.data = Crypt.encrypt(JSON.stringify(requestBody))
}
and then in the backend (inside authSignInWeb()) do:
let userAuthData = request.payload;
if (process.env.REACT_APP_ENCRYPT) {
userAuthData = JSON.parse(cryptoHelper.decrypt(userAuthData.data))
}
I want to make a HTTPS request to an external link through Node JS. On my first call, I need to fetch user id by looping through several users. On my second call, I need to input that user id in the URL link and fetch user properties. Keep repeating this process till I go through all users. The end goal is to store data of every user in a JSON format. There is no front-end involved. Any direction/advice is much appreciated.
I can't share the actual link due to api keys. But here is the hypothetical scenario. I only show 2 users here. I have about 10,000 users in my actual data set.
Link 1
https://www.google.com/all_users
JSON Output
{
"name": "joe",
"uri": "/id/UserObject/User/1234-1234",
},
{
"name": "matt",
"uri": "/id/UserObject/User/5678-5678",
}
Link 2
https://www.google.com//id/UserObject/User/1234-1234
JSON Output
{
"name": "joe",
"uri": "/id/UserObject/User/1234-1234",
"Property Values": {
"height": "2",
"location": "canada"
},
"Other Values": {
"work": "google",
"occupation": "developer"
}
}
Nested JSON
{
"PropertySetClassChildrenResponse": {
"PropertySetClassChildren": {
"PropertySetInstances": {
"totalCount": "1",
"Elements": [
{
"name": "SystemObject",
"uri": "/type/PropertySetClasses/SystemObject"
}
]
}
}
}
}
Not tested, but this should point you in the right direction. It uses Promises and assumes that run in an ES6 environment:
const rp = require('request-promise');
const Promise = require('bluebird');
fetchAllUsers()
.then(extractUserUris)
.then(extractUserIds)
.then(buildUserDetailRequests)
.then(Promise.all) // run all the user detail requests in parallel
.then(allUserData => {
// allUserData is an array of all users' data
});
function fetchAllUsers() {
return rp('https://api.whatever.com/all_users');
}
function extractUserUris(users) {
return users.map(user => user.uri);
}
function extractUserIds(userUris) {
return userUris.map(userUri => userUri.split('/').pop());
}
function buildUserDetailRequests(userIds) {
return userIds.map(userId => rp("https://api.whatever.com/user/" + userId));
}
I'd suggest using the request package to make your HTTP requests easier.
> npm install request
Then you would obtain a list of all users with something like this:
var request = require('request');
request.get({url: "https://example.org/all_users"}, handleUsersResponse);
You'd handle the request response like this:
function(err, response, body) {
if (!err && response.statusCode == 200) {
// parse json (assuming array of users)
var users = JSON.parse(body);
// iterate through each user and obtain user info
for(var i = 0; i < users.length; i++) {
var userUri = users[i].uri;
obtainUserInfo(userUri)
}
}
}
obtainUserInfo function would be similar to the above code.
One important thing to keep in mind is that since the HTTP requests are being made asynchronously, when you make the requests in a loop, the next iteration of the loop does not wait until the work is finished before moving to the next iteration and starting the next request. So in effect, your loop would start all the HTTP requests nearly in parallel. This can easily overwhelm both your client and the server. One way to get around this is to use a worker queue to enqueue the work and ensure that only a maximum number of HTTP requests are being executed at any given time.
You don't want to do synchronous calls, it defeats the purpose of using Node. So by the Node powers invested in me by the State of Texas I hereby cast that synchronous way I thinking out of you!
Just kidding :), but let's do this the Node way.
Install these two libraries:
sudo npm install Promise
sudo npm install request
And set your code to look like:
var Promise = require('promise');
var request = require('request');
//Get your user data, and print the data in JSON:
getUserData()
.then(function(userData) {
console.log(JSON.stringify(userData));
}).catch(function(err) {
console.log('Error: ' +err);
});
/**
* Prepares an Object containing data for all users.
* #return Promise - Contains object with all user data.
*/
function getUserData() {
return new Promise(function(fulfill, reject) {
// Make the first request to get the user IDs:
var url1 = 'https://www.google.com/all_users';
get(url1)
.then(function(res) {
res = JSON.parse(res);
// Loop through the object to get what you need:
// Set a counter though so we know once we are done.
var counter = 0;
for (x=0; x<res.users.length; x++) {
var url2 = 'https://www.google.com//id/UserObject/User/';
url2 = url2 + res.users.id; //Wherever the individual ID is stored.
var returnDataArr = [];
get(url2)
.then(function(res2) {
// Get what you need from the response from the 2nd URL.
returnDataArr.push(res2);
counter++;
if (counter === res.users.length) {
fulfill({data: returnDataArr}); //Return/Fulfill an object containing an array of the user data.
}
}).catch(function(err) {
// Catch any errors from the 2nd HTTPS request:
reject('Error: ' +err);
});
}).catch(function(err) {
// Catch any errors from the 1st HTTPS request:
reject('Error: ' +err);
});
}
/**
* Your HTTPS GET Request Function
* #param url - The url to GET
* #return Promise - Promise containing the JSON response.
*/
function get(url) {
return new Promise(function(fulfill, reject) {
var options = {
url: url,
headers: {
'Header Name': 'Header Value',
'Accept': 'application/json',
'Content-Type': 'application/json'
};
request(options, function(err, res, body) {
if (err) {
reject(err);
} else {
fulfill(body);
}
});
});
}
So what this Promise does, is that it returns the value once we actually have it. In the code above, we are first getting that list of users, and then as we parse through it, we are making a new asynchronous HTTP request to get the additional data on it. Once we get the user data, we push it to an array.
Finally, once our counter hits its endpoint, we know that we have gotten all the user data, and so we call fulfill which essentially means return, and it returns an object containing an array of the user data.
Let me know if this makes sense.
The answers above helped me go further with my solution and get the desired outcome. However, I spent a lot of time trying to understand node, promises in node, making an API call, etc. Hopefully, this will help to a beginner level node developer.
NODE
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
If you are a JavaScript developer, you would prefer to use Node as you wouldn't have to spend time learning a new language like Java or Python.
GOAL
Make a HTTPS call to an external link to fetch all server URIs. Pass in the URI as a param to create a second link to fetch all server properties. Loop through to all server uris and properties. Refer the original post on the top for the data structure. The external link also required basic auth and headers.
CODE
Install NPM modules request (https call), bluebird (promises) and lodash(utility) and express(node framework).
/
********************** MODULES/DEPENDENCIES **********************/
var express = require('express');
var request = require('request');
var Promise = require('bluebird');
var _ = require("lodash");
/********************** INITIATE APP **********************/
var app = express();
console.log("Starting node server...");
/**
* Your HTTPS GET Request Function
* #param url - The url to GET
* #return Promise - Promise containing the JSON response.
*/
function get(url) {
return new Promise(function(resolve, reject) {
// var auth = "Basic " + new Buffer(username + ':' + password).toString("base64");
var options = {
url: url,
headers: {
// 'Authorization': auth,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
console.log("Calling GET: ", url);
if ('development' == app.get('env')) {
console.log("Rejecting node tls");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
request(options, function(error, response, body) {
if (error) {
reject(error);
} else {
// console.log("THIS IS BODY: ", body);
resolve(body);
}
});
});
}
/********************** GET DATA FUNCTION **********************/
function getServerData() {
/********************** URI VARIABLES **********************/
var username = 'username',
password = 'password',
role = 'Read-Only',
url_host = 'https://link.com:10843';
/********************** URL 1 **********************/
var url1 = url_host + '/type/PropertySetClasses/SystemObject/Server/?maxResults=1000&username=' + username + '&password=' + password + '&role=' + role;
console.log("Getting server data...", url1);
/********************** GET REQUEST 1 **********************/
return get(url1)
.then(function(res) {
console.log("Got response!");
res = JSON.parse(res);
res = res.PropertySetClassChildrenResponse.PropertySetClassChildren.PropertySetInstances.Elements;
// console.log("THIS IS RES: ", res);
/********************** FETCH URI FROM RES NESTED OBJECT **********************/
var server_ids = _.map(res, function(server) {
return server.uri;
});
console.log("Calling server urls", server_ids);
// Loop through the object to get what you need:
// Set a counter though so we know once we are done.
return Promise.map(server_ids, function (id) {
var url2 = url_host + id + '?username=' + username + '&password=' + password + '&role=' + role;
console.log("Calling URL", url2);
return get(url2)
.then(function(res2) {
res2 = JSON.parse(res2);
var elements = res2.PropertySetInstanceResponse.PropertySetInstance.PropertyValues.Elements;
console.log("Got second response", res2, elements);
return elements;
});
})
.then(function (allUrls) {
console.log("Got all URLS", allUrls);
return allUrls;
});
})
.catch(function(err) {
console.error(err);
throw err;
});
};
app.listen(8080, function() {
console.log("Server listening and booted on: " + 8080);
app.get("/serverInfo", function (req, res) {
console.log("Calling server info");
return getServerData()
.then(function(userData) {
var userData = JSON.stringify(userData, null, "\t");
console.log("This is USERDATA Data: ", userData);
res.send(userData);
})
.catch(function(err) {
console.error(err);
res.send({
__error: err,
message: err.message
});
});
});
});