I am attempting to authenticate with a service account to work on behalf of a user account on the domain. I have delegated admin access and added to the GSuite console. I can get an access token with the below but the making batch requests to copy drive files returns "code: 404, message: 'File not found:". The below code is writted in Google Apps Script. Am I missing something form the process to creating and authenticating the service account?
var CREDENTIALS = {
private_key: "-----BEGIN PRIVATE KEY----- XXXXXXX \n-----END PRIVATE KEY-----\n",
client_email: "XXXXXX#fXXXXXX.iam.gserviceaccount.com",
client_id: "1XXXXXXXXXXXXXXXX",
user_email: "XXXXX#XXXX.XXX.XXX",
scopes: ["https://www.googleapis.com/auth/drive","https://www.googleapis.com/auth/spreadsheets","https://www.googleapis.com/auth/userinfo.email","https://www.googleapis.com/auth/script.external_request"]
};
function oAuthToken(){
var url = "https://www.googleapis.com/oauth2/v3/token";
var header = {
alg: "RS256",
typ: "JWT",
};
var now = Math.floor(Date.now() / 1000);
var claim = {
iss: CREDENTIALS.client_id,
sub: CREDENTIALS.user_email,
scope: CREDENTIALS.scopes.join(" "),
aud: url,
exp: (now + 3600).toString(),
iat: now.toString(),
};
var signature = Utilities.base64Encode(JSON.stringify(header)) + "." + Utilities.base64Encode(JSON.stringify(claim));
var jwt = signature + "." + Utilities.base64Encode(Utilities.computeRsaSha256Signature(signature, CREDENTIALS.private_key));
var params = {
method: "post",
payload: {
assertion: jwt,
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
},
};
var res = UrlFetchApp.fetch(url, params).getContentText();
return JSON.parse(res)
}
The batch process is a bit rough but this is the gist of it.
var request={
batchPath:
requests:[]
}
var backoff =0
function batch(request) {
var oAuth=oAuthToken().access_token
var url ='https://www.googleapis.com/'+request.batchPath
var body =request.requests
if(body.length<1){
return []
}
var boundary = 'xxxxxxxxxx';
var contentId = 0;
var data = '--' + boundary + '\r\n';
for (var i in body) {
if(typeof body[i]=='object'){
data += 'Content-Type: application/http\r\n';
data += 'Content-ID: ' + ++contentId + '\r\n\r\n';
data += body[i].method + ' ' + body[i].endpoint + '\r\n';
data += body[i].requestBody ? 'Content-Type: application/json; charset=utf-8\r\n\r\n' : '\r\n';
data += body[i].requestBody ? JSON.stringify(body[i].requestBody) + '\r\n' : '';
data += "--" + boundary + '\r\n';
}
}
var parseBatchRes = function(res) {
var splittedRes = res.split('--batch');
return splittedRes.slice(1, splittedRes.length - 1).map(function(e) {
return {
contentId: Number(e.match(/Content-ID: response-(\d+)/)[1]),
status: Number(e.match(/HTTP\/\d+.\d+ (\d+)/)[1]),
object: JSON.parse(e.match(/{[\S\s]+}/)[0]),
};
});
};
var payload = Utilities.newBlob(data).getBytes();
var head = {Authorization: 'Bearer ' + oAuth}
var options = {
method: 'POST',
contentType: 'multipart/mixed; boundary=' + boundary,
payload: payload,
headers: head,
muteHttpExceptions: false
};
var complete=false;
var finalResponse=[];
for (var n=0; n<=backoff; n++) {
if(complete){
break;
}
var complete = true
console.log('backoff',n);
var response =UrlFetchApp.fetch(url, options).getContentText();
for(var j=0;j<response.length;j++){
if(response[r].status!=200){
var complete = false
}
}
}
}
Add the supportsAllDrives = true query parameter to the request.
The parameters indicates whether the requesting application supports both My Drives and shared drives and the default value for this is false.
Reference
Drive API Parameters
Related
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.. :(
I can't log in successfully from Google Apps Script despite looking for solutions hours long, including many entries in SoF.
The login process sends a POST request to this url: https://finbox.com/_/api/v5/tokens
Input of the credentials happens here: https://finbox.com/login/email
Response code is 400.
Any help will be really appreciated :)
So far I ended up with this:
function testFinBoxWithCredentials(stock = "AAPL") {
var options = {
method: 'post',
contentType: 'application/json',
payload: {
email: 'xxxxxxxxx',
password: 'xxxxxxxxx'
},
muteHttpExceptions: true,
followRedirects: false
};
const response = UrlFetchApp.fetch("https://finbox.com/_/api/v5/tokens", options);
Logger.log(response.getResponseCode());
if ( response.getResponseCode() == 200 ) {
Logger.log("Couldn't login.");
}
else if ( response.getResponseCode() == 302 ) {
Logger.log("Logged in successfully");
var cookie = response.getAllHeaders()['Set-Cookie'];
Logger.log(cookie);
//Access then a link like https://finbox.com/NASDAQGS:AAPL
/* var cookies = response.getAllHeaders()['Set-Cookie'];
for (var i = 0; i < cookies.length; i++) {
cookies[i] = cookies[i].split( ';' )[0];
};
url2 = "https://finbox.com/NASDAQGS:AAPL";
options2 = {
"method": "get",
"headers": {
"Cookie": cookies.join(';')
}
}
var response2 = UrlFetchApp.fetch(url2, options2);
var content = response2.getContentText();
Logger.log(content)*/
} ```
I am trying to retrieve some info from the camera system DVR from HIK Vision with GAS' UrlFetchApp. I am able to perform a successful request with Postman but having trouble getting this to work with the Google App Script's UrlFetchApp.fetch. I confirmed that my Digest calculation is correct. I did that by substituting values for nonce, cnonce, qop etc in my GAS with the ones which were in Postman and got the same Response string as the Postman did. See images of the Postman below:
Postman gets a 200 response and all the data that I need.
The way my app works is it sends a first request and gets 401 response. Then it sends a second request with the auth header calculated from the data received from the first request. I always get 401 no matter what I do. I played with the double quotes in the headers properties etc.
Here's the code:
function getRecordedDaysCount(){
const url = 'my_server_url';
const userName = 'user';
const pass = "password";
const uri = "/ISAPI/ContentMgmt/record/tracks/101/dailyDistribution";
const method = "POST"
const updatedUrl = url + uri;
let year = '2021';
let month = '03';
//payload required by the server for this request
let payload = '<?xml version: "1.0" encoding="utf-8"?><trackDailyParam><year>' + year + '</year><monthOfYear>' + month + '</monthOfYear></trackDailyParam>';
let options = {
"method" : method,
"muteHttpExceptions": true,
"headers":{
"Accept": "application/xml, text/plain, */*",
}
}
let data = UrlFetchApp.fetch(updatedUrl, options);
if(data.getResponseCode() == 401){
let wwwAuthenticate = data.getAllHeaders()["WWW-Authenticate"];
// Example WWWAuthenticate
// "Digest realm="493b21e13dddb4ef7745edaa", domain="::", qop="auth",
// nonce="17fbf10682dc4a7ceb04206cbcd95d8d:1616709300571", opaque="", algorithm="MD5", stale="FALSE""
let authData = wwwAuthenticate.split(',');
let authType = authData[0].split(' ')[0]; // Digest
if(authType === "Digest"){
let realm = authData[0].split('"')[1]; // "493b21e13dddb4ef7745edaa"
let qop=authData[2].split('"')[1]; // "auth"
let nonce = authData[3].split('"')[1]; // "17fbf10682dc4a7ceb04206cbcd95d8d:1616709300571"
let algorithm = authData[5].split('"')[1]; // "MD5"
let nc="00000001";
let cnonce= new Date().getTime().toString(16);
let hash1 = signMD5(userName +':'+ realm +':'+ pass);
let hash2 = signMD5(method+':'+ uri);
let response = signMD5(hash1+':'+nonce+':'+nc+':'+cnonce+':'+qop+':'+hash2);
let digestAuth = "Digest username=\"" + userName + "\"" +
", realm=\"" + realm + "\"" +
", nonce=\"" + nonce + "\"" +
", uri=\"" + uri + "\"" +
", qop=auth" +
", nc=" + nc +
", algorithm=\"MD5\"" +
", cnonce=\"" + cnonce + "\"" +
", response=\"" + response + "\"";
let headers = {
"Content-Type": "text/plain", // copied from Postman
"Accept-Encoding": "gzip, deflate, br", // copied from Postman
"Authorization": digestAuth,
}
let options = {
"method" : method,
"muteHttpExceptions": true,
"headers": headers,
"payload": (payload)
}
logUrlFetch(updatedUrl, options);
}
}
}
function signMD5(message){
let signature = Utilities.computeDigest(
Utilities.DigestAlgorithm.MD5,
message,
Utilities.Charset.UTF_8);
let signatureStr = '';
for (i = 0; i < signature.length; i++) {
let byte = signature[i];
if (byte < 0)
byte += 256;
let byteStr = byte.toString(16);
// Ensure we have 2 chars in our byte, pad with 0
if (byteStr.length == 1) byteStr = '0'+byteStr;
signatureStr += byteStr;
}
Logger.log(signatureStr);
return signatureStr;
}
function logUrlFetch(url, opt_params) {
let params = opt_params || {};
params.muteHttpExceptions = true;
let request = UrlFetchApp.getRequest(url, params);
Logger.log('Request: >>> ' + JSON.stringify(request));
let response = UrlFetchApp.fetch(url, params);
Logger.log('Response Code: <<< ' + response.getResponseCode());
Logger.log('Response text: <<< ' + response.getContentText());
if (response.getResponseCode() >= 400) {
throw Error('Error in response: ' + response);
}
return response;
}
These are the logs I'm getting:
headers after the first request
log after the second request
I compared the headers in the Postman and my app multiple times and they are mostly the same with some differences. The UrlFetchApp.fetch adds a few headers to the request however I added them also in Postman and it still worked so I concluded these additional headers were not a problem. Also the UrlFetchApp.fetch sends a request method as 'post' (lower case) and the Postman uses all caps - 'POST'. The http method is a part of the Digest HA2 calculation, which is case sensitive. This was the only idea that I had for why my requests didn't work. I changed my app's method to lower case 'post' for the digest calculation and for the headers and this did not help.
Is there a way to send requests in the Google App Script other than UrlFetchApp.fetch?
Thanks a lot!
I want to set some command on my t-bot, but i cant understand how to do that
var token = "123456....";
var url = "https://api.telegram.org/bot" + token;
function setMyCommands(chat_id, message_id){
var data = {
method: "post",
payload:{
method: "setMyCommands",
chat_id: String(chat_id),
message_id: message_id,
parse_mode: "HTML"
}
};
UrlFetchApp.fetch(url + '/', data);
}
Now, I am doing like
if message is "/command" to do so. through following:
function sendMessage(id, text, keyBoard){
var data = {
method: "post",
payload: {
method: "sendMessage",
chat_id: String(id),
text: text,
parse_mode: "HTML",
reply_markup: JSON.stringify(keyBoard)
}
};
UrlFetchApp.fetch(url + "/", data);
}
function doPost(e) {
var contents = JSON.parse(e.postData.contents);
var id = contents.message.from.id;
var text = contents.message.text;
if (text == "/command") {
sendMessage(id,"someTEXT...")
}
}
could u pls, also explain what should var data look like and where can I take a form(or structure) of this to apply other methods from telegram bot API
Pyhton case:
create variables:
BOT_TOKEN = '12324'
BOT_COMMANDS= [{"command":"a", "description":"aaa"},{"command":"b","description":"bbb"}]
create the function:
def setMyCommands():
send_text = 'https://api.telegram.org/bot' + BOT_TOKEN + '/setMyCommands?commands=' + str(json.dumps(BOT_COMMANDS) )
response = requests.get(send_text)
and finnaly execute:
if __name__ == '__main__':
#getMyCommands()
setMyCommands()
i want a telegram bot that set a specific photo as a group image. Here you can see the bot api about that method(https://core.telegram.org/bots/api#setchatphoto).
I'm using this function but it doesn't work.
function myFunction(){
var chatId = SUPERGOUP_CHAT_ID
var photo = {
file_id: FILE_ID,
file_size: 425707,
file_path: 'documents/file_2.png'
}
var data = {
method: "post",
payload: {
method: "setChatPhoto",
chat_id: String(zone),
photo: JSON.stringify(photo)
}
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', data);
}
Telegram answer to the function up here:
{"ok":false,"error_code":400,"description":"Bad Request: photo should be uploaded as an InputFile"}
Searching online i found this solution but it also doesn't work.
function uploadFile() {
var group = SUPERGOUP_CHAT_ID
var boundary = "labnol";
var blob = DriveApp.getFileById('FILE_ID').getBlob();
var attributes = "{\"name\":\"asd.jpg\", \"parent\":{\"id\":\"FOLDER_ID\"}}";
var requestBody = Utilities.newBlob(
"--"+boundary+"\r\n"
+ "Content-Disposition: form-data; name=\"attributes\"\r\n\r\n"
+ attributes+"\r\n"+"--"+boundary+"\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\""+blob.getName()+"\"\r\n"
+ "Content-Type: " + blob.getContentType()+"\r\n\r\n").getBytes()
.concat(blob.getBytes())
.concat(Utilities.newBlob("\r\n--"+boundary+"--\r\n").getBytes());
var options = {
method: "post",
contentType: "multipart/form-data; boundary="+boundary,
payload: {
method: "setChatPhoto",
chat_id: String(group),
photo: requestBody
}
};
var request = UrlFetchApp.fetch('https://api.telegram.org/bot' + token , options);
Logger.log(request.getContentText());
}
Telegram answer to the function up here:
Timeout: https://api.telegram.org/bot_TOKEN
Can you maybe find a solution?
Thanks in advance.