How to get CSV data as a string from GmailAttachment? - google-apps-script

I'd like to grab gmail attachments that are CSV files then import them into a google sheet.
Here's where I'm stuck - turning the attachment into a string. I think I have a blob to which the method getContentAsString applies, but apparently I still have the type GmailAttachment because I'm getting this error:
TypeError: Cannot find function getContentAsString in object GmailAttachment.
here's the relevant code:
//************** get the attachments ***************************************
var attachments = [];
var files = [];
for (var i = 0; i < 4; i ++) {
attachments = messages[i].getAttachments();
for (var k = 0; k < attachments.length; k = k+2) { //2 attachments per message, but I only want the 1st one
j = k/2;
files[j] = attachments[k].copyBlob();
Logger.log('Message "%s" contains the attachment "%s" (%s bytes)',
messages[i].getSubject(), files[j].getName(), files[j].getSize());
}
}
var csvFile = "";
for (var i = 0; i < files.length; i++) {
csvFile = files[i].getContentAsString();
}
why is .copyBlob() not returning a blob, but in this case a GmailAttachemnt, and how can I fix it?
is the problem here:
files[j] = attachments[k].copyBlob();
?
by the way I also tried getBlob() instead of copyBlob() and it returned a type error at this line above.
using copyBlob() I get the typeError at this line:
csvFile = files[i].getContentAsString();
Thanks for your help!

Inorder to get the string value of a blob you should use: getDataAsString() method in Blob class. The .getContentAsString() method is from the DocsList Service which is deprecated.
You could also use the getDataAsString() method from the GmailAttachment class itself. Hope that helps!

Related

Use importData to fetch and parse JSON

