Amazon MWS Signature mismatch in Google Sheet - google-apps-script

I am creating a Google Sheet add-on wherein I am downloading the FBA Orders. I have been trying to make a successful API call. Am I perhaps missing something here?
Response I am getting:
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
Google Sheet JavaScript code:
function POSTRequest() {
var config = amazonMWSConfigProperties.getProperty('amazonConfig');
var configData = JSON.parse(config);
var sellerID=configData.sellerID;
var accessKey=configData.accessKey;
var secretKey=configData.secretKey;
var authToken=configData.authToken;
var defaultMarket=configData.defaultMarket;
var url = 'https://mws.amazonservices.in/Orders/2013-09-01?';
var today1 = new Date();
var today = new Date();
var todayTime = Utilities.formatDate(today, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
yesterday.setHours(0,0,0,0);
var yesterdayTime = Utilities.formatDate(yesterday, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var dayBeforeYesterday = new Date();
dayBeforeYesterday.setDate(today.getDate() - 2);
dayBeforeYesterday.setHours(0,0,0,0);
var dayBeforeYesterdayTime = Utilities.formatDate(dayBeforeYesterday, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'");
var unsignedURL =
'POST\nmws.amazonservices.in\n/Orders/2013-09-01\n'+
'AWSAccessKeyId=' +accessKey+
'&Action=ListOrders'+
'&CreatedAfter='+encodeURIComponent(Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
//'&CreatedBefore=2020-01-18T18%3A30%3A00Z' + //yesterdayTime +
'&FulfillmentChannel.Channel.1=AFN' +
'&MWSAuthToken=' +authToken+
'&MarketplaceId.Id.1=' +defaultMarket+
'&SellerId='+sellerID+
'&SignatureMethod=HmacSHA256'+
'&SignatureVersion=2'+
'&Timestamp='+encodeURIComponent(Utilities.formatDate(today1, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
'&Version=2013-09-01';
Logger.log(unsignedURL);
var SignedRequest = calculatedSignature(unsignedURL, secretKey);
var Encoded = Utilities.base64Encode(SignedRequest);
var param = 'AWSAccessKeyId=' +accessKey+
'&Action=ListOrders'+
'&CreatedAfter='+encodeURIComponent(Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
//'&CreatedBefore=2020-01-18T18%3A30%3A00Z' + //yesterdayTime +
'&FulfillmentChannel.Channel.1=AFN' +
'&MWSAuthToken=' +authToken+
'&MarketplaceId.Id.1=' +defaultMarket+
'&SellerId='+sellerID+
'&Signature='+encodeURIComponent(SignedRequest) +
'&SignatureMethod=HmacSHA256'+
'&SignatureVersion=2'+
'&Timestamp='+encodeURIComponent(Utilities.formatDate(today1, "GMT", "yyyy-MM-dd'T'HH:mm:ss'Z'")) +
'&Version=2013-09-01';
Logger.log(url+param);
var result = UrlFetchApp.fetch(url+param);
//writeDataToXML(result);
Logger.log(result);
if (result.getResponseCode() == 200) {
//writeDataToXML(result);
}
}
function calculatedSignature(url,secret) {
var urlToSign = url;
var byteSignature = Utilities.computeHmacSha256Signature(urlToSign, secret);
// convert byte array to hex string
var signature = byteSignature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
//Logger.log("URL to sign: " + urlToSign);
//Logger.log("");
//Logger.log("byte " + byteSignature);
//Logger.log("");
//Logger.log("reg " + signature);
var byte64 = Utilities.base64Encode(byteSignature)
//Logger.log("base64 byte " + Utilities.base64Encode(byteSignature));
//Logger.log("");
//Logger.log("base64 reg " + Utilities.base64Encode(signature));
Logger.log("base64 reg " + byte64)
return byte64;
}

In addition to using the built-in encodeURIComponent, I have found that you also have to manually replace certain values. This is the function that I use to encode my URL:
/**
* Performs URL encoding compatible with MWS http requests
* #param {String} The string to encode
*/
function urlEncode(str) {
str = encodeURIComponent(str);
str = str.replace(/\*/g, '%2A');
str = str.replace(/\(/g, '%28');
str = str.replace(/\)/g, '%29');
str = str.replace(/'/g, '%27');
str = str.replace(/\!/g, '%21');
return str;
}
I call it in my createSignature function:
function createSignature(value, key) {
var signature = Utilities.computeHmacSha256Signature(value, key);
return urlEncode(Utilities.base64Encode(signature));
}
Which I call on my unsigned URL, passing my secret as the key.
I hope that helps!

Related

Attachments: undefined Apps Script

I am trying to pull an attachment from an email which is a zip file and send that file to Google Analytics to upload the data. I am struggling with the getAttachment part in the script as it is showing as undefined. I have no idea what I am doing wrong. Any help would be greatly appreciated. I have now fixed where it is pulling the right attachment by using: var attachments = messages[0].getAttachments(); However I am now getting a new error where it seems to be looking for a title of the attachment but it is still showing as undefined.
function refundImport() {
/// use custom report to schedule the email - will need to adjust the processCsv() function based on your schema
/// if you are using a non-bing data source - you will probably need to adjust the findCsvAttachments() function as it grabs a zip file now
var CONFIG = {
'emailSubject': 'Refunded or Partially Refunded Orders - TKS',
'customDataSourceId': 'xxxxxxxxxxxxxxxxxxxxx',
'now': new Date(),
'zipFileName': 'refunded_or_partially_refunded_orders.zip',
//'csvFileName': 'refunded_or_partially_refunded_orders.csv',
'analyticsAccountId': '12345678',
'analyticsPropertyId': 'UA-12345678-1'
}
//adds one whole day to a date object - can take negative days if you want yesterday etc
function addDaysToDate(DATE, DAYS) {
var newDate = DATE.getTime() + DAYS * 3600000 * 24;
var newDate1 = new Date(newDate);
return newDate1;
}
//takes a date object and formats it as a string
function formatDateAsString(DATE) {
var dateString = Utilities.formatDate(DATE, 'GMT+12:00', 'yyyy/MM/dd');
return dateString;
}
//after and before must be date strings - use the above function
function grabEmailAttachments(SUBJECT, AFTER, BEFORE) {
var query = 'subject:' + SUBJECT + ' ' + 'has:attachment after:' + AFTER + ' ' + 'before:' + BEFORE;
//assumes only 1 will match - if more than 1 - will match the first one
var thread = GmailApp.search('in:inbox from:"noreply#highviewapps.com"');
var messages = thread[0].getMessages();
var content = messages[0].getPlainBody();
//var attachments = thread.getAttachments()[0];
var attachments = messages[0].getAttachments();
//thread.moveToTrash();
return attachments;
}
// finds csv attachment and creates 2d array of row,column e.g. csv[0][1] = value in row 0 column 1 of csv
function findCsvAttachment(attachments, zipFileToSearch, fileNameToSearch) {
var counter = 0;
for (i = 0; i < attachments.length; i++) {
if (attachments[i].getName().search(zipFileToSearch) != -1) {
var unzip = Utilities.unzip(attachments[i]);
var csvData = Utilities.parseCsv(unzip[0].getDataAsString(), ",");
counter = counter + 1;
}
}
if (counter == 0) {
Logger.log('No file with ' + fileNameToSearch + ' in its name was found.');
}
if (counter == 1) {
return csvData;
}
if (counter > 1) {
Logger.log('More than 1 file with ' + fileNameToSearch + ' in its name was found - the last one was used.');
}
}
function processCsv(csvData, date) {
var headers = 'ga:transactionId,ga:productSku,ga:productPrice,ga:quantityRefunded,ga:transactionRevenue';
var dataForUpload = headers;
return dataForUpload;
}
//assumes media dataType for upload https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/uploads/uploadData
function uploadDataToAnalytics(data, accountId, webPropertyId, customDataSourceId) {
var dataBlob = Utilities.newBlob(data, "application/octet-stream");
var upload = Analytics.Management.Uploads.uploadData(accountId, webPropertyId, customDataSourceId, dataBlob);
return upload;
}
///////// ACTUAL IMPLEMENTATION OF SCRIPT /////////////
var tomorrowString = formatDateAsString(addDaysToDate(CONFIG.now, 1));
var yesterdayString = formatDateAsString(addDaysToDate(CONFIG.now, -1));
var todayString = formatDateAsString(CONFIG.now);
var emailAttachments = grabEmailAttachments(CONFIG.emailSubject, yesterdayString, tomorrowString);
var csv = findCsvAttachment(emailAttachments, CONFIG.zipFileName, CONFIG.csvFileName);
var csvForUpload = processCsv(csv, yesterdayString);
var analyticsUpload = uploadDataToAnalytics(csvForUpload, CONFIG.analyticsAccountId, CONFIG.analyticsPropertyId, CONFIG.customDataSourceId);
}
Try changing these lines of code
From:
var attachments = messages.getAttachments();
and
var unzip = Utilities.unzip(attachments[i]);
To:
var attachments = messages[0].getAttachments();
and
var unzip = Utilities.unzip(attachments[i].copyBlob());

My Google app script can successfully iterate and extract file attachments but not hyperlinked attachments. Is there a method I can use for this?

I have a function that successfully extracts file attachments from new emails. But it is unable to detect hyperlinked attachments like this email from our office scanner:
Would anyone know of an approach to address this?
UPDATE:
I found a similar case in How to download file from a download link on Gmail and uploading it to Google Drive using Google Apps Script
and revised my code below.
Here's the result:
a) extracting a hyperlinked attachment using this publicly available URL (people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv) as my sample attachment works OK and saves it in my preferred filename, but
b) for the actual email from our scanner I get this error message when it executes UrlFetchApp.fetch(url):
Exception: Bad request: http://10.87.38.21:8000/cmj/asd/download?dev=...
What could be causing this issue?
(my revised code below):
//===================================
function ExtractEmailAttachmentsJP() {
//===================================
var spr = SpreadsheetApp.getActiveSpreadsheet();
var ss = SpreadsheetApp.getActiveSheet();
var sheet = SS.getSheetByName("UPLOADER");
var logsheet = SS.getSheetByName("Documents Uploaded");
TEMPFOLDERID = PropertiesService.getDocumentProperties().getProperty('TEMPFOLDERID');
TEMPFOLDER = DriveApp.getFolderById(TEMPFOLDERID);
//do not overwrite, just append to the last non-empty (max) row
var startRow = lastNonblankRow("N",SSID,"UPLOADER")+1
COUNTER = 0; //initialize file attachment counter
var gmailLabels = "PeopleDoc";
var threads = GmailApp.search("label:" + gmailLabels + " is:Unread", 0, 3);
if (threads.length > 0) {
SS.toast("New emails found. Extracting attachment(s)...");
/*Iterate through the threads of emails*/
for (var t=0; t<threads.length; t++) {
var msgs = threads[t].getMessages();
//Iterate through each mail
for (var m=0; m<msgs.length; m++) {
var msg = msgs[m];
var data = msg.getPlainBody();
var urlField = data.indexOf('http://');
var url = data.substr(urlField, 117);
var fileUrl = UrlFetchApp.fetch(url);
var fileBlob = fileUrl.getBlob();
COUNTER = COUNTER + 1;
var timestamp = Utilities.formatDate(new Date(msg.getDate()), "GMT+8", 'dd MMM hh:mm');
var pdfID = TEMPFOLDER.createFile(fileBlob).setName("attachment" + COUNTER + " " + timestamp).getId();
//show each generic file row by row
sheet.getRange(startRow-1+COUNTER, 10).setFormula('=HYPERLINK(\"https://drive.google.com/file/d/' + pdfID + '\", \"' + "attachment" + COUNTER + " " + timestamp + '\")');
sheet.getRange(startRow-1+COUNTER, 15).setValue(pdfID);
sheet.getRange(startRow-1+COUNTER, 16).setValue("Gmail Inbox");
}

Coinbase v2 with Google Sheets

I'm trying to use the Coinbase API v2 (NOT PRO) to simply pull the contents of my wallet in to my spreadsheet. I have the following:
var url = 'https://api.coinbase.com/v2';
var requestPath = '/accounts';
var api_key = '********';
var secret_key = '********';
// var timestamp = new Date().getTime().toString().slice(0, -3);
var timestamp = Math.floor(Date.now() / 1000).toString();
var method = 'GET';
var message = Utilities.base64Decode(Utilities.base64Encode(timestamp + method + requestPath));
var decoded_secret = Utilities.base64Decode(secret_key);
var signature = Utilities.base64Encode(Utilities.computeHmacSha256Signature(message, decoded_secret));
//previous attempt
/* var signature = Utilities.computeHmacSha256Signature(message, decoded_secret);
signature = signature.map(function(e) {
var v = (e < 0 ? e + 256 : e).toString(16);
return v.length == 1 ? "0" + v : v;
}).join("");*/
var params = {
'method': method,
'headers': {
'Content-Type': 'application/json',
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': api_key,
'CB-VERSION': '2021-02-03'
}
};
Logger.log(params);
var res = await UrlFetchApp.fetch(url + requestPath, params);
Unfortunately I am getting the invalid signature error. I feel like I'm close. It could be that I don't have the url/request path going in to the signature correctly.
Creating a Coinbase signature with Apps Script is a bit tricky
As you can see from the documentation for Coinbase API, in Python the signature is built with
signature = hmac.new(self.secret_key, message, hashlib.sha256).hexdigest().
In other words, the signature needs to be hex-encoded.
There is no 1:1 equivalent to hexdigest() in Apps Script, but you can build it manually.
This works for me:
var message = (timestamp + method + requestPath + body);
var byteSignature = Utilities.computeHmacSha256Signature(message, secret);
var signature = byteSignature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
Thereby secret is your credential provided by Coinbase - no need to decode it.

Saving Telegram Bot messages into google spreadsheet

I'm a beginner, so I'll try my best to explain what happends in the code and what I want to do. I've created a telegram bot and I want to save all the message log into google spreadsheets. I'm using Google App Scripts for this. The problem is that when I'm using the following code, only the messages that I write are saved into google sheets, it happends in the "doPost" function. What I want to do is to save also the messages I receive from the bot. I tried adding the same code the I used in "doPost" and worked into the function "sendText" but I'm still unable to save the messages that the bot sends. How can I do this? here's my code:
var token = "..."; // 1. FILL IN YOUR OWN TOKEN
var telegramUrl = "https://api.telegram.org/bot" + token;
var webAppUrl = "..."; // 2. FILL IN YOUR GOOGLE WEB APP ADDRESS
var ssId = "..."; // 3. FILL IN THE ID OF YOUR SPREADSHEET
var adminID = "..."; // 4. Fill in your own Telegram ID for debugging
// connect to bot
function getMe() {
var url = telegramUrl + "/getMe";
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
function setWebhook() {
var url = telegramUrl + "/setWebhook?url=" + webAppUrl;
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
}
// make the bot to send a message
function sendText(id,text) {
var url = telegramUrl + "/sendMessage?chat_id=" + id + "&text=" + encodeURIComponent(text);
var response = UrlFetchApp.fetch(url);
Logger.log(response.getContentText());
try {
// this is where telegram works
var data = JSON.parse(response.getContentText());
var text = data.message.text;
var id = data.message.chat.id;
var name = data.message.chat.first_name + " " + data.message.chat.last_name;
var answer = "Hi " + name;
// sendText(id,answer);
SpreadsheetApp.openById(ssId).getSheets()[0].appendRow([new Date(),id,name,text,answer]);
if(/^#/.test(text)) {
var sheetName = text.slice(1).split(" ")[0];
var sheet = SpreadsheetApp.openById(ssId).getSheetByName(sheetName) ? SpreadsheetApp.openById(ssId).getSheetByName(sheetName) : SpreadsheetApp.openById(ssId).insertSheet(sheetName);
var newText = text.split(" ").slice(1).join(" ");
sheet.appendRow([new Date(),id,name,newText,answer]);
sendText(id,"your text '" + newText + "' is now added to the sheet '" + sheetName + "'");
}
} catch(e) {
sendText(adminID, JSON.stringify(e,null,4));
}
}
function doGet(e) {
return HtmlService.createHtmlOutput("Hi there");
}
// send a message from me to the bot
function doPost(e) {
try {
// this is where telegram works
var data = JSON.parse(e.postData.contents);
var text = data.message.text;
var id = data.message.chat.id;
var name = data.message.chat.first_name + " " + data.message.chat.last_name;
var answer = "Hi " + name;
// sendText(id,answer);
SpreadsheetApp.openById(ssId).getSheets()[0].appendRow([new Date(),id,name,text,answer]);
if(/^#/.test(text)) {
var sheetName = text.slice(1).split(" ")[0];
var sheet = SpreadsheetApp.openById(ssId).getSheetByName(sheetName) ? SpreadsheetApp.openById(ssId).getSheetByName(sheetName) : SpreadsheetApp.openById(ssId).insertSheet(sheetName);
var newText = text.split(" ").slice(1).join(" ");
sheet.appendRow([new Date(),id,name,newText,answer]);
sendText(id,"your text '" + newText + "' is now added to the sheet '" + sheetName + "'");
}
} catch(e) {
sendText(adminID, JSON.stringify(e,null,4));
}
}
Thank you.

How to get Hex value from computeHmacSha256Signature method of Google Apps Script?

I read solution for MD5 below, but I could not quite get it.
get back a string representation from computeDigest(algorithm, value) byte[]
I'd like to create API signature with HMAC-SHA256 hash.
var date = new Date();
var nonce = Math.floor(date.getTime()/1000);
var url = "https://mysweet.com/api/accounts"
var secret = "my_secret";
var signature = Utilities.computeHmacSha256Signature(nonce+url, secret);
but it returns byte array [42, -8, -47, -21, ..], and it cannot be used as API signature directly.
Is there a simple way to get a Hex value from the method? or conversion?
I applied the method you linked to and get:
var sig = signature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');;
So here's a test function:
function testSig() {
var date = new Date();
var message = "Violets are red";
var secret = "my_secret";
var signature = Utilities.computeHmacSha256Signature(message, secret);
var sig = signature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
Logger.log(sig); // fe70fa2e74b3ee0d67aa3c1d5c2844e558fea6802e8cfa58e5d4cbdf8bad25fe
//output from http://www.freeformatter.com/hmac-generator.html#ad-output is:
// fe70fa2e74b3ee0d67aa3c1d5c2844e558fea6802e8cfa58e5d4cbdf8bad25fe
}
Plus golf:
Utilities.computeHmacSha256Signature(message, secret)
.map(function(chr){return (chr+256).toString(16).slice(-2)})
.join('')
var date = new Date();
var nonce = Math.floor(date.getTime()/1000);
var url = "https://mysweet.com/api/accounts"
var secret = "my_secret";
var signature = Utilities.computeHmacSha256Signature(nonce+url, secret)
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");