My add-on will not run on messages in the SPAM folder - google-apps-script

When I try to activate my add-on for a message in the SPAM folder it tells me "Spam and suspicious messages can’t be used for recommended content or actions." How do I make it work?
function getContextualAddOn(event) {
var message = getCurrentMessage(event);
var card = createCard(message);
return [card.build()];
}
/**
* Retrieves the current message given an action event object.
* #param {Event} event Action event object
* #return {Message}
*/
function getCurrentMessage(event) {
var accessToken = event.messageMetadata.accessToken;
var messageId = event.messageMetadata.messageId;
GmailApp.setCurrentMessageAccessToken(accessToken);
return GmailApp.getMessageById(messageId);
}
function createCard(message) {
var emailFrom = message.getHeader("return-path-1");
var card = CardService.newCardBuilder();
card.setHeader(CardService.newCardHeader().setTitle("Forward e-mail"));
var statusSection = CardService.newCardSection();
statusSection.addWidget(CardService.newTextParagraph()
.setText("<b>Sender: </b>" + emailFrom ));
card.addSection(statusSection);
var formArea = CardService.newCardSection();
var widget = CardService.newTextInput()
.setFieldName("forwardTo")
.setTitle("To:");
formArea.addWidget(widget);
card.addSection(formArea);
return card;
}

Gmail add-ons cannot currently handle emails inside the SPAM folder.
There is an open Feature Request in Issue Tracker regarding this functionality:
Allow GMAIL AddOns To Process emails inside SPAM folder
I'd suggest you to star this issue, in order to prioritize it and to keep track of any updates.
Workarounds:
Move the emails outside the SPAM folder, forward them, or what have you, before interacting with them through the add-on.

Related

Google Apps Scripts Update Subject and CC with compose at same time

I would like to open the compose UI and be able to update the draft subject / recipients / and CC all at the same time, not in multiple operations.
The sample code Google gives you doesn't work out of the box, you need to correct the errors. Here is the working code.
/**
* Compose trigger function that fires when the compose UI is
* requested. Builds and returns a compose UI for inserting images.
*
* #param {event} e The compose trigger event object. Not used in
* this example.
* #return {Card[]}
*/
function startApp(e) {
return [buildComposeCard()];
}
/**
* Build a card to display interactive buttons to allow the user to
* update the subject, and To, Cc, Bcc recipients.
*
* #return {Card}
*/
function buildComposeCard() {
var card = CardService.newCardBuilder();
var cardSection = CardService.newCardSection().setHeader('Update email');
cardSection.addWidget(
CardService.newTextButton()
.setText('Update subject')
.setOnClickAction(CardService.newAction()
.setFunctionName('applyUpdateSubjectAction')));
cardSection.addWidget(
CardService.newTextButton()
.setText('Update To recipients')
.setOnClickAction(CardService.newAction()
.setFunctionName('applyUpdateToRecipientsAction')));
cardSection.addWidget(
CardService.newTextButton()
.setText('Update Cc recipients')
.setOnClickAction(CardService.newAction()
.setFunctionName('applyUpdateCcRecipientsAction')));
cardSection.addWidget(
CardService.newTextButton()
.setText('Update Bcc recipients')
.setOnClickAction(CardService.newAction()
.setFunctionName('applyUpdateBccRecipientsAction')));
return card.addSection(cardSection).build();
}
/**
* Updates the subject field of the current email when the user clicks
* on "Update subject" in the compose UI.
*
* Note: This is not the compose action that builds a compose UI, but
* rather an action taken when the user interacts with the compose UI.
*
* #return {UpdateDraftActionResponse}
*/
function applyUpdateSubjectAction() {
// Get the new subject field of the email.
// This function is not shown in this example.
var subject = ['this is a subject'];
var response = CardService.newUpdateDraftActionResponseBuilder()
.setUpdateDraftSubjectAction(CardService.newUpdateDraftSubjectAction()
.addUpdateSubject(subject))
.build();
return response;
}
/**
* Updates the To recipients of the current email when the user clicks
* on "Update To recipients" in the compose UI.
*
* Note: This is not the compose action that builds a compose UI, but
* rather an action taken when the user interacts with the compose UI.
*
* #return {UpdateDraftActionResponse}
*/
function applyUpdateToRecipientsAction() {
// Get the new To recipients of the email.
// This function is not shown in this example.
var toRecipients = ['johhny.appleseed#gmail.com'];
var response = CardService.newUpdateDraftActionResponseBuilder()
.setUpdateDraftToRecipientsAction(CardService.newUpdateDraftToRecipientsAction()
.addUpdateToRecipients(toRecipients))
.build();
return response;
}
/**
* Updates the Cc recipients of the current email when the user clicks
* on "Update Cc recipients" in the compose UI.
*
* Note: This is not the compose action that builds a compose UI, but
* rather an action taken when the user interacts with the compose UI.
*
* #return {UpdateDraftActionResponse}
*/
function applyUpdateCcRecipientsAction() {
// Get the new Cc recipients of the email.
// This function is not shown in this example.
var ccRecipients = ['big.blue#montana.com'];
var response = CardService.newUpdateDraftActionResponseBuilder()
.setUpdateDraftCcRecipientsAction(CardService.newUpdateDraftCcRecipientsAction()
.addUpdateCcRecipients(ccRecipients))
.build();
return response;
}
/**
* Updates the Bcc recipients of the current email when the user clicks
* on "Update Bcc recipients" in the compose UI.
*
* Note: This is not the compose action that builds a compose UI, but
* rather an action taken when the user interacts with the compose UI.
*
* #return {UpdateDraftActionResponse}
*/
function applyUpdateBccRecipientsAction() {
// Get the new Bcc recipients of the email.
// This function is not shown in this example.
var bccRecipients = ['spacer#gmail.com'];
var response = CardService.newUpdateDraftActionResponseBuilder()
.setUpdateDraftBccRecipientsAction(CardService.newUpdateDraftBccRecipientsAction()
.addUpdateBccRecipients(bccRecipients))
.build();
return response;
}
When that code is opened, it looks like this compose UI display.
However
these links only work one at a time and close the screen. You have to perform all the actions in multiple moves. I would like it to be able to perform more then one action at a time.
For example, if I click "Update Subject", the action works and the compose UI screen closes, but I don't want to open the add-on a second time to add the CC email address.
I believe your goal as follows.
You want to run these functions of applyUpdateSubjectAction(), applyUpdateToRecipientsAction(), applyUpdateCcRecipientsAction() and applyUpdateBccRecipientsAction() by one click at the dialog.
In this case, how about the following modification?
Modified script:
Please modify buildComposeCard() as follows.
function buildComposeCard() {
var card = CardService.newCardBuilder();
var cardSection = CardService.newCardSection().setHeader('Update email');
cardSection.addWidget(CardService.newTextButton().setText('Update email').setOnClickAction(CardService.newAction().setFunctionName('applyUpdateEmail')));
return card.addSection(cardSection).build();
}
And, please add the following function.
function applyUpdateEmail() {
var subject = ['this is a subject'];
var toRecipients = ['johhny.appleseed#gmail.com'];
var ccRecipients = ['big.blue#montana.com'];
var bccRecipients = ['spacer#gmail.com'];
return CardService.newUpdateDraftActionResponseBuilder()
.setUpdateDraftSubjectAction(CardService.newUpdateDraftSubjectAction().addUpdateSubject(subject))
.setUpdateDraftToRecipientsAction(CardService.newUpdateDraftToRecipientsAction().addUpdateToRecipients(toRecipients))
.setUpdateDraftCcRecipientsAction(CardService.newUpdateDraftCcRecipientsAction().addUpdateCcRecipients(ccRecipients))
.setUpdateDraftBccRecipientsAction(CardService.newUpdateDraftBccRecipientsAction().addUpdateBccRecipients(bccRecipients))
.build();
}
In this modification, you can see "Update email" at the opened dialog. When you click it, addUpdateSubject, addUpdateToRecipients, addUpdateCcRecipients and addUpdateBccRecipients are run.
Note:
When you modified the Google Apps Script, please modify the deployment as new version. By this, the modified script is reflected to the add-on. Please be careful this.
References:
Class UpdateDraftActionResponse
Class UpdateDraftActionResponseBuilder

Handling persistent user-specific values in Gmail Add-ons

I have created simple Gmail addon, now I'm struggling with below strategies.
After installing the addon, when first inbox is opened, we ask basic info input from users, when he opens second mail we won't ask basic info details again.
How I can handle this?
I have tried property service but no luck.
Update
var MAX_THREADS = 5;
/**
* Returns the array of cards that should be rendered for the current
* e-mail thread. The name of this function is specified in the
* manifest 'onTriggerFunction' field, indicating that this function
* runs every time the add-on is started.
*
* #param {Object} e data provided by the Gmail UI.
* #returns {Card[]}
*/
function buildAddOn(e) {
// Activate temporary Gmail add-on scopes.
//Logger.log('E', Session.getActiveUser());
var accessToken = e.messageMetadata.accessToken;
GmailApp.setCurrentMessageAccessToken(accessToken);
var userProperties = PropertiesService.getUserProperties();
var Token = userProperties.getProperty('Token');
Logger.log('Token value:',typeof Token);
if(Token != null ){
var messageId = e.messageMetadata.messageId;
var senderData = extractSenderData(messageId);
var cards = [];
// Build a card for each recent thread from this email's sender.
if (senderData.recents.length > 0) {
senderData.recents.forEach(function(threadData) {
cards.push(buildRecentThreadCard(senderData.email, threadData));
});
} else {
// Present a blank card if there are no recent threads from
// this sender.
cards.push(CardService.newCardBuilder()
.setHeader(CardService.newCardHeader()
.setTitle('No recent threads from this sender')).build());
}
return cards;
}
else{
var cards = []
var login_card = build_login_card()
cards.push(login_card);
return cards;
}
}
/**
* This function builds a set of data about this sender's presence in your
* inbox.
*
* #param {String} messageId The message ID of the open message.
* #return {Object} a collection of sender information to display in cards.
*/
function extractSenderData(messageId) {
// Use the Gmail service to access information about this message.
var mail = GmailApp.getMessageById(messageId);
var threadId = mail.getThread().getId();
var senderEmail = extractEmailAddress(mail.getFrom());
var recentThreads = GmailApp.search('from:' + senderEmail);
var recents = [];
// Retrieve information about up to 5 recent threads from the same sender.
recentThreads.slice(0,MAX_THREADS).forEach(function(thread) {
if (thread.getId() != threadId && ! thread.isInChats()) {
recents.push({
'subject': thread.getFirstMessageSubject(),
'count': thread.getMessageCount(),
'link': 'https://mail.google.com/mail/u/0/#inbox/' + thread.getId(),
'lastDate': thread.getLastMessageDate().toDateString()
});
}
});
var senderData = {
"email": senderEmail,
'recents': recents
};
return senderData;
}
/**
* Given the result of GmailMessage.getFrom(), extract only the email address.
* getFrom() can return just the email address or a string in the form
* "Name <myemail#domain>".
*
* #param {String} sender The results returned from getFrom().
* #return {String} Only the email address.
*/
function extractEmailAddress(sender) {
var regex = /\<([^\#]+\#[^\>]+)\>/;
var email = sender; // Default to using the whole string.
var match = regex.exec(sender);
if (match) {
email = match[1];
}
return email;
}
/**
* Builds a card to display information about a recent thread from this sender.
*
* #param {String} senderEmail The sender email.
* #param {Object} threadData Infomation about the thread to display.
* #return {Card} a card that displays thread information.
*/
function buildRecentThreadCard(senderEmail, threadData) {
var card = CardService.newCardBuilder();
card.setHeader(CardService.newCardHeader().setTitle(threadData.subject));
var section = CardService.newCardSection()
.setHeader("<font color=\"#1257e0\">Recent thread</font>");
section.addWidget(CardService.newTextParagraph().setText(threadData.subject));
section.addWidget(CardService.newKeyValue()
.setTopLabel('Sender')
.setContent(senderEmail));
section.addWidget(CardService.newKeyValue()
.setTopLabel('Number of messages')
.setContent(threadData.count.toString()));
section.addWidget(CardService.newKeyValue()
.setTopLabel('Last updated')
.setContent(threadData.lastDate.toString()));
var threadLink = CardService.newOpenLink()
.setUrl(threadData.link)
.setOpenAs(CardService.OpenAs.FULL_SIZE);
var button = CardService.newTextButton()
.setText('Open Thread')
.setOpenLink(threadLink);
section.addWidget(CardService.newButtonSet().addButton(button));
card.addSection(section);
return card.build();
}
function build_login_card(){
var card = CardService.newCardBuilder();
card.setHeader(CardService.newCardHeader().setTitle("Login Here"));
var userProperties = PropertiesService.getUserProperties();
var Token = userProperties.setProperty('Token',"Token");
return card.build()
}
According to comments, the primary issue here is that uninstalling the add-on does not remove bits from the UserProperties datastore, and thus if the add-on is re-installed, the add-on configuration steps cannot be performed again.
Generic Add-on Solution
If this were not a Gmail add-on, this could be simply solved with a time-based trigger, since per documentation:
Add-on triggers will stop firing in any of the following situations:
- If the add-on is uninstalled by the user
- If the add-on is disabled in a document (if it is re-enabled, the trigger will become operational again)
- If the developer unpublishes the add-on or submits a broken version to the add-on store
I.e., you would simply write the day's date to a key (e.g. LAST_SEEN) in PropertiesService#UserProperties every day, and then check both TOKEN and LAST_SEEN when deciding which card to display.
Gmail Add-on Solution:
Per Gmail add-on documentation, you cannot create or use Apps Script simple / installable triggers in a Gmail add-on. So, the easiest implementation of the solution is not available. However, we can still use this approach, by moving the region in which that LAST_SEEN key is updated.
Right now (2018 March), the only available trigger for a Gmail add-on is the contextual trigger unconditional:
Currently, the only contextual trigger type available is unconditional, which triggers for all emails regardless of content.
So, if you bind to this contextual trigger, while your add-on is installed it will run a function whenever the user opens an email. The solution is then for your contextually triggered function to include the snippet
PropertiesService.getUserProperties().setProperty("LAST_SEEN", String(new Date().getTime()));
If you have other stuff to do in your contextually triggered function, that code will be uninfluenced by this addition. If you don't have a contextually triggered function, then you'd want to return [] (an empty card stack) in order to avoid showing any UI.
To use this new property, in your buildAddon(e) method you want to query for this value in addition to the TOKEN property you are using:
var userProps = PropertiesService.getUserProperties().getAll();
var Token = userProps["Token"];
var lastSeen = userProps["LAST_SEEN"] || 0; // If found, will be milliseconds since epoch.
var absence = new Date().getTime() - lastSeen; // Time in ms since last use of add-on.
if (Token == null || absence > /* some duration you choose */ ) {
// New install, or user has started using app again.
return [build_login_card()];
} else {
// User is still using add-on, so do normal stuff.
}
This is obviously not a perfect solution (i.e. an uninstall contextual trigger would be much better), but can help detect lack-of-use situations
There are rate limits on how often you can write to PropertiesService. If you have speedy/"power" users, they might trip quotas.
Could combine CacheService and PropertiesService to handle frequent reads in a "short" session (of up to 6hr since last storage into cache).
Robert , have you tried caching the user input ?
I do caching in the event handler.
function onDomainChange(e){
var cache = CacheService.getScriptCache();
Logger.log(e.formInput);
cache.put('domain',e.formInput.domain);
Logger.log(cache.get('domain'));
}
Refer cache docs

How to Embed a Google Form in Email in Google App Script

I have a form that requires login and I am trying to fetch its html to embed in an email. Similar to the question Google Apps Script: how to access or call "Send this form to others"?
var form = FormApp.create('New Form');
form.setRequireLogin(true);
...
var url = form.getPublishedUrl();
var response = UrlFetchApp.fetch(url);
var htmlBody = HtmlService.createHtmlOutput(response).getContent();
MailApp.sendEmail({
to: email,
subject: subject,
htmlBody: htmlBody,
});
...
Howerver, the URLFetchApp does not seem to have the correct OAuth configuration and I always get the HTML for Google Login page.
Is there a way to set the correct OAuth parameters to get the form HTML?
Your question asks about the OAuth parameters required to allow UrlFetch() to obtain the HTML content of a form with requiresLogin() set. This answer doesn't address that directly.
Without requiring OAuth, you can briefly change the login requirement for the form, just long enough to grab the HTML from it. For a small amount of time, your form could be accessed by individuals outside of your domain, if they happened to have the URL, and filled the form fast enough to submit their response before you locked the form up again.
Script
The following sendForm() function will work for consumer & GApps domain accounts, whether or not requiresLogin() is set.
/**
* Send user an email containing the given form, in HTML.
*
* #param {Form} form Form object.
* #param {String} email One or more email addresses, comma separated.
*/
function sendForm(form,email) {
var url = form.getPublishedUrl();
// Temporarily disable requiresLogin so UrlFetch will operate
if (form.requiresLogin()) {
var requiresLogin = true;
form.setRequireLogin(false);
}
// Fetch form's HTML
var response = UrlFetchApp.fetch(url);
var htmlBody = HtmlService.createHtmlOutput(response).getContent();
// Re-enable requireLogin, if necessary
if (requiresLogin) {
form.setRequireLogin(true);
}
var subject = form.getTitle();
MailApp.sendEmail(email,
subject,
'This message requires HTML support to view.',
{
name: 'Form Emailer Script',
htmlBody: htmlBody
});
}
For completeness, here's a test function...
function test_sendForm() {
// Build new form for testing
var form = FormApp.create('New Form');
var formTitle = 'Form Name';
form.setTitle(formTitle)
.setDescription('Description of form')
.setConfirmationMessage('Thanks for responding!')
.setAllowResponseEdits(true)
.setAcceptingResponses(true)
// Require Login (for GApp Domain accounts only)
try {
form.setRequireLogin(true);
} catch (e) {
// Error is expected for consumer accounts - carry on.
}
// Just one question
form.addTextItem().setTitle("Q1");
// Send it to self
var email = Session.getEffectiveUser().getEmail();
sendForm(form,email)
}

(Gmail) Sending emails from spreadsheet. How to add signature with image?

Due to the large amount of emails i'm sending with GMAIL i decided to automatize this process using a script and following this tutorial.Tutorial: Sending emails from a Spreadsheet
The "Message" is being generated by another function i created, called prepareEmails.
The problems are the following:
1) How can i tell prepareEmails to add my personal signature? I can't simply copy its text into that function, because my signature contains an image (for which i have the URL), and i want that image to be into the signature.
2) How can i make my signature BOLD?
Thanks everybody
There is an open Issue 2441 requesting the ability to append gmail signatures to email when using the GMailService. Visit and star it to receive updates.
As #wchiquito suggests, you can craft a script to attach images, producing a signature. You can also use HTML tags such as <B></B> to render text in bold, and so on.
Here's a different approach that will instead use a draft email as a template. This way, you can produce your signature with a variety of fonts and images using the online editor, and end up with a capability similar to automatic signature insertion.
The template needs to be saved in your Drafts folder, and it needs to have a tag indicating where the body of emails should go.
Example
function sendWithTemplate() {
var msgBody = "Test of sending a message using a template with a signature.";
sendGmailTemplate(Session.getActiveUser().getEmail(), 'test', msgBody );
}
Script
/**
* Insert the given email body text into an email template, and send
* it to the indicated recipient. The template is a draft message with
* the subject "TEMPLATE"; if the template message is not found, an
* exception will be thrown. The template must contain text indicating
* where email content should be placed: {BODY}.
*
* #param {String} recipient Email address to send message to.
* #param {String} subject Subject line for email.
* #param {String} body Email content, may be plain text or HTML.
* #param {Object} options (optional) Options as supported by GmailApp.
*
* #returns GmailApp the Gmail service, useful for chaining
*/
function sendGmailTemplate(recipient, subject, body, options) {
options = options || {}; // default is no options
var drafts = GmailApp.getDraftMessages();
var found = false;
for (var i=0; i<drafts.length && !found; i++) {
if (drafts[i].getSubject() == "TEMPLATE") {
found = true;
var template = drafts[i];
}
}
if (!found) throw new Error( "TEMPLATE not found in drafts folder" );
// Generate htmlBody from template, with provided text body
var imgUpdates = updateInlineImages(template);
options.htmlBody = imgUpdates.templateBody.replace('{BODY}', body);
options.attachments = imgUpdates.attachments;
options.inlineImages = imgUpdates.inlineImages;
return GmailApp.sendEmail(recipient, subject, body, options);
}
/**
* This function was adapted from YetAnotherMailMerge by Romain Vaillard.
* Given a template email message, identify any attachments that are used
* as inline images in the message, and move them from the attachments list
* to the inlineImages list, updating the body of the message accordingly.
*
* #param {GmailMessage} template Message to use as template
* #returns {Object} An object containing the updated
* templateBody, attachments and inlineImages.
*/
function updateInlineImages(template) {
//////////////////////////////////////////////////////////////////////////////
// Get inline images and make sure they stay as inline images
//////////////////////////////////////////////////////////////////////////////
var templateBody = template.getBody();
var rawContent = template.getRawContent();
var attachments = template.getAttachments();
var regMessageId = new RegExp(template.getId(), "g");
if (templateBody.match(regMessageId) != null) {
var inlineImages = {};
var nbrOfImg = templateBody.match(regMessageId).length;
var imgVars = templateBody.match(/<img[^>]+>/g);
var imgToReplace = [];
if(imgVars != null){
for (var i = 0; i < imgVars.length; i++) {
if (imgVars[i].search(regMessageId) != -1) {
var id = imgVars[i].match(/realattid=([^&]+)&/);
if (id != null) {
var temp = rawContent.split(id[1])[1];
temp = temp.substr(temp.lastIndexOf('Content-Type'));
var imgTitle = temp.match(/name="([^"]+)"/);
if (imgTitle != null) imgToReplace.push([imgTitle[1], imgVars[i], id[1]]);
}
}
}
}
for (var i = 0; i < imgToReplace.length; i++) {
for (var j = 0; j < attachments.length; j++) {
if(attachments[j].getName() == imgToReplace[i][0]) {
inlineImages[imgToReplace[i][2]] = attachments[j].copyBlob();
attachments.splice(j, 1);
var newImg = imgToReplace[i][1].replace(/src="[^\"]+\"/, "src=\"cid:" + imgToReplace[i][2] + "\"");
templateBody = templateBody.replace(imgToReplace[i][1], newImg);
}
}
}
}
var updatedTemplate = {
templateBody: templateBody,
attachments: attachments,
inlineImages: inlineImages
}
return updatedTemplate;
}
Credit where credit is due: The "Yet Another Mail Merge" script includes code that preserves inline images in emails during a mail merge - I've borrowed from that. Thanks Romain!
Currently the API does not offer anything to include the signature to messages, however, if you have control of the signature, you can use the method sendEmail(recipient, subject, body, options) available in classes GmailApp/MailApp. The options parameter allows you to set additional parameters to get what you need, for example, include images, with which the signature can build manually, and sets text in bold.
I invite you to take a look at the documentation/examples and post any questions you may have. An interesting example can be found here.

Determine current user in Apps Script

I'm trying to identify current user's name to make notes of who edited what like this:
r.setComment("Edit at " + (new Date()) + " by " + Session.getActiveUser().getEmail());
but it won't work - user's name is an empty string.
Where did I go wrong?
GOOD NEWS: It's possible with this workaround!
I'm using some protection functionality that reveals the user and owner of the document and I'm storing it in the properties for better performance. Have fun with it!
function onEdit(e) {
SpreadsheetApp.getUi().alert("User Email is " + getUserEmail());
}
function getUserEmail() {
var userEmail = PropertiesService.getUserProperties().getProperty("userEmail");
if(!userEmail) {
var protection = SpreadsheetApp.getActive().getRange("A1").protect();
// tric: the owner and user can not be removed
protection.removeEditors(protection.getEditors());
var editors = protection.getEditors();
if(editors.length === 2) {
var owner = SpreadsheetApp.getActive().getOwner();
editors.splice(editors.indexOf(owner),1); // remove owner, take the user
}
userEmail = editors[0];
protection.remove();
// saving for better performance next run
PropertiesService.getUserProperties().setProperty("userEmail",userEmail);
}
return userEmail;
}
I suppose you have this piece of code set to execute inside an onEdit function (or an on edit trigger).
If you are on a consumer account, Session.getActiveUser().getEmail() will return blank. It will return the email address only when both the author of the script and the user are on the same Google Apps domain.
I had trouble with Wim den Herder's solution when I used scripts running from triggers. Any non script owner was unable to edit a protected cell. It worked fine if the script was run from a button. However I needed scripts to run periodically so this was my solution:
When a user uses the sheet the first time he/she should click a button and run this:
function identifyUser(){
var input = Browser.inputBox('Enter User Id which will be used to save user to events (run once)');
PropertiesService.getUserProperties().setProperty("ID", input);
}
This saves the user's input to a user property. It can be read back later at any time with this code:
var user = PropertiesService.getUserProperties().getProperty("ID");
In this code you can use a cell for input. Authorising scripts are not required.
function onEdit(e){
checkUsername(e);
}
function checkUsername(e){
var sheet = e.source.getActiveSheet();
var sheetToCheck = 'Your profile';
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetToCheck);
var CellInputUsername = 'B4';
var ActiveCell = SpreadsheetApp.getActive().getActiveRange().getA1Notation();
if (sheet.getName() !== sheetToCheck || ActiveCell !== CellInputUsername){return;}
var cellInput = sheetName.getRange(CellInputUsername).getValue();
PropertiesService.getUserProperties().setProperty("Name", cellInput);
// Make cell empty again for new user
sheetName.getRange(CellInputUsername).setValue("");
var Username = PropertiesService.getUserProperties().getProperty("Name");
SpreadsheetApp.getUi().alert("Hello " + Username);
}