1. Visão geral
Neste codelab, você vai usar o Google Apps Script para escrever um complemento do Google Workspace para o Gmail que permite aos usuários adicionar dados de recibos de um e-mail a uma planilha diretamente no Gmail. Quando um usuário recebe um recibo por e-mail, ele abre o complemento, que recebe automaticamente as informações relevantes de despesas do e-mail. O usuário pode editar as informações de despesas e enviá-las para registrar os gastos em uma planilha Google Planilhas.
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 o Google Planilhas usando o Google Apps Script
- Armazenar valores de usuário usando o serviço Properties 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 de e-mail, no Gmail
2. Acessar o exemplo de código
Ao trabalhar neste codelab, pode ser útil consultar uma versão funcional do código que você vai escrever. O repositório do GitHub contém um exemplo de código que pode ser usado como referência.
Para receber o exemplo de código, execute o seguinte na linha de comando:
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 mostra um formulário de despesas ao lado de um e-mail.
Primeiro, crie um projeto do Apps Script e abra o arquivo de manifesto.
- Acesse script.google.com. Lá, você pode criar, gerenciar e monitorar seus projetos do Apps Script.
- Para criar um projeto, clique em Novo projeto no canto superior esquerdo. O novo projeto é aberto com um arquivo padrão chamado
Code.gs. DeixeCode.gspor enquanto. Você vai trabalhar com ele mais tarde. - Clique em Projeto sem título, nomeie o projeto como Expense It! e clique em Renomear.
- À esquerda, clique em Configurações do projeto
. - Marque a caixa de seleção Mostrar arquivo de manifesto "appscript.json" no editor.
- Clique em Editor
. - Para abrir o arquivo de manifesto, clique em
appscript.jsonà esquerda.
Em appscript.json, especifique os metadados associados ao complemento, como nome e 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 recebe detalhes sobre o e-mail aberto e retorna um conjunto de cards para mostrar ao usuário.
Para criar a função getContextualAddOn, siga estas etapas:
- À esquerda, passe o cursor sobre
Code.gse clique em Menu
> Renomear. - Digite
GetContextualAddOne pressione a teclaEnter. O Apps Script adiciona automaticamente.gsao nome do arquivo, então você não precisa digitar uma extensão. Se você digitarGetContextualAddOn.gs, o Apps Script vai nomear o arquivo comoGetContextualAddOn.gs.gs. - Em
GetContextualAddOn.gs, substitua o código padrão pela funçãogetContextualAddOn:
/**
* 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 de usuário de cada complemento do Google Workspace consiste em cards divididos em uma ou mais seções, cada uma contendo widgets que podem mostrar e receber informações do usuário. A função getContextualAddOn cria um único card com detalhes sobre uma despesa encontrada em um e-mail. O card tem uma seção com campos de entrada de texto para dados relevantes. A função retorna uma matriz dos cards do complemento. Nesse caso, a matriz retornada inclui apenas um card.
Antes de implantar o complemento 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. Saiba mais em Projetos do Google Cloud Platform.
Para implantar e executar o complemento, siga estas etapas:
- Abra seu projeto do GCP e copie o número dele.
- No projeto do Apps Script, à esquerda, clique em Configurações do projeto
. - Em "Projeto do Google Cloud Platform (GCP)", clique em Mudar projeto.
- Insira o número do projeto do GCP e clique em Definir projeto.
- Clique em Implantar > Testar implantações.
- 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
e selecione Complemento do Google Workspace como o tipo de implantação. - Ao lado de Aplicativos: Gmail, clique em Instalar.
- Clique em Concluído.
Agora você pode ver o complemento na caixa de entrada do Gmail.
- Abra o Gmail no computador.
- No painel lateral à direita, o Expense It! O complemento
aparece. Talvez seja necessário clicar em Mais complementos
para encontrar essa opção. - Abra um e-mail, de preferência um recibo com despesas.
- Para abrir o complemento, clique em "Expense It!" no painel lateral à direita.
. - Clique em Autorizar acesso e siga as instruções para conceder acesso à sua Conta do Google.
O complemento mostra um formulário simples ao lado de uma mensagem aberta do Gmail. Ele ainda não faz mais nada, mas você vai criar a funcionalidade dele na próxima seção.
Para conferir as atualizações do complemento enquanto continua este laboratório, basta salvar o código e atualizar o Gmail. Nenhum outro deployment é necessário.
4. Acessar mensagens de e-mail
Adicione um código que busque o conteúdo do e-mail e modularize o código para um pouco mais de organização.
Ao lado de "Arquivos", clique em Adicionar
> 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 do Helpers.gs para preencher os 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 pré-preencher o formulário como um argumento opcional. A função pode mostrar uma mensagem de status opcional, que fica vermelha se o status começar com "Error:" e verde caso contrário. Em vez de adicionar cada campo ao formulário manualmente, uma função auxiliar chamada createFormSection faz um loop no 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 vão retornar apenas a string "TODO", porque você vai implementar a lógica de pré-preenchimento em uma etapa posterior.
Em seguida, atualize o código em GetContextualAddon.gs para que ele aproveite 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 do usuário. Para que essa função funcione, adicione um escopo extra ao manifesto do script que permita acesso somente leitura às mensagens do Gmail.
Em 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 o complemento e autorize o acesso do Expense It! para ver mensagens de e-mail. Os campos do formulário agora estão pré-preenchidos com "TODO".
5. Interagir com o Google Planilhas
O complemento Expense It! 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, vamos usar a classe ButtonSet. Para interagir com o Google Planilhas, vamos usar o serviço do Google Planilhas.
Modifique createFormSection para retornar um botão chamado "Enviar" como parte da seção de formulário do card. Siga estas etapas:
- Crie um botão de texto usando
CardService.newTextButton()e rotule-o como "Enviar" usandoCardService.TextButton.setText(). - Projete o botão de forma que, quando ele for clicado, a seguinte ação
submitFormseja chamada viaCardService.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];
});
}
- Crie um widget de conjunto de botões usando
CardService.newButtonSet()e adicione o botão de texto ao conjunto comCardService.ButtonSet.addButton(). - Adicione o widget de conjunto de botões à seção de formulário do card usando
CardService.CardSection.addWidget().
Com apenas algumas linhas de código, é possível abrir uma planilha pelo URL e adicionar uma linha de dados a essa planilha. 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 card de despesas em branco com um status favorável. Se encontrarmos um erro, vamos retornar o card preenchido original 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 novamente e solicite 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 tiver criado uma planilha, acesse 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 várias despesas na mesma planilha. Por isso, é conveniente oferecer o URL da planilha mais recente como um valor padrão no card. Para saber o URL da planilha mais recente, precisamos armazenar essas informações sempre que o complemento for usado.
O serviço de propriedades permite armazenar pares de chave-valor. No nosso caso, uma chave razoável seria "SPREADSHEET_URL", e 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.
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 Google. Para nosso complemento, o comportamento desejado é que uma pessoa veja a planilha mais recente dela (e não de outra pessoa) como a opção padrão no formulário. Por isso, 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 em Helpers.gs para retornar a propriedade armazenada. Assim, o usuário vai ver 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 precisa 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 economizar ainda mais o tempo dos usuários, vamos preencher o formulário com informações relevantes sobre a despesa do e-mail. Já criamos funções em Helpers.gs que desempenham essa função, mas até agora só retornamos "TODO" para a data, o valor e a descrição da despesa.
Por exemplo, podemos receber a data em que o e-mail foi recebido e usar isso 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:
getExpenseDescriptionpode envolver a junçã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.- Para
getLargestAmount, procure símbolos específicos associados a dinheiro. Os recibos geralmente têm vários valores listados, como tributos e outras taxas. Pense em como você pode identificar o valor correto. As expressões regulares também podem ser úteis.
Se precisar de mais inspiração, confira a documentação de referência para GmailMessage ou o código da solução que você baixou no início do codelab. Depois de criar suas próprias implementações para todas as funções em Helpers.gs, teste o complemento. Abra os recibos e comece a registrar tudo em uma planilha.
8. Limpar o formulário com ações do card
O que acontece se o Expense It! identificar incorretamente uma despesa em um e-mail aberto e preencher o formulário com informações incorretas? O usuário limpa o formulário. A classe CardAction permite especificar uma função que é chamada quando a ação é clicada. Vamos usá-lo para dar ao usuário uma maneira rápida de limpar o formulário.
Modifique createExpensesCard para que o card retornado tenha uma ação com o rótulo "Limpar formulário". Quando clicado, ele chama a seguinte função clearForm, que pode ser colada em Cards.gs. Você precisará transmitir opt_status como um parâmetro chamado "Status" para a ação. Assim, quando o formulário for limpo, a mensagem de status vai permanecer. Os parâmetros opcionais para 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, você pode criar uma nova de forma programática. Para nosso complemento, vamos permitir que o usuário crie uma planilha de despesas. Para começar, adicione a seção de card a seguir ao card que createExpensesCard retorna.
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 clica no botão "Nova planilha", o complemento gera uma planilha formatada com uma linha de cabeçalho congelada para que esteja sempre visível. O usuário especifica um título para a nova planilha no formulário, mas incluir um valor padrão caso o formulário esteja em branco pode ser uma boa opção. Na sua implementação de createExpensesSheet, retorne um card quase idêntico ao existente, com a adição de uma mensagem de status adequada e o preenchimento do campo de URL com o URL da nova planilha.
10. Parabéns!
Você criou e implementou um complemento do Gmail que encontra uma despesa em um e-mail e ajuda os usuários a registrar essa despesa em uma planilha em questão de segundos. Você usou o Google Apps Script para interagir com várias APIs do Google e persistiu dados entre várias execuções do complemento.
Possíveis melhorias
Deixe sua imaginação guiar você ao aprimorar o Expense It!, mas aqui estão algumas ideias para criar um produto ainda mais útil:
- Link para a planilha depois 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