I had the following function running perfectly:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var habSheet = ss.getSheetByName("Harvests");
var bVals = habSheet.getRange("b2:b").getValues();
var habs = bVals.filter(String).length;
var habitats = habSheet.getRange("B2:B"+habs+1).getDisplayValues();
var data = [];
var traitNames = habSheet.getRange("D1:U1").getValues();
var values = new Array(habs);
for (i = 0; i < habs; i++) {
values[i] = new Array(traitNames[0].length);
for (j=0; j<traitNames[0].length; j++){
values[i][j] = [""];
}
}
var rawData = "";
var names = new Array(habs);
for (i = 0; i < habs; i++) {
names[i] = new Array(1);
}
for (i=0; i<habs; i++){
try{
rawData = UrlFetchApp.fetch("https://api.genopets.me/habitat/"+habitats[i]);
data[i] = JSON.parse(rawData.getContentText());
names[i][0] = data[i].name;
for (j=0; j<data[i].attributes.length; j++){
value = data[i].attributes[j].value;
trait = data[i].attributes[j].trait_type;
for (k=0; k<=21; k++){
if (traitNames[0][k] == trait){
values[i][k] = value;
}
}
}
}
catch(err){
But I'm exceeding max fetch calls daily. I'm in an emergency situation because this needs to run again within an hour.
I'm trying to build a temporary fix, so I'm using importData to call the API with the following formula:
=join(",",IMPORTDATA("https://api.genopets.me/habitat/"&B2,","))
Then, I want to just replace rawData in the code with this imported data. However, now it comes in as text and can't be parsed in the same way. Is there a quick way to force it into JSON format or otherwise convert to a dictionary as before so that I can parse it with the same code?
I'm getting stuck because .name, .length, etc. are failing as the "rawData" is now just a string.
This is the code snippet I'm playing with to try and get this right and build the quick patch for right now:
// for (i=0; i<habs; i++){
var i=0;
importData = habSheet.getRange("AL1").getDisplayValue();
rawData = JSON.stringify(importData);
// Logger.log(rawData);
data[i] = rawData;
// data[i] = JSON.parse(rawData.getContentText());
names[i][0] = data[i].name;
for (j=0; j<data[i].attributes.length; j++){
value = data[i].attributes[j].value;
trait = data[i].attributes[j].trait_type;
for (k=0; k<=21; k++){
if (traitNames[0][k] == trait){
values[i][k] = value;
}
}
}
I've tried as above, and also without stringify, but I can't get this yet.
For reference, this is an example of the API response:
https://api.genopets.me/habitat/7vTz9dniU14Egpt8XHkMxP1x36BLRd15C11eUTaWhB19
Appreciate any help!
I have done a lot of testing to find a simple workaround, but could not find one, the string resulting from the =join(",",IMPORTDATA(url,",")) (and none of the other =IMPORTXXX functions) will work for your code. When using these IMPORT functions the data is interpreted and certain characters are removed or the values formatted, it is NOT recommended to use these functions.
Since you mentioned the message you are getting is related to quota limits you should consider splitting the load of this script in multiple Apps Script projects. As a possible immediate solution you can make a copy of the script (or file bound to the script), authorize the new copy and try again.
To increase performance you could try using the calls in bulk, use this other function fetchAll (https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetchallrequests). There is a 100 request limit for this method. This will result in the same quota usage.

How do I isolate Drive file ID from URL using regex?

I am trying to get the file name for a file I only have the Google Drive URL for. I am currently using a Google Sheets regexextract function to extract the file ID from the URL, and then using the script to find the file based on the ID, but would prefer to do the regex within the script.
I've looked through various posts on here to try to figure it out with no luck. Hopefully someone can spell out what I have done wrong in trying to incorporate the regex.
var sheet = SpreadsheetApp.getActive().getSheetByName("Test1");
var link1 = sheet.getRange("N2:N").getValues();
var regex_ids = new RegExp("/file/d/[a-zA-Z0-9]g");
var links = regex_ids.exec(link1);
var filenames = [];
for (var i = 0; i < links.length; i++) {
var url = links[i][0];
if (url != "") {
var filename = DriveApp.getFileById(links[i][0]).getName();
filenames.push([filename]);
}
}
var startRow = 2; // print in row 2 since row 1 is the header row
var fileNameColumn = 18; // Column B = column 2
var destination = sheet.getRange(startRow, fileNameColumn, filenames.length, filenames[0].length);
destination.setValues(filenames);
}
Currently I am stuck with error "TypeError: Cannot read property "length" from null. (line 7, file "Code")" because the regex is not configured correctly.
Issues/Solution:
Invalid Syntax:
Regexp() accepts a regex string and a flag string as arguments, while the code provides a concatenated regex flag string.
exec() accepts a string argument, while the code provides a 2D array.
Insufficient regex:
Filename IDs also contain underscores _.
Regex should capture() only the ID. The regex provided also captures /file/d
ID contains more than 1 character. Use +
Snippet:
var link1 = sheet.getRange("N2:N" + sheet.getLastRow()).getValues();//modified
var regex_ids = /\/file\/d\/([^\/]+)/;//or new RegExp("/file/d/([a-zA-Z0-9_]+)","g");() =capture ids
//var links = regex_ids.exec(link1);
var filenames = [];
for (var i = 0; i < link1.length; i++) {//modified;loop through values2D array
var url = link1[i][0];//modified;
var preId = regex_ids.exec(url);//added;
var id;
if (preId && (id=preId[1])) {//modified; [1] = first capture group
var filename = DriveApp.getFileById(id).getName();//modified
filenames.push([filename]);
} else {
filenames.push([""]);
}
}
References:
Regex guide
Regexp#exec

JSON Query in Google Sheets/Scripts

I am importing data from a JSON file using Google Apps Script and Google Sheets. I have learned the basics on this, but the formatting on the JSON file I am attempting to parse is throwing me off.
What is confusing me is how I would search for information based on "name". Currently I am using this:
function JSONReq(url, xpath){
var res = UrlFetchApp.fetch(url);
var content = res.getContentText();
var json = JSON.parse(content);
var patharray = xpath.split("/");
for(var i = 0; i < patharray.length; i++){
json = json[patharray[i]];
}
return json;
}
I'm a bit lost now to be honest with you.
I want to have a cell where I can type a name that I already know of, then find it in the JSON file and pull the return that information however I decide to do it. I can pull and write to cells, I have the basics down. But I just can't understand how I could search by the name.
That JSON file is an array of objects. To find a specific object with a given "name", you would parse it into an object (which you do already), then iterate through them and check the name parameter:
var myName = "name of thing I want";
var arr = JSON.parse( ... );
for(var i = 0; i < arr.length; ++i) {
var obj = arr[i];
if(obj.name == myName) { // could be done as obj["name"] == ... too
// do stuff with obj
}
}
For your case, you might add an additional argument to your function (i.e. 2nd arg = the object's property, e.g. "name", with the 3rd = the desired value. This will be fine for any simple key-value properties, but would need specific handling for where the value is itself an object (e.g. the "category" field in your specific JSON file).

Can I ungzip files in Google Drive with apps script?

This is a follow-up to this thread.
I am trying to use the code provided by #July.Tech there, but I keep getting unknown compression method error or incorrect header check error. The error came up when either of two different gzip methods were used to create the compressed file, so I would think the file is correctly gzipped.
Any suggestions? (My input file is gzipped so I cannot use Utilities.unzip().)
Here is the entire code:
reports_folder_id = 'xxxxx'; //id of folder where gzipped csv reports are saved
report_name = 'xxxxxx.gz'; // name of gzipped CSV file
function importData() {
var fSource = DriveApp.getFolderById(reports_folder_id);
var fi = fSource.getFilesByName(report_name); // latest report file
eval(UrlFetchApp.fetch('https://cdn.rawgit.com/nodeca/pako/master/dist/pako.js').getContentText());
if ( fi.hasNext() ) { // proceed if report_name file exists in the reports folder
var file = fi.next();
var charData = file.getBlob().getDataAsString(); // same error if .getBytes() is used
var binData = [];
for (var i = 0; i < charData.length; i++) {
binData.push(charData[i] < 0 ? charData[i] + 256 : charData[i]);
}
var data = pako.ungzip(binData); // I get same error for pako.inflate(binData);
var decoded = '';
for (var i = 0; i < data.length; i++) {
decoded += String.fromCharCode(data[i]);
}
}
}
If no suggestion for fixing the above, any ideas on how to ungzip a gDrive file programatically?
Thanks.
As of January 19, 2018 (see release notes) Apps Script now supports gzip compression accessible using the following Utilities methods:
gzip(blob)
ungzip(blob)
Running the supplied example code indeed results in unknown compression method error in my environment as well.
Try changing
var charData = file.getBlob().getDataAsString(); // same error if .getBytes() is used
To
var charData = file.getBlob().getBytes();
So that is
function myFunction() {
reports_folder_id = '<FOLDER_ID>';
report_name = 'zip3.csv.gz'; // name of gzipped CSV file
var fSource = DriveApp.getFolderById(reports_folder_id);
var fi = fSource.getFilesByName(report_name);
eval(UrlFetchApp.fetch('https://cdn.rawgit.com/nodeca/pako/master/dist/pako.js').getContentText());
if ( fi.hasNext() ) {
var file = fi.next();
var blobData = file.getBlob();
var charData = blobData.getBytes();
var binData = [];
for (var i = 0; i < charData.length; i++) {
binData.push(charData[i] < 0 ? charData[i] + 256 : charData[i]);
}
var data = pako.inflate(binData);
var decoded = '';
for (var i = 0; i < data.length; i++) {
decoded += String.fromCharCode(data[i]);
}
Logger.log(decoded);
}
}
Try running this script on a subset of your original "GlicemiaMisurazioni.csv.gz" file: https://drive.google.com/file/d/0B8geUNXmd4J2YzJoemFLMnBTbVU/view?usp=sharing
(I truncated the original csv to 32 rows to speed up execution for the sake of test – the original takes way too long to run)
Checking the logs shows that the uncompressing worked:
4;02/07/2017 03.00.30;;;158.0;158.0;1;0M0000UMQ5D;;;0;;
4;02/07/2017 02.59.30;;;158.0;158.0;1;0M0000UMQ5D;;;0;;
4;02/07/2017 02.58.30;;;159.0;159.0;1;0M0000UMQ5D;;;0;;
4;02/07/2017 02.57.30;;;159.0;159.0;1;0M0000UMQ5D;;;0;;
4;02/07/2017 02.56.30;;;158.0;158.0;1;0M0000UMQ5D;;;0;;
4;02/07/2017 02.56.00;;;;;0;;0.4;Novorapid ;0;Left flank;Test
You need to find out if pako supports gzip compression. If not, you should look for another compression package that supports gzip.

Action Script 3.0 : parsing json data

I want to parse the json data shown in this link in AS3 to obtain the "message" field . I am trying with
as3corelib.swc with no success. The json data seems to be bit different . Please help me to parse this
I am using the below code
var j:Object = JSON.decode(Json_data);
var message:String = j["message"];
trace(message);
Here trace always showing null
There is no message under root object. Probably you are looking for this:
var j:Object = JSON.decode(str);
var data:Array = j["data"];
for (i = 0; i < data.length; i++) {
trace(data[i].message);
}