Facilite o e-mail com os complementos do Google Workspace

1. Visão geral

Neste codelab, você vai usar o Google Apps Script para criar um complemento do Google Workspace para o Gmail. Com ele, os usuários podem adicionar dados de comprovantes de pagamento a uma planilha diretamente no Gmail. Quando um usuário recebe um comprovante por e-mail, ele abre o complemento que recebe automaticamente informações de despesas relevantes por e-mail. O usuário pode editar as informações de despesas e enviá-las para registrar as despesas em uma planilha Google.

O que você vai aprender

  • Criar um complemento do Google Workspace para o Gmail usando o Google Apps Script
  • Analisar um e-mail com o Google Apps Script
  • Interagir com as Planilhas Google pelo Google Apps Script
  • Armazenar valores de usuário usando o serviço de propriedades do Google Apps Script

O que é necessário

  • Acesso à Internet e a um navegador da Web
  • Uma Conta do Google
  • Algumas mensagens, de preferência, recibos por e-mail, no Gmail

2. Fazer o download do exemplo de código

Neste codelab, pode ser útil consultar uma versão do código que você vai criar. O repositório do GitHub contém um exemplo de código que pode ser usado como referência.

Para ver o exemplo de código, na linha de comando, execute:

git clone https://github.com/googleworkspace/gmail-add-on-codelab.git

3. Criar um complemento básico

Comece escrevendo o código de uma versão simples do complemento que exibe um formulário de despesas ao lado de um e-mail.

Primeiro, crie um novo projeto do Apps Script e abra o arquivo de manifesto correspondente.

  1. Acesse script.google.com. Aqui, você pode criar, gerenciar e monitorar seus projetos do Apps Script.
  2. Para criar um novo projeto, clique em Novo projeto no canto superior esquerdo. O novo projeto é aberto com um arquivo padrão chamado Code.gs. Não mexa no Code.gs por enquanto. Você trabalhará com ele mais tarde.
  3. Clique em Projeto sem título, nomeie o projeto como Expense It! e clique em Renomear.
  4. À esquerda, clique em Configurações do projeto Configurações do projeto.
  5. Marque a caixa de seleção Show "appscript.json" arquivo de manifesto no editor".
  6. Clique em Editor Editor.
  7. Para abrir o arquivo de manifesto, clique em appscript.json à esquerda.

Em appscript.json, especifique os metadados associados ao complemento, como o nome e as permissões necessárias. Substitua o conteúdo de appsscript.json por estas configurações:

{
  "timeZone": "GMT",
  "oauthScopes": [
    "https://www.googleapis.com/auth/gmail.addons.execute"
  ],
  "gmail": {
    "name": "Expense It!",
    "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/receipt_black_24dp.png",
    "contextualTriggers": [{
      "unconditional": {
      },
      "onTriggerFunction": "getContextualAddOn"
    }],
    "primaryColor": "#41f470",
    "secondaryColor": "#94f441"
  }
}

Preste atenção especial à parte do manifesto chamada contextualTriggers. Essa parte do manifesto identifica a função definida pelo usuário a ser chamada quando o complemento é ativado pela primeira vez. Nesse caso, ele chama getContextualAddOn, que consegue detalhes sobre o e-mail aberto e retorna um conjunto de cards a serem exibidos ao usuário.

Para criar a função getContextualAddOn, siga estas etapas:

  1. À esquerda, mantenha o ponteiro sobre Code.gs e clique em Menu Menu "Mais" > Renomear.
  2. Digite GetContextualAddOn e pressione a tecla Enter. O Apps Script anexa .gs automaticamente ao nome do arquivo para que você não precise digitar uma extensão. Se você digitar GetContextualAddOn.gs, o Apps Script nomeará o arquivo como GetContextualAddOn.gs.gs.
  3. Em GetContextualAddOn.gs, substitua o código padrão pela função getContextualAddOn:
/**
 * Returns the contextual add-on data that should be rendered for
 * the current e-mail thread. This function satisfies the requirements of
 * an 'onTriggerFunction' and is specified in the add-on's manifest.
 *
 * @param {Object} event Event containing the message ID and other context.
 * @returns {Card[]}
 */
function getContextualAddOn(event) {
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle('Log Your Expense'));

  var section = CardService.newCardSection();
  section.addWidget(CardService.newTextInput()
    .setFieldName('Date')
    .setTitle('Date'));
  section.addWidget(CardService.newTextInput()
    .setFieldName('Amount')
    .setTitle('Amount'));
  section.addWidget(CardService.newTextInput()
    .setFieldName('Description')
    .setTitle('Description'));
  section.addWidget(CardService.newTextInput()
    .setFieldName('Spreadsheet URL')
    .setTitle('Spreadsheet URL'));

  card.addSection(section);

  return [card.build()];
}

A interface do usuário de todos os complementos do Google Workspace consiste em cards divididos em uma ou mais seções, cada um com widgets que podem mostrar e receber informações do usuário. A função getContextualAddOn cria um único cartão que recebe detalhes sobre uma despesa encontrada em um e-mail. O card tem uma seção que contém campos de entrada de texto para dados relevantes. A função retorna uma matriz dos cartões do complemento. Nesse caso, a matriz retornada inclui apenas um cartão.

Antes de implantar o Expense It! você precisa de um projeto do Google Cloud Platform (GCP), que os projetos do Apps Script usam para gerenciar autorizações, serviços avançados e outros detalhes. Para saber mais, acesse Projetos do Google Cloud Platform.

Para implantar e executar o complemento, siga estas etapas:

  1. Abra seu projeto do GCP e copie o número dele.
  2. À esquerda do seu projeto do Apps Script, clique em Configurações do projeto Configurações do projeto.
  3. Em "Projeto do Google Cloud Platform (GCP)", clique em Alterar projeto.
  4. Digite o número do projeto do GCP e clique em Definir projeto.
  5. Clique em Implantar > Testar implantações.
  6. Verifique se o tipo de implantação é Complemento do Google Workspace. Se necessário, na parte de cima da caixa de diálogo, clique em Ativar tipos de implantação Ativar tipos de implantação e selecione Complemento do Google Workspace como o tipo de implantação.
  7. Ao lado de Aplicativos(s): Gmail, clique em Instalar.
  8. Clique em Concluído.

Agora você verá o complemento na sua caixa de entrada do Gmail.

  1. Abra o Gmail no computador.
  2. No painel lateral direito, a guia Expense It! O complemento Despesa! ícone do comprovante é exibido. Talvez seja necessário clicar em Mais complementos Mais complementos para encontrá-lo.
  3. Abra um e-mail, de preferência um recibo com despesas.
  4. Para abrir o complemento, clique em "Expense It!" no painel lateral direito. Despesa! ícone do comprovante.
  5. Dê gastos! acesso à sua Conta do Google, clicando em Autorizar acesso e seguindo as instruções.

O complemento mostra um formulário simples ao lado de uma mensagem aberta no Gmail. Ele ainda não faz mais nada, mas você vai desenvolver as funcionalidades dele na próxima seção.

Para acessar as atualizações do complemento ao longo deste laboratório, basta salvar o código e atualizar o Gmail. Nenhuma outra implantação é necessária.

4. Acessar mensagens de e-mail

Adicione código que busque o conteúdo do e-mail e modularize o código para melhorar a organização.

Ao lado de Files, clique em Adicionar Adicionar um arquivo > Script e crie um arquivo chamado Cards. Crie um segundo arquivo de script chamado Helpers. O Cards.gs cria o card e usa funções de Helpers.gs para preencher campos do formulário com base no conteúdo do e-mail.

Substitua o código padrão em Cards.gs por este:

var FIELDNAMES = ['Date', 'Amount', 'Description', 'Spreadsheet URL'];

/**
 * Creates the main card users see with form inputs to log expenses.
 * Form can be prefilled with values.
 *
 * @param {String[]} opt_prefills Default values for each input field.
 * @param {String} opt_status Optional status displayed at top of card.
 * @returns {Card}
 */
function createExpensesCard(opt_prefills, opt_status) {
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle('Log Your Expense'));
  
  if (opt_status) {
    if (opt_status.indexOf('Error: ') == 0) {
      opt_status = '<font color=\'#FF0000\'>' + opt_status + '</font>';
    } else {
      opt_status = '<font color=\'#228B22\'>' + opt_status + '</font>';
    }
    var statusSection = CardService.newCardSection();
    statusSection.addWidget(CardService.newTextParagraph()
      .setText('<b>' + opt_status + '</b>'));
    card.addSection(statusSection);
  }
  
  var formSection = createFormSection(CardService.newCardSection(),
                                      FIELDNAMES, opt_prefills);
  card.addSection(formSection);
  
  return card;
}

/**
 * Creates form section to be displayed on card.
 *
 * @param {CardSection} section The card section to which form items are added.
 * @param {String[]} inputNames Names of titles for each input field.
 * @param {String[]} opt_prefills Default values for each input field.
 * @returns {CardSection}
 */
function createFormSection(section, inputNames, opt_prefills) {
  for (var i = 0; i < inputNames.length; i++) {
    var widget = CardService.newTextInput()
      .setFieldName(inputNames[i])
      .setTitle(inputNames[i]);
    if (opt_prefills && opt_prefills[i]) {
      widget.setValue(opt_prefills[i]);
    }
    section.addWidget(widget);
  }
  return section;
}

A função createExpensesCard usa uma matriz de valores para preencher automaticamente o formulário como um argumento opcional. A função pode exibir uma mensagem de status opcional, que ficará em vermelho se o status começar com "Error:" e em verde. Em vez de adicionar cada campo ao formulário manualmente, uma função auxiliar chamada createFormSection percorre o processo de criação de widgets de entrada de texto, define cada valor padrão com setValue e adiciona os widgets às respectivas seções no card.

Agora, substitua o código padrão em Helpers.gs por este:

/**
 * Finds largest dollar amount from email body.
 * Returns null if no dollar amount is found.
 *
 * @param {Message} message An email message.
 * @returns {String}
 */
function getLargestAmount(message) {
  return 'TODO';
}

/**
 * Determines date the email was received.
 *
 * @param {Message} message An email message.
 * @returns {String}
 */
function getReceivedDate(message) {
  return 'TODO';
}

/**
 * Determines expense description by joining sender name and message subject.
 *
 * @param {Message} message An email message.
 * @returns {String}
 */
function getExpenseDescription(message) {
  return 'TODO';
}

/**
 * Determines most recent spreadsheet URL.
 * Returns null if no URL was previously submitted.
 *
 * @returns {String}
 */
function getSheetUrl() {
  return 'TODO';
}

As funções em Helpers.gs são chamadas por getContextualAddon para determinar os valores pré-preenchidos no formulário. Por enquanto, essas funções só retornarão a string "TODO" porque você vai implementar a lógica de preenchimento automático em uma etapa posterior.

Em seguida, atualize o código em GetContextualAddon.gs para que ele utilize o código em Cards.gs e Helpers.gs. Substitua o código em GetContextualAddon.gs por este:

/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Returns the contextual add-on data that should be rendered for
 * the current e-mail thread. This function satisfies the requirements of
 * an 'onTriggerFunction' and is specified in the add-on's manifest.
 *
 * @param {Object} event Event containing the message ID and other context.
 * @returns {Card[]}
 */
function getContextualAddOn(event) {
  var message = getCurrentMessage(event);
  var prefills = [getReceivedDate(message),
                  getLargestAmount(message),
                  getExpenseDescription(message),
                  getSheetUrl()];
  var card = createExpensesCard(prefills);

  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);
}

Observe a nova função getCurrentMessage, que usa o evento fornecido pelo Gmail para ler a mensagem aberta no momento do usuário. Para que essa função funcione, adicione outro escopo ao manifesto do script que permita acesso somente leitura às mensagens do Gmail.

No appscript.json, atualize o oauthScopes para que ele também solicite o escopo https://www.googleapis.com/auth/gmail.addons.current.message.readonly.

"oauthScopes": [
  "https://www.googleapis.com/auth/gmail.addons.execute",
   "https://www.googleapis.com/auth/gmail.addons.current.message.readonly"
],

No Gmail, execute seu complemento e autorize o acesso ao Expense It! para ver mensagens de e-mail. Os campos do formulário agora são preenchidos automaticamente com "TODO".

5. Interagir com o Planilhas Google

A despesa! tem um formulário para o usuário inserir detalhes sobre uma despesa, mas esses detalhes não têm para onde ir. Vamos adicionar um botão que envia os dados do formulário para uma planilha Google.

Para adicionar um botão, usaremos a classe ButtonSet. Para interagir com o Planilhas Google, usaremos o serviço Planilhas Google.

Modifique createFormSection para retornar um botão com o rótulo "Enviar". como parte da seção de formulário do cartão. Siga estas etapas:

  1. Crie um botão de texto usando CardService.newTextButton(), rotulando o botão como "Enviar". usando CardService.TextButton.setText().
  2. Crie o botão de forma que, quando ele receber um clique, a seguinte ação submitForm seja chamada usando CardService.TextButton.setOnClickAction():
/**
 * Logs form inputs into a spreadsheet given by URL from form.
 * Then displays edit card.
 *
 * @param {Event} e An event object containing form inputs and parameters.
 * @returns {Card}
 */
function submitForm(e) {
  var res = e['formInput'];
  try {
    FIELDNAMES.forEach(function(fieldName) {
      if (! res[fieldName]) {
        throw 'incomplete form';
      }
    });
    var sheet = SpreadsheetApp
      .openByUrl((res['Spreadsheet URL']))
      .getActiveSheet();
    sheet.appendRow(objToArray(res, FIELDNAMES.slice(0, FIELDNAMES.length - 1)));
    return createExpensesCard(null, 'Logged expense successfully!').build();
  }
  catch (err) {
    if (err == 'Exception: Invalid argument: url') {
      err = 'Invalid URL';
      res['Spreadsheet URL'] = null;
    }
    return createExpensesCard(objToArray(res, FIELDNAMES), 'Error: ' + err).build();
  }
}

/**
 * Returns an array corresponding to the given object and desired ordering of keys.
 *
 * @param {Object} obj Object whose values will be returned as an array.
 * @param {String[]} keys An array of key names in the desired order.
 * @returns {Object[]}
 */
function objToArray(obj, keys) {
  return keys.map(function(key) {
    return obj[key];
  });
}
  1. Crie um widget de conjunto de botões usando CardService.newButtonSet() e adicione o botão de texto ao conjunto de botões com CardService.ButtonSet.addButton().
  2. Adicione o widget do conjunto de botões à seção do formulário do card usando CardService.CardSection.addWidget().

Com apenas algumas linhas de código, podemos abrir uma planilha pelo URL e anexar uma linha de dados a ela. As entradas do formulário são transmitidas para a função como parte do evento e, e verificamos se o usuário preencheu todos os campos. Supondo que não ocorram erros, criamos um cartão de despesas em branco com um status favorável. Quando encontramos um erro, retornamos o cartão preenchido original junto com a mensagem de erro. A função auxiliar objToArray facilita a conversão das respostas do formulário em uma matriz, que pode ser anexada à planilha.

Por fim, atualize a seção oauthScopes em appsscript.json para solicitar novamente o escopo https://www.googleapis.com/auth/spreadsheets. Quando autorizado, esse escopo permite que o complemento leia e modifique as Planilhas Google de um usuário.

"oauthScopes": [
  "https://www.googleapis.com/auth/gmail.addons.execute",
  "https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
  "https://www.googleapis.com/auth/spreadsheets"
],

Se você ainda não criou uma nova planilha, crie uma em https://docs.google.com/spreadsheets/.

Agora, execute o complemento novamente e tente enviar o formulário. Insira o URL completo do URL de destino no campo do formulário URL da planilha.

6. Armazenar valores com o serviço Properties

Muitas vezes, os usuários registram muitas despesas na mesma planilha. Por isso, seria conveniente oferecer o URL da planilha mais recente como valor padrão no cartão. Para saber o URL da planilha mais recente, precisaremos armazenar essa informação toda vez que o complemento for usado.

Com o serviço de propriedades, é possível armazenar pares de chave-valor. Em nosso caso, uma chave razoável seria "SPREADSHEET_URL" enquanto o valor seria o próprio URL. Para armazenar esse valor, modifique submitForm em Cards.gs para que o URL da planilha seja armazenado como uma propriedade ao anexar uma nova linha à página.

Observe que as propriedades podem ter um de três escopos: script, usuário ou documento. O escopo do documento não se aplica aos complementos do Gmail, embora seja relevante para um tipo separado de complemento ao armazenar informações específicas de um determinado documento ou planilha do Google. Para nosso complemento, o comportamento esperado é que uma pessoa veja sua própria planilha mais recente (não a de outra pessoa) como a opção padrão do formulário. Consequentemente, selecionamos o escopo user em vez do escopo script.

Use PropertiesService.getUserProperties().setProperty() para armazenar o URL da planilha. Adicione o seguinte a submitForm em Cards.gs:

PropertiesService.getUserProperties().setProperty('SPREADSHEET_URL', 
    res['Spreadsheet URL']);

Em seguida, modifique a função getSheetUrl no Helpers.gs para retornar a propriedade armazenada, de modo que o usuário veja o URL mais recente sempre que usar o complemento. Use PropertiesService.getUserProperties().getProperty() para receber o valor da propriedade.

/**
 * Determines most recent spreadsheet URL.
 * Returns null if no URL was previously submitted.
 *
 * @returns {String}
 */
function getSheetUrl() {
  return PropertiesService.getUserProperties().getProperty('SPREADSHEET_URL');
}

Por fim, para acessar o serviço de propriedade, o script também precisará ser autorizado. Adicione o escopo https://www.googleapis.com/auth/script.storage ao manifesto como antes para permitir que o complemento leia e grave informações de propriedade.

7. Analisar a mensagem do Gmail

Para realmente salvar os dados vamos preencher o formulário com informações relevantes sobre as despesas do e-mail. Já criamos funções em Helpers.gs que têm esse papel, mas até agora só retornamos "TODO" para a data, o valor e a descrição da despesa.

Por exemplo, podemos obter a data em que o e-mail foi recebido e usá-la como o valor padrão para a data da despesa.

/**
 * Determines date the email was received.
 *
 * @param {Message} message - The message currently open.
 * @returns {String}
 */
function getReceivedDate(message) {
  return message.getDate().toLocaleDateString();
}

Implemente as duas funções restantes:

  1. O getExpenseDescription pode envolver a combinação do nome do remetente e do assunto da mensagem, embora existam maneiras mais sofisticadas de analisar o corpo da mensagem e fornecer uma descrição ainda mais precisa.
  2. Para getLargestAmount, procure símbolos específicos associados a dinheiro. Os comprovantes geralmente têm vários valores listados, como tributos e outras taxas. Pense em como identificar o valor correto. Expressões regulares também podem ser úteis.

Se precisar de mais inspiração, consulte a documentação de referência de GmailMessage ou o código da solução que você transferiu por download no início do codelab. Depois de elaborar suas próprias implementações para todas as funções em Helpers.gs, teste seu complemento. Abra os comprovantes e comece a registrá-los em uma planilha.

8. Limpar o formulário com ações de card

O que acontece se o Expense It! identifica uma despesa incorretamente em um e-mail aberto e preenche o formulário com informações incorretas? O usuário limpa o formulário. Com a classe CardAction, podemos especificar uma função que é chamada quando ocorre um clique na ação. Vamos usá-lo para oferecer ao usuário uma maneira rápida de limpar o formulário.

Modifique createExpensesCard para que o cartão retornado tenha uma ação de card chamada "Limpar formulário". e, quando clicado, chama a seguinte função clearForm, que pode ser colada em Cards.gs. É necessário transmitir opt_status como um parâmetro chamado "Status". à ação para garantir que, quando o formulário for apagado, a mensagem de status permaneça. Os parâmetros opcionais das ações precisam ser do tipo Object.<string, string>. Portanto, se opt_status não estiver disponível, transmita {'Status' : ''}.

/**
 * Recreates the main card without prefilled data.
 *
 * @param {Event} e An event object containing form inputs and parameters.
 * @returns {Card}
 */
function clearForm(e) {
  return createExpensesCard(null, e['parameters']['Status']).build();
}

9. Criar uma planilha

Além de usar o Google Apps Script para editar uma planilha já existente, você pode criar uma planilha totalmente nova de forma programática. Para nosso complemento, vamos permitir que o usuário crie uma planilha para despesas. Para começar, adicione a seguinte seção ao cartão retornado por createExpensesCard.

var newSheetSection = CardService.newCardSection();
var sheetName = CardService.newTextInput()
  .setFieldName('Sheet Name')
  .setTitle('Sheet Name');
var createExpensesSheet = CardService.newAction()
  .setFunctionName('createExpensesSheet');
var newSheetButton = CardService.newTextButton()
  .setText('New Sheet')
  .setOnClickAction(createExpensesSheet);
newSheetSection.addWidget(sheetName);
newSheetSection.addWidget(CardService.newButtonSet().addButton(newSheetButton));
card.addSection(newSheetSection);

Agora, quando o usuário clicar em "Nova planilha" o complemento gera uma nova planilha formatada com uma linha de cabeçalho que é congelada para que fique sempre visível. O usuário especifica um título para a nova planilha no formulário, ainda que incluir um valor padrão caso o formulário esteja em branco possa ser uma boa opção. Na sua implementação de createExpensesSheet, retorne um card quase idêntico ao cartão atual, adicionando uma mensagem de status adequada e preenchendo previamente o campo "URL" com o URL da nova planilha.

10. Parabéns!

Você projetou e implementou um complemento do Gmail que encontra uma despesa em um e-mail e ajuda os usuários a registrá-las em uma planilha em questão de segundos. Você usou o Google Apps Script para interagir com várias APIs do Google e manter dados entre várias execuções do complemento.

Possíveis melhorias

Deixe sua imaginação guiá-lo enquanto você aprimora o Gasto!, mas aqui estão algumas ideias para criar um produto ainda mais útil:

  • Link para a planilha assim que o usuário registrar uma despesa
  • Adicionar a capacidade de editar/desfazer o registro de uma despesa
  • Integrar APIs externas para permitir que os usuários façam pagamentos e solicitem dinheiro

Saiba mais