Gmail property missing from event fired from gmail add-on contextualTriggers - google-apps-script

I'm redeveloping an gmail addon I wrote in the past (yay typescript and clasp) and I'm trying to support mobile. I'm searching the message content using the following to get the message
function onGmailMessage(e) {
console.log(e);
// Get the ID of the message the user has open.
let messageId = e.gmail.messageId;
// Get an access token scoped to the current message and use it for GmailApp
let accessToken = e.gmail.accessToken;
GmailApp.setCurrentMessageAccessToken(accessToken);
This works great on when called from a desktop browser. However, when called from mobile iOS Gmail the gmail property is missing from e. I'm using V8 runtime with the following scopes:
"oauthScopes": [
"https://www.googleapis.com/auth/calendar.addons.execute",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/drive.addons.metadata.readonly",
"https://www.googleapis.com/auth/gmail.addons.current.action.compose",
"https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
"https://www.googleapis.com/auth/gmail.addons.execute",
"https://www.googleapis.com/auth/script.locale",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/script.external_request"],
"runtimeVersion": "V8",
Should this be available, or is there a different approach to support mobile/iOS?
Thanks
-Matt

Related

How to get Google app script deployment details in run time using JS

I am trying to read deployment information (version number and description) of google sheets addon during runtime using java script.
I found this web app related post. I am not sure whether it is different as my GAS is not web app.
However I also tried this API here and it looks OK, I got all the script deployments info when supplying only the script ID.
I modified appsscript.json to include all script required scopes (until so now it looks like that
{
"timeZone": "Asia/Jerusalem",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/script.deployments",
"https://www.googleapis.com/auth/script.deployments.readonly",
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/spreadsheets"
]
}
this is the call to the API
function getDeployments(){
let OAuthToken = ScriptApp.getOAuthToken();
let id = ScriptApp.getScriptId();
var url = 'https://script.googleapis.com/v1/projects/'+ id + '/deployments';
var options = { "muteHttpExceptions": true,
"headers": {
'Authorization': 'Bearer ' + OAuthToken
}
}
var response = UrlFetchApp.fetch (url, options);
console.log( "util.getDeployment" ,response.getResponseCode())
}
but this call returns http code 403.
Please advice
#Tanaike guiding question helped a lot.
Checking the response.getContentText() as opposed response.getContent() I checked before made the issue clear. The latter returned array of numbers, whereas the response.getContentText() returns readable string.
Problem was the App script API was has not been enabled. Enabling this API solved the problem.

Session.getActiveUser().getEmail() gives two values when creating a sidebar and when calling a appscript method from templated html

I have been using https://github.com/labnol/apps-script-starter as a starter guide to create app script projects for Google Slides.
But I came across an issue related to the context (active user) that the appscript function is called. While calling the method Session.getActiveUser().getEmail() in the onOpen() function I get the correct active account but when I call the same function from a templated HTML I get the default account for that browser. Has anyone faced this issue? A solution will be great.
const onOpen = (e) => {
Logger.log(Session.getActiveUser().getEmail());
Logger.log(Session.getEffectiveUser().getEmail());
..
}
const customFunction = () => {
Logger.log(Session.getActiveUser().getEmail());
Logger.log(Session.getEffectiveUser().getEmail());
..
}
const showSidebar = () => {
const template = HtmlService.createTemplateFromFile('gra-main-v3');
template.include = include;
const html = template.evaluate().setTitle('New Sidebar');
SlidesApp.getUi().showSidebar(html);
};
gra-main-v3.html
<HTML>
...
<script>
google.script.run.customFunction();
</script>
..
</HTML>
I have two google accounts logged in to the same browser emailA#gmail.com (default) and emailB#gmail.com. When using the default account everything works as expected, but while using the second account the custom function called from the templated HTML gives the wrong result.
Case 1 When emailA#gmail.com is active:
Results in onOpen = emailA#gmail.com
Results in customFunction = emailA#gmail.com
Case 2 When emailB#gmail.com is active:
Results in onOpen = emailB#gmail.com
Results in customFunction = emailA#gmail.com
appscript.json
{
"timeZone": "Asia/Calcutta",
"runtimeVersion": "V8",
"dependencies": {
"enabledAdvancedServices": [],
"libraries": []
},
"webapp": {
"access": "ANYONE",
"executeAs": "USER_ACCESSING"
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/presentations.currentonly",
"https://www.googleapis.com/auth/presentations",
"https://www.googleapis.com/auth/userinfo.email"
]
}
This is a multi-account sign-in issue. Please navigate to the corresponding issue on issue tracker and star it, so that Google knows it's important to solve.
Meanwhile a couple of work-arounds have been proposed, which both boil down to alerting the user that there is a multi-account issue:
By Romain Vilard
Comment 117 in the tracker.
The idea is to compare the effective user to the current user, or a document owner to the current user, and if they are not the same, then alert the user that they need to sign out out of other accounts.
That's the best we can do for now.
From the documentation:
getEffectiveUser():
Gets information about the user under whose authority the script is running. If the script is a web app set to "execute as me" (the developer), this returns the developer's user account. If the script is running under an installable trigger, this returns the account of the user who created the trigger. In most other scenarios, this returns the same account as getActiveUser().
So when you call the function from templated HTML, it return the account that own this script (emailA#gmail.com I guess).
You can set Execute as to `User accessing the web-app' if necessary (also reffer to add-on).

Google Apps Script API Authorizations for DriveApp.getFileById

I have a Google Cloud Platform Project (GCP) that runs almost all scripts/functions directly, but a couple through an API. Everything is only accessible internally to the G Suite domain, and the OAuth consent screen Application Type is "Internal".
I have a script, which is not called via an API, but directly on a timer every x minutes. It performs a
DriveApp.getFileByID(pictureID)
This works great! No problems.
I also have a different script, in the same GCP Project, which instead of running triggered by a timer, runs by being called through an API. Here is the line of code that calls it (not really important):
var result = UrlFetchApp.fetch(url, options);
The API script runs great! Until it gets to the following lines:
try { var file = DriveApp.getFileById(pictureID); }
catch (e) {
Logger.log('e = ' + JSON.stringify(e));
return;
}
The result of the log is
e = {"name":"Exception"}
I verified that the pictureID is the same as it is in the non-API script that works. I am doing a "try" in this API-run script to make sure that the software has access to the file, not to actually access it.
I am pretty sure that this is an authorization issue. I've been using GCP only for a little while now, have some experience with authorizations, but not a lot.
Here are some details around the authorizations...
The project's scripts Project Properties (File/Project Properties) shows that it needs the following OAuth Scopes:
According to Google's documentation at https://developers.google.com/apps-script/reference/drive/drive-app#getfilebyidid,
Scripts that use this method require authorization with one or more of the following scopes:
https://www.googleapis.com/auth/drive.readonly
https://www.googleapis.com/auth/drive
Here are the scopes that I've defined now on the GCP Oauth consent screen:
As you can see, I've added drive, drive.readonly, & drive.file (which doesn't really seem to be needed).
On top of it all, this particular image file is stored in the Google Drive of the owner of the GCP Project, scripts, and top-level admin of the G Suite domain. That will not always be the case, as users will be sharing images from their own Google Drive to this software/GCP owner. However, I have a feeling that even now the script triggered by a timer would work with those user-shared files, but not the script called through an API.
I'm pretty sure this is an Auth issue, but I am missing something somewhere.
Thank you for your help!
Update:
Here is the code from the script that CALLS the API script (changed some for confidentiality), as I'm wondering if perhaps the problem may not be on the client/calling side. Perhaps I'm not getting the OAuthToken correctly? Or the token doesn't have the correct permissions?
var token = ScriptApp.getOAuthToken();
var header = {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
};
var parms = [id];
var data = {
"function": "updateSettings",
"parameters": parms,
"devMode": true,
}
var options = {
"method":"POST",
"headers": header,
"muteHttpExceptions": true,
"payload": JSON.stringify(data)
};
// Call the API
var result = UrlFetchApp.fetch(url, options);
I figured it out after contacting Google, and unfortunately not getting much help, and a few more hours of trying and research...
What I was missing was adding "oathScopes" to the manifest/appsscript.json file. I played around with the scopes needed and ended up with the following two shown below. I figured out the solution by looking at this answer: Using AuthToken obtained via ScriptApp.getAuthToken() to call web apps in GAS.
Inside the script editor, go to View/Show manifest file.
Here is what it looked like before:
{
"timeZone": "America/New_York",
"dependencies": {
},
"webapp": {
"access": "MYSELF",
"executeAs": "USER_DEPLOYING"
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
and here is what it looks like now...
{
"timeZone": "America/New_York",
"dependencies": {
},
"webapp": {
"access": "MYSELF",
"executeAs": "USER_DEPLOYING"
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/script.external_request"
]
}
After adding, I saved appsscript.json. I then went to my runOnEdit trigger (the calling script), removed and re-added it. However, that caused a nasty looking error:
I looked that up, and found this post: Receiving error when creating new trigger on Apps Script. So, I ran the code in the script editor, and indeed it brought up the auth screen. I approved, re-added the trigger, and now everything works great in calling the API!

How to not execute "onAddToSpace" when I add a OAUTH scope

I'm trying to create a bot using GoogleChatAPI.
Here is what I am trying to do:
If a User adds the bot that I'm creating, I want the bot to show a message of caution for using the bot.
If a User posts a message in the bot, the bot will post that message to another chat room.
The method of posting a message is "UrlFetchApp.fetch" (and add an OAUTH scope at the same time)
To achieve task 1, I have used the code onAddToSpace.
To achieve task 2, I have used the code onMessage.
But when the Users add the bot, onAddToSpace does not get ignited. This event isn't in the Executions List.
But when I delete the code UrlFetchApp.fetch (and delete an OAUTH scope at the same time), then onAddToSpace gets ignited.
How can I write the codes to achieve the two things I'm trying to do at the same time?
Or is it even possible?
Code:
function onMessage(event) {
var name = "";
var message = "ご依頼承りました!情シスからご連絡いたしますので少々お待ち下さい。";
postICTRoom(event);
return { "text": message };
}
function onAddToSpace(event) {
var message = "情シスへ質問、依頼ができるbotです。送信したメッセージは、ユーザー名付きですべて情シスチームチャットに送信されます。遊びが一切ないbotです、ご利用の際は十分ご注意ください。ここは万事屋ではありません。";
return { "text": message };(*1)
}
function onRemoveFromSpace(event) {
console.info("Bot removed from ",
(event.space.name ? event.space.name : "this chat"));
}
function postICTRoom(event){
var messageDate = new Date()
var url = "https://chat.googleapis.com/v1/spaces/...";
var thread = "spaces/..."
var payload = {
"text" : event.user.displayName + " " + Utilities.formatDate(messageDate, "JST", 'yyyy/MM/dd_HH:mm:ss') + "\n" + event.message.text,
"thread": {
"name": thread
}
}
var json = JSON.stringify(payload);
var options = {
'method': 'POST',
'contentType': 'application/json; charset=UTF-8',
"payload" : json
};
var response = UrlFetchApp.fetch(url, options);
Manifest:
{
"timeZone": "Asia/Tokyo",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"chat": {
}
}
OAuthScope
https://www.googleapis.com/auth/script.external_request
Update
onAddToSpace wasn't executed when I added the bot.
I posted a message and approved OAuth, then onMessage got executed.
After when I approved OAuth, and I once removed the bot, I added it again, onAddToSpace was already executed as I have expected...
I want to know if it is possible and if so, how I can get onAddToSpace executed before I have to approve OAuth.
Update2
Example of onAddToSpace successed(1)
Set scopes script.external_request and chat.
Added the bot.onAddToSpace was not executed at that time, because the log didn't display on screen and the bot didn't post a message(point of (*1) in Code).
Posted a message. onMessage successed. At that time I approved OAuth.
Removed the bot and added it again. onAddToSpace was executed the first time.
Example of onAddToSpace successed(2)
Removed scopes script.external_request and chat. And removed "UrlFetchApp.fetch".
Added the bot. onAddToSpace is executed from the beginning. Of course, It was not OAuth.
Posted a message. onMessage successed.
I'd like to get onAddToSpace to be executed at the point of successed(1)-Step2.
I don't know how I can make this work.
Update3
Manifest(modified):
{
"timeZone": "Asia/Tokyo",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"chat": {
"addToSpaceFallbackMessage": "情シスへ質問、依頼ができるbotです。送信したメッセージは、ユーザー名付きですべて情シスチームチャットに送信されます。遊びが一切ないbotです、ご利用の際は十分ご注意ください。ここは万事屋ではありません。"
}
}
For correct functioning of your code you need to include the necessary OAuth scopes into the manifest
For this please modify your manifest as following:
{
"timeZone": "Asia/Tokyo",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"oauthScopes": ["https://www.googleapis.com/auth/chat", "https://www.googleapis.com/auth/script.external_request"],
"runtimeVersion": "V8",
"chat": {
}
}
UPDATE
I understand that you want to show a custom message before configuring your bot
This is not possible for security reasons
It is part of the Google OAuth authenticatication concept that your bot cannot anything (not even writing a message) before being authorized by the user
As states int he documentation
onAddToSpace(e)
This function is executed when your bot is added to a space.
Before you authorize the access you bot is NOT added to a space
WORKAROUND
You can customize the OAuth2 concent screen in your GCP console
Unfortunately there is no way to add a custom message to the consent screen, but you can include / set it as the Application name:
After doing so, your content screen will look as following:
HOW TO TEST
In order to trigger authorization again
Go on https://myaccount.google.com/security
Third party apps with account access
Remove your bot
Now you can test authorization with the new consent screen

Apps Script Error - "Execution failed: Login Required"

I have the following line of code in my apps script file, followed by a basic onOpen() function to build a menu:
var listContainers = TagManager.Accounts.Containers.list('accounts/' + accountId);
When that line of code is commented out, the menu is created. But if that line is not commented out, the menu is not created, and the script doesn't run.
When I check the execution transcript (View --> Execution transcript), I see the following error associated with the line shown above:
Execution failed: Login Required
Under Resources-->Advanced Google Services, I have turned on the Tag Manager API, and I have also enabled that API in the Google API Console. The accountId is one for a GTM account that I am the owner of.
This is what my manifest file looks like (View --> Show manifest file):
{
"timeZone": "America/Mexico_City",
"oauthScopes": [
"https://www.googleapis.com/auth/script.container.ui",
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/tagmanager.delete.containers",
"https://www.googleapis.com/auth/tagmanager.edit.containers",
"https://www.googleapis.com/auth/tagmanager.edit.containerversions",
"https://www.googleapis.com/auth/tagmanager.manage.users",
"https://www.googleapis.com/auth/tagmanager.publish"
],
"dependencies": {
"enabledAdvancedServices": [{
"userSymbol": "TagManager",
"serviceId": "tagmanager",
"version": "v2"
}]
},
"exceptionLogging": "STACKDRIVER",
"executionApi": {
"access": "ANYONE"
}
}
I feel like all of my grounds are covered here, so I'm not understanding why it is giving me that Login Required error. I have googled up-and-down for a solution, and can't find anything indicating I am doing anything wrong.
Any ideas would be greatly appreciated...thanks!
Global variables can't access external services that require authorization in the same way that simple triggers like onOpen and onEdit as well as custom functions can't access them.
If you really want that listContainers be a global variable You could initialize it at the global scope by doing something like
var listContainers;
Then on a function called by a user interface or an installable trigger assign the tag manager list to that variable in the following way:
listContainers = TagManager.Accounts.Containers.list('accounts/' + accountId);