List all Gmail History details - google-apps-script

function history(){
var newResponse = Gmail.Users.History.list('email ID / 'me'');
console.log(newResponse);
for(var i = 0; i< newResponse.history.length; i++){
var label = newResponse.history[i];
console.log(JSON.stringify(label));
}
}
GOAL I have been trying to list all my history using the above given function but it is not working. I have been trying to search for a way to make this work but I keep receiving the same error over and over: "GoogleJsonResponseException: API call to gmail.users.history.list failed with error: Missing/invalid parameter: startHistoryId (line 13, file "GMailAPI")"
I hope my question is understandable.

I believe your goal as follows.
You want to retrieve the value from the method "Users.history: list" in Gmail API using Google Apps Script.
How about this answer?
Modification points:
In your case, it is required to include startHistoryId in the request of Gmail.Users.History.list. At the official document, startHistoryId is "Required".
I think that the reason of your issue is due to this.
In order to retrieve startHistoryId, please use the method "Users: getProfile" in Gmail API.
When above points are reflected to your script, it becomes as follows.
Usage:
In order to retrieve the values from Gmail.Users.History.list, please run the following flow.
Please run the function getStartHistoryId().
By this, startHistoryId is saved to PropertiesService.
For example, as a sample situation, please send a sample email on Gmail using browser.
Please run the function history().
By this, you can retrieve the values from Gmail.Users.History.list.
When history() is run just after getStartHistoryId() was run, no values are returned from Gmail.Users.History.list. Please be careful this. The official document of startHistoryId is as follows.
startHistoryId : Required. Returns history records after the specified startHistoryId. The supplied startHistoryId should be obtained from the historyId of a message, thread, or previous list response. History IDs increase chronologically but are not contiguous with random gaps in between valid IDs. Supplying an invalid or out of date startHistoryId typically returns an HTTP 404 error code. A historyId is typically valid for at least a week, but in some rare circumstances may be valid for only a few hours. If you receive an HTTP 404 error response, your application should perform a full sync. If you receive no nextPageToken in the response, there are no updates to retrieve and you can store the returned historyId for a future request.
Modified script:
// Added: At first, please run this function and save the startHistoryId to the PropertiesService.
function getStartHistoryId() {
var startHistoryId = Gmail.Users.getProfile('me').historyId;
PropertiesService.getScriptProperties().setProperty("startHistoryId", startHistoryId);
}
// Modified: As the next step, please run this function.
function history() {
var startHistoryId = PropertiesService.getScriptProperties().getProperty("startHistoryId");
var newResponse = Gmail.Users.History.list('me', {startHistoryId: Number(startHistoryId)});
if ("history" in newResponse) {
for(var i = 0; i< newResponse.history.length; i++){
var label = newResponse.history[i];
console.log(JSON.stringify(label));
}
}
}
In my environment, I could confirm that {startHistoryId: startHistoryId} can be also used.
Note:
This modified script supposes that Gmail API has already been enabled at Advanced Google services. Ref
References:
Users.history: list
Users: getProfile

Related

Google Apps Script: What is the correct way to check if I have access to a Google Spreadsheet by URL

I am currently writing a Google Apps Script inside a Google Sheet to read data from a list of spreadsheets (spreadsheet url is provided by the user). However, I cant seems to find a way to check if the url is valid or if user have access to the spreadsheet or not before calling SpreadsheetApp.openByUrl().
I have written the following code to "validate" the url:
for(int i = 0; i < urls.length; i++) {
let spreadsheet = null
try {
spreadsheet = SpreadsheetApp.openByUrl(urls[i]);
} catch (e) {
continue;
}
// Continue to do other stuff to read data from spreadsheet...
}
This however has an issue, it was able to catch the first few 'You do not have permission to access the requested document.' exception. But after a certain number of exception had occur, I would get a permenant error that cant be caught, stopping the script all together.
Is there a better way to do this?
Minimal reproducible example:
Create 3 google sheet using different google account
Using a different google account, create a google sheet and add the following code into Code.gs
function myFunction() {
// Put any 3 real spreadsheet url that you do not have access to
let urls = [
"https://docs.google.com/spreadsheets/d/1gOyEAz0amm4RghpE4B7f26okU3PG3vWZkrfiC-SBlbw/edit#gid=0",
"https://docs.google.com/spreadsheets/d/1Oia7ADu5BmYroUq1SLyDMHTJowrwSXOhCEyNO3nXmMA/edit#gid=0",
"https://docs.google.com/spreadsheets/d/1HE_IXURpBr_FJN--mwLo6k9gih07ZEtDGBqYSk6KgiA/edit#gid=0",
]
urls.forEach(url => {
try {
SpreadsheetApp.openByUrl(url)
} catch (e) {
console.log("Unable to open this spreadsheet")
}
})
}
function onOpen() {
SpreadsheetApp.getUi().createMenu("Test").addItem("myFunction", "myFunction").addToUi()
}
Run the function once in the apps script panel and authorize the application
Refresh this google sheet
Wait for the Custom Menus to show up and press "Menu" > "myFunction"
As you can see, the openByUrl() call is sitting inside the try catch block, however when you run the function through custom menu, you will still get "Error: You do not have permission to access the requested document.".
Executions Log:
From your question, I thought that your situation might be due to the specification or a bug of SpreadsheetApp.openByUrl. If my understanding is correct, in order to avoid this issue, how about putting the method for checking whether the file can be used before SpreadsheetApp? In your script, how about the following modification?
From :
SpreadsheetApp.openByUrl(url)
To:
var fileId = url.split("/")[5];
var file = DriveApp.getFileById(fileId);
spreadsheet = SpreadsheetApp.open(file);
In this modification, the file is retrieved with DriveApp.getFileById(fileId). When fileId cannot be used, an error occurs. But in this case, try-catch can be correctly worked. By this, the issue of SpreadsheetApp doesn't occur.

appendRow() adds blank row in google sheets (app script)

I've setup a google app script that would be triggered from an external system. This script would fetch the details from the third party system and add them to google sheet row.
function doPost(request) {
try{
var jsonString = request.postData.getDataAsString(); //get the request from KF as JSON String
setLog("\n postData*********************"+jsonString+"************************************* \n");
setLog("before the row append");
ss.appendRow([jsonString["Name"], jsonString["Age"], jsonString["Contact"]]);
setLog("After the row append");
var returnJson = '{"status": "success"}';
//used to send the return value to the calling function
setLog("/n returnJson****************************"+returnJson+"************************************* /n")
return ContentService.createTextOutput(returnJson).setMimeType(ContentService.MimeType.JSON);
}
There's absolutely no errors or warnings, but somehow it keeps adding the blank rows into the sheet.
Note: setLog() is a function where I print the values into google doc for debugging.
Maybe the reason your script is not working has to do with the value of jsonString.
I could not find any reference to request.postData.getDataAsString() inside GAS Documentation, so maybe you are trying to call a method on an object which does not support it, which would not raise an Error, but would return undefined.
One quick way to debug this would be to LOG the value (using your custom function or Logger.log(jsonString)) BEFORE you call .appendRow(). Then, you can verify if your variable has the value you expect it to have.
On the other hand, my suggestion is to use this method:
var jsonString = JSON.parse(request.postData.contents) //Gets the content of your request, then parses it
This method is present in the Documentation, and has been consistently working on all of my projects.
I think you should sort the coulmns with google app script. Write this code after ss.appendRow. The column will be sorted and all blank rows gets down.
// Sorts the sheet by the first column, ascending
ss.sort(1)
or if errors try this one also
var fl = SpreadsheetApp.getActiveSpreadsheet();
var sheet = fl.getSheets()[0];
fl.sort(1)

Fetch Telegram Post's views with Google Apps Script

I'm trying to fetch this number:
Tested this Telegram post:
https://t.me/s/google_sheets/393
I want to do it with a script. Interesting, it was possible for me to achieve this with a formula:
=INDEX( REGEXEXTRACT(SUBSTITUTE(QUERY(FLATTEN(IMPORTDATA("https://t.me/s/google_sheets/393")),,2^99),CHAR(10),), "(.*tgme_widget_message_views"">)(.+)(<\/span><span class=""copyonly"".*)"),2)
This is the script I've tried:
function GET() {
var reg1 = /tgme_widget_message_views">(.+)<\/span><span class="copyonly"/i;
var response = UrlFetchApp.fetch('https://t.me/s/google_sheets/393').getContentText();
var match = response.match(new RegExp(reg1))[1];
Logger.log(match); // 13.4K, expected: 11.2K
}
It gives me the wrong result. I'm willing to do the work with a script because I want an afficient way of fetching multiple URLs at once. The formula will crash in this situation.
My script gives me the wrong result, it may be the number for other Telegram post (https://t.me/google_sheets/374 ), I'm not sure.
Please help me to resolve this issue.
Since the one you are looking for is that specific post and not the others, you may want to limit what you are searching for by appending the id of the post at the end of your regex. Thus we are sure we are looking just until post id 393. As you can see, I added .+google_sheets\/393 in your regex.
function GET() {
var reg1 = /tgme_widget_message_views">(.+)<\/span><span class="copyonly".+google_sheets\/393/i;
var response = UrlFetchApp.fetch('https://t.me/s/google_sheets/393').getContentText();
var match = response.match(new RegExp(reg1))[1];
Logger.log(match); //11.2K
}

GAS - Authentication w/ UrlFetchApp - Form to Spreadsheet

I am testing the functionality of UrlFetchApp and passing data from a Form and its Spreadsheet. I know it's possible to do this another way, however I am testing the functionality of UrlFetchApp (first time using it) within google scripts themselves, and want to get it to work with this method.
Here's the scenario I got, add a bound script to a Form App as so:
function makeRequest()
{
var webAppUrl = "https://script.google.com/macros/s/WebAppID/exec";
var auth = ScriptApp.getOAuthToken();
var header = { 'Authorization': 'Bearer ' + auth };
var options = { 'method':'post', 'headers':header };
var resp = UrlFetchApp.fetch(webAppUrl, options);
Logger.log(resp);
}
Add a bound script to the attached spreadsheet:
function doPost()
{
var ss = SpreadsheetApp.openById('ssID');
var name = ss.getName();
return ContentService.createTextOutput(name);
}
And then publish this second script attached to the sheet as a web app with only myself to have access.
Currently the above code does not work. The following error appears on the Form side of the script:
Request failed for
https://script.google.com/macros/s/WebAppID/exec
returned code 401. Truncated server response:
Unauthorized Unauthorized Error 401
(use muteHttpExceptions option to examine full response) (line
12, file "Code")
Fails on the UrlFetchApp line of code.
However, if I remove the header option, then publish the web app for public use, this works just fine. Obviously this is not wanted.
What am I missing regarding authentication between scripts that I own?
Side Notes:
Anyone know why SpreadsheetApp.getActiveSheet() doesn't work when run in this fashion? That script is directly bound to a google sheet, so kind of odd.
Ok, found the answer to my own question. It was quite simple really. Needed to add the following scope to my project for accessing a spreadsheet:
https://www.googleapis.com/auth/drive
The easiest way I found to do this is to add a simple function like this and call it:
function authorizeDrive()
{
var forScope = DriveApp.getRootFolder();
}
Doesn't need to return or do anything, just call any method from the DriveApp. Once run, it'll then popup with a dialogue for authorization. Don't even need to call this every time you do your main method calls. Don't even need to leave it coded in the script either. I wonder if there is way to just simple add the scope you need to a project from a properties window (I didn't find any). Or perhaps a way to pass a parameter along with UrlFetchApp regarding what scope need authorized.
Buy anyhow this still wasn't too bad.
Regarding my side note, I still haven't found a reason as to why SpeadsheetApp.getActiveSheet() returns null or undefined. I have to open by ID or URL, which is a pain. Especially since this is a container bound script. Also noticed that Logger.log() doesn't actually add anything to the Logger when run in this manner. If anyone could still shed some light on either of these, that would be great.
You need to get the 'Spreadsheet' object first.
SpeadsheetApp.getActive().getActiveSheet()
However, if you are creating an add-on menu you can use 'SpreadsheetApp.getActiveSheet()'
function myFunction() {
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
var range = SpreadsheetApp.getActiveSheet().getRange(lastRow, 1, 1, 26);
SpreadsheetApp.setActiveRange(range);
}
function onOpen(e) {
SpreadsheetApp.getUi().createAddonMenu()
.addItem('showLastRow', 'myFunction')
.addToUi();
}

Gmail API message.list from trigger

I am having a problem creating an google script that would run every night. The code runs fine when I run it from the script file and behaves as expected, however when performed by an installed trigger I get the following:
TypeError: Cannot read property "length" from undefined. (line 17, file "Main")
EDIT: To be clear I know that the particular query used has to return results as running the same script from the script editor works fine
Code:
function doGet(query) {
var sSheet = sheetSelect(), //calls spreadsheet selection function and assigns the spreadsheet to variable
queriedMessages, //object to store the queried messages list
pageToken, //string token value that will be pulled from the queredMessages
auth = 'me';
if (!query) query = 'in:all newer_than:1d -is:chats -in:trash';
do {
queriedMessages = Gmail.Users.Messages.list(auth, {'q':query, 'pageToken':pageToken}); //callls the Gmail API to query messages
dataOutput(sSheet, queriedMessages.messages, queriedMessages.messages.length); //calls function to output all data to spreadsheet from the current list
pageToken = queriedMessages.nextPageToken; //gets the next page token from the list
}
while (pageToken); //the loop is executed until there are no more next page tokens left
}
Any ideas why it behaves so differently? I have tried providing userId for a specific e-mail. Seems like this might be some kind of authentication issue but I cannot figure out how to fix it other than forgetting about Gmail API and go a roundabout way of using Gmail App as it seems to be an issue with Gmail API method messages.list()
Thank You for any help in advance!
I managed to fix the issue. The problem was me wanting to leave an option to pass on a query with the function call. The problem then is that the installed trigger actually passed on a variable to the query variable and a new one is then not set.
I think it's much simpler than that. If I list messages in the last day, I get:
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?q=newer_than%3A1d
Response:
{
"messages": [
{
"id": "150612f9d7f83db9",
"threadId": "150611d4e92b7a5f"
}, ...
]
}
If I list messages in the last second, I get:
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?q=newer_than%3A1s
Response:
{
"resultSizeEstimate": 0
}
In other words, queriedMessages.messages will be undefined if you get no messages with that particular query, and queriedMessages.messages.length will give rise to your error.