
Compartilhar:
Karl is a writer, speaker, and technology team lead. He’s currently the Chief Technology Officer at The Graide Network and runs CFP Land in his spare time.
Crie uma ferramenta de lembretes por SMS para professores usando o Google Classroom
Tempo de leitura: 8 minutos
Trabalho com tecnologia educacional há vários anos, e um desafio que os professores frequentemente mencionam é que os alunos não verificam seus Accounts de e-mail. Existem empresas inteiras, como Remind, criadas justamente para resolver esse problema de comunicação.
Neste passo a passo, vamos criar um aplicativo de lembretes por SMS que permite que os professores lembrem seus alunos sobre as próximas tarefas no Google Classroom. Usaremos a API do Google Classroom para realizar a autenticação e obter dados sobre cursos e tarefas, e a Messages API do Vonage para enviar as mensagens de texto que os professores enviam aos alunos.
Planejamento das Applications
Antes de começarmos, vamos entender a funcionalidade principal e a arquitetura do nosso aplicativo. Neste tutorial, abordaremos três histórias de usuário:
Os professores podem fazer login em nosso aplicativo usando seu account do Google.
Os professores podem ver uma lista de suas tarefas mais recentes e selecionar aquela sobre a qual desejam lembrar os alunos.
Os professores podem avisar cada aluno por SMS sobre a próxima tarefa.
Vamos analisar o fluxo de dados entre nosso aplicativo e as duas Applications de suporte (Google Classroom e Vonage Messages):

Vamos usar Node e Express para esta demonstração, mas tanto o Google quanto a Vonage oferecem clientes de API na maioria das principais linguagens de programação. Se você quiser pular essa parte, pode baixar o código no GitHub e seguir a seção “Quick Start” no arquivo Readme para colocar o aplicativo em funcionamento.
Pré-requisitos
A Account de APIs do Google e um projeto com credenciais de cliente OAuth 2.0
A Account do Google Classroom com uma turma que tenha vários alunos e pelo menos uma tarefa
Criação do aplicativo
Etapa 1: Criação de um novo aplicativo Express
Primeiro, vamos criar um novo aplicativo Express que utilize Handlebars para a geração de modelos e express-session para armazenamento de sessão. Usaremos o gerador de aplicativos Express para facilitar isso:
npx express-generator --hbs --git classroom-remindersEste comando criará um novo diretório chamado classroom-reminders com um aplicativo Express padrão dentro dele. Vamos acessar esse diretório e instalar o pacote de armazenamento de sessão do Express, bem como nossos outros pacotes fornecidos pelo Express:
npm i --save express-session && npm i
Para usar o pacote `session`, precisaremos adicioná-lo ao nosso app.js arquivo. Adicione as seguintes linhas onde indicado no comentário:
// Add this line to the top of your app.js file
var session = require("express-session");
...
var app = express();
...
// And this block after the app has been created
app.use(session({
secret: process.env.SESSION_SECRET,
resave: true,
saveUninitialized: true,
}));
...Se você quiser verificar se tudo está funcionando até agora, execute SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> npm start e acesse localhost:3000 no seu navegador. Você deverá ver a página inicial padrão do Express.
Etapa 2: Adicionar a autenticação do Google
Agora que já temos um novo aplicativo Express, vamos adicionar a autenticação usando o cliente OAuth do Google.
Caso ainda não tenha feito isso, crie um novo projeto no portal da API do Google e adicione credenciais do OAuth 2.0 a ele. Certifique-se de definir sua URL de retorno de chamada do OAuth como localhost:3000 para este tutorial, mas, se você implantar este aplicativo em um ambiente de produção, será necessário alterar a URL de retorno de chamada.
Creating a Client ID in the Google Developer Console
O Google irá gerar um ID de cliente e um segredo de cliente que usaremos ao longo deste tutorial.
A seguir, vamos instalar o pacote npm das APIs do Google:
npm i --save googleapisPara ajudar a manter nosso código organizado, vamos criar um novo arquivo em nosso classroom-reminders projeto exclusivamente para o nosso código da API do Google. Crie uma nova pasta chamada helpers/ e um arquivo dentro dela chamado google-api.js. Adicione o seguinte a esse arquivo:
const google = require("googleapis").google;
const googleConfig = {
clientId: process.env.GOOGLE_OAUTH_ID,
clientSecret: process.env.GOOGLE_OAUTH_SECRET,
redirect: process.env.GOOGLE_OAUTH_REDIRECT,
};
const createConnection = () => {
return new google.auth.OAuth2(
googleConfig.clientId,
googleConfig.clientSecret,
googleConfig.redirect
);
};
const getConnectionUrl = (auth) => {
return auth.generateAuthUrl({
access_type: "offline",
prompt: "consent",
scope: [
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/classroom.courses.readonly",
"https://www.googleapis.com/auth/classroom.rosters.readonly",
"https://www.googleapis.com/auth/classroom.coursework.students.readonly",
],
});
};
/**
* Exported functions
*/
module.exports.loginUrl = () => {
const auth = createConnection();
return getConnectionUrl(auth);
};
module.exports.getToken = async (code) => {
const auth = createConnection();
const data = await auth.getToken(code);
return data.tokens;
};
module.exports.getCurrentUser = async (tokens) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.oauth2({
auth,
version: "v2",
})
.userinfo.get();
return { ...res.data };
};
Não vou entrar em todos os detalhes sobre como o OAuth funciona, mas as três funções exportadas fazem parte de um fluxo de trabalho padrão do OAuth no lado do servidor. A função loginUrl gera uma URL de login exclusiva que os usuários verão antes de se autenticarem. A getToken função troca um código de uso único gerado pelos servidores do Google por um token de acesso de longa duração. A getCurrentUser função usa esse token de acesso para obter as informações do usuário atualmente autenticado a partir da API do Google.
Outro aspecto que vale a pena levar em conta são os scopes que estamos solicitando:
scope: [
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/classroom.courses.readonly",
"https://www.googleapis.com/auth/classroom.rosters.readonly",
"https://www.googleapis.com/auth/classroom.coursework.students.readonly",
],Os escopos limitam os dados aos quais nosso aplicativo pode acessar. Em geral, você deve solicitar o mínimo de acesso possível para desenvolver seu aplicativo; por isso, estamos solicitando apenas informações do perfil do usuário e acesso de leitura ao Google Classroom.
A seguir, vamos atualizar o Application Express routes/index.js . Essa rota verificará se há um code na string de consulta e, se for encontrado, usará o google-api arquivo auxiliar que criamos para trocar esse código por um token de autorização. Em seguida, salvará esse token (junto com um token de atualização e a data de validade) no armazenamento de sessão. Por fim, redirecionará os usuários para a /assignments página após o login:
const express = require("express");
const router = express.Router();
const googleApi = require("../helpers/google-api");
router.get("/", function (req, res, next) {
if (req.query.code) {
googleApi.getToken(req.query.code).then((tokens) => {
req.session.tokens = tokens;
req.session.save(() => {
res.redirect("/assignments");
});
});
} else {
res.render("index", {
loginUrl: googleApi.loginUrl(),
});
}
});
module.exports = router;
Também precisaremos modificar o views/index.hbs arquivo para exibir esse link de login:
<h1>Google Classroom Reminders</h1>
<p>Log in with Google to remind your students about their upcoming assignments.</p>
<p>
<a href="{{ loginUrl }}">Login</a>
</p>Se quisermos testar nossa aplicação até este ponto, precisaremos iniciá-la com nosso ID, segredo e URL de redirecionamento do Google OAuth:
GOOGLE_OAUTH_ID=<YOUR GOOGLE OAUTH ID> \
GOOGLE_OAUTH_SECRET=<YOUR GOOGLE OAUTH SECRET> \
GOOGLE_OAUTH_REDIRECT=http://localhost:3000/ \
SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> \
npm startDesta vez, quando acessarmos localhost:3000, veremos um link de login:
Login screen for Google Classroom Reminders application
Depois de clicar em “Entrar”, o Google nos guiará pelo processo de aprovação do nosso novo aplicativo:
Permissions approval for Google Classroom Reminders application
Depois que você aprovar nossa aplicação, você será redirecionado para localhost:3000/assignments, mas essa URL ainda não existe. Vamos criá-la na próxima seção.
Etapa 3: Exibição das tarefas e dos cursos de um professor no Google Classroom
Agora que criamos um processo de login para nosso aplicativo, precisamos usar o token de acesso do usuário para obter seus cursos e tarefas por meio da API do Google Classroom.
Primeiro, precisaremos adicionar duas novas funções ao helpers/google-api.js arquivo:
...
module.exports.getCourses = async (tokens) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.list({ teacherId: "me", courseStates: "ACTIVE" });
return res.data.courses ? [...res.data.courses] : [];
};
module.exports.getCourseWorks = async (tokens, courseId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.courseWork.list({ courseId: courseId, orderBy: "dueDate desc" });
return res.data.courseWork ? [...res.data.courseWork] : [];
};
Isso nos permitirá solicitar uma lista de cursos e Trabalhos do Curso (nome usado pelo Google Classroom para tarefas) da API do Google Classroom em nome do usuário atual. Em seguida, crie um novo arquivo de rota em routes/assignments.js:
const express = require("express");
const router = express.Router();
const googleApi = require("../helpers/google-api");
router.get("/", function (req, res, next) {
if (!req.session.tokens) {
res.redirect("/");
}
googleApi.getCourses(req.session.tokens).then(async (courses) => {
Promise.all(
courses.map(async (course) => {
course.assignments = await googleApi.getCourseWorks(
req.session.tokens,
course.id
);
return course;
})
).then((courses) => {
res.render("assignments", { courses });
});
});
});
module.exports = router;
Isso percorrerá todos os cursos do usuário e buscará as tarefas mais recentes de cada um deles. Também precisaremos adicionar essa rota ao app.js arquivo:
var assignmentsRouter = require('./routes/assignments');
...
app.use('/', indexRouter);
app.use('/assignments', assignmentsRouter);
...Por fim, vamos criar um novo arquivo de visualização (views/assignments.hbs) para exibir todos os cursos e tarefas do usuário atual:
<h1>Google Classroom Reminders</h1>
<p>Select an Assignment to remind your students about.</p>
{{#each courses}}
<h2>{{ this.name }}</h2>
{{#if assignments}}
<ul>
{{#each assignments}}
<li>
<a href="/assignments/{{ ../id }}:{{ this.id }}">{{ this.title }}</a><br/>
Due on {{ this.dueDate.month }}/{{ this.dueDate.day }}/{{ this.dueDate.year }}
</li>
{{/each}}
</ul>
{{else}}
<p>No assignments found</p>
{{/if}}
{{/each}}
Se você abrir o aplicativo como fez na etapa anterior e fizer login novamente, deverá ver uma lista dos seus cursos e tarefas do Google Classroom:
Viewing Google Classroom courses and assignments
Nesse momento, os usuários podem fazer login em nosso aplicativo usando seu Account do Google e ver uma lista das tarefas mais recentes de seus cursos do Google Classroom. Em seguida, permitiremos que os usuários detalhem a visualização e vejam os alunos de cada curso, bem como se eles entregaram ou não uma determinada tarefa.
Etapa 4: Exibição da lista de alunos de um professor e dos trabalhos dos alunos para uma determinada tarefa do Google Classroom
Para exibir uma lista dos alunos matriculados em um curso e verificar se eles entregaram aquela tarefa específica, precisaremos acessar alguns novos endpoints da API do Google Classroom.
Vamos adicionar essas novas funções ao google-api.js arquivo auxiliar:
...
module.exports.getCourse = async (tokens, courseId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.get({ id: courseId });
return { ...res.data };
};
module.exports.getCourseRoster = async (tokens, courseId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.students.list({ courseId: courseId });
return res.data.students ? [...res.data.students] : [];
};
module.exports.getCourseWork = async (tokens, courseId, assignmentId) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.courseWork.get({ courseId: courseId, id: assignmentId });
return { ...res.data };
};
module.exports.getStudentSubmissions = async (
tokens,
courseId,
assignmentId
) => {
const auth = createConnection();
auth.setCredentials(tokens);
const res = await google
.classroom({ version: "v1", auth })
.courses.courseWork.studentSubmissions.list({
courseId: courseId,
courseWorkId: assignmentId,
});
return res.data.studentSubmissions ? [...res.data.studentSubmissions] : [];
};
A seguir, vamos criar uma nova rota no routes/assignments.js arquivo para obter o seguinte:
Um único curso
A lista de alunos desse curso
Um único objeto “Trabalho do Curso”
Trabalhos entregues pelos alunos para essa disciplina
Curiosamente, a API do Google Classroom não nos permite obter um único trabalho do curso sem o ID do curso e o ID do trabalho do curso. Para passar ambos os IDs como um único parâmetro de rota, nós os concatenamos com um : na etapa anterior. Daí essa linha no views/assignments.hbs arquivo:
...
<a href="/assignments/{{ ../id }}:{{ this.id }}">{{ this.title }}</a>
...Agora precisamos analisar esses dois IDs em nossa nova rota e, em seguida, passá-los para as funções apropriadas que criamos no google-api.js arquivo. Adicione as seguintes linhas ao seu routes/assignments.js arquivo:
...
router.get("/:id", function (req, res, next) {
if (!req.session.tokens) {
res.redirect("/");
}
const ids = req.params.id.split(":");
const courseId = ids[0];
const assignmentId = ids[1];
Promise.all([
googleApi.getCourse(req.session.tokens, courseId),
googleApi.getCourseRoster(req.session.tokens, courseId),
googleApi.getCourseWork(req.session.tokens, courseId, assignmentId),
googleApi.getStudentSubmissions(req.session.tokens, courseId, assignmentId),
]).then(([course, students, courseWork, submissions]) => {
// Match submissions to students
if (
students &&
students.length > 0 &&
submissions &&
submissions.length > 0
) {
students.map((student) => {
student.submission = submissions.find(
(submission) => submission.userId === student.userId
);
if (student.submission && student.submission.state === "TURNED_IN") {
student.turnedIn = true;
}
return student;
});
}
res.render("assignment", {
course,
students,
courseWork,
submissions,
});
});
});
...
Por fim, precisaremos de uma nova visualização para ver todos os alunos e o status de envio de cada um para uma determinada tarefa. Crie um arquivo em views/assignment.hbs e adicione o seguinte:
<h1>Google Classroom Reminders</h1>
<p>
Send your students reminders about <a href="{{ courseWork.alternateLink }}">{{ courseWork.title }}</a>
in <a href="{{ course.alternateLink }}">{{ course.name }}</a>.
</p>
{{#if students}}
<div>
{{#each students}}
<p>
{{#if this.turnedIn}}
<a href="{{ this.alternateLink }}" title="Assignment turned in">✅</a>
{{else}}
<span title="Assignment not turned in">❗️</span>
{{/if}}
<strong>{{ this.profile.name.fullName }}</strong>
</p>
{{/each}}
</div>
{{else}}
<p>No students found</p>
{{/if}}
<p><a href="/assignments">↩️ Back to all assignments</a></p>Agora, se iniciarmos o aplicativo e fizermos login novamente, podemos acessar uma tarefa específica e verificar o status de envio de cada aluno (indicado por ou ❗️).
Na etapa final, permitiremos que os usuários enviem mensagens SMS aos alunos usando a Messages API do Vonage.
Etapa 5: Como adicionar lembretes por mensagem de texto usando a Messages API do Vonage
A Messages API do Vonage pode enviar e receber mensagens por diversos canais, mas, para este aplicativo, usaremos apenas mensagens de texto SMS.
Supondo que você já tenha criado um aplicativo da API da Vonage, o próximo passo é instalar o cliente JavaScript. Além desse cliente, também adicionaremos a pacote google-libphonenumber para ajudar a formatar números de telefone:
npm i --save nexmo@beta google-libphonenumberA seguir, vamos criar outro arquivo auxiliar para o nosso código, que formata números de telefone e envia mensagens SMS por meio da biblioteca Nexmo. Crie um novo arquivo em helpers/nexmo-api.js:
const Nexmo = require("nexmo");
const PNF = require("google-libphonenumber").PhoneNumberFormat;
const phoneUtil = require("google-libphonenumber").PhoneNumberUtil.getInstance();
const nexmo = new Nexmo({
apiKey: process.env.NEXMO_API_KEY,
apiSecret: process.env.NEXMO_API_SECRET,
applicationId: process.env.NEXMO_APP_ID,
privateKey: process.env.NEXMO_PRIVATE_KEY_PATH,
});
module.exports.sendSms = (telephone, message, callback) => {
const formattedPhoneNumber = phoneUtil.format(
phoneUtil.parseAndKeepRawInput(telephone, "US"),
PNF.E164
);
nexmo.channel.send(
{ type: "sms", number: formattedPhoneNumber },
{ type: "sms", number: process.env.NEXMO_PHONE_NUMBER },
{
content: {
type: "text",
text: message,
},
},
callback,
{ useBasicAuth: true }
);
};
Para processar as entradas do usuário e chamar a sendSms função que acabamos de criar, vamos criar um novo arquivo de rotas em routes/messages.js:
const express = require("express");
const router = express.Router();
const nexmoApi = require("../helpers/nexmo-api");
router.post("/", function (req, res, next) {
if (!req.session.tokens) {
res.redirect("/");
}
const { telephones, messages } = req.body;
Promise.all(
telephones.map((telephone, key) => {
if (telephone) {
return nexmoApi.sendSms(telephone, messages[key]);
}
})
).then((results) => {
res.redirect("/assignments");
});
});
module.exports = router;
Este arquivo percorre uma matriz de números de telefone e mensagens e chama o sendSms método para cada um que contenha um número de telefone. Também precisaremos atualizar nosso app.js arquivo para usar essa nova rota:
var messagesRouter = require('./routes/messages');
...
app.use('/', indexRouter);
app.use('/assignments', assignmentsRouter);
app.use('/messages', messagesRouter);
...Como a API do Google não nos dá acesso aos números de telefone dos alunos, teremos que fazer com que os professores insiram esses números em nossa interface, juntamente com uma mensagem para cada aluno. Vamos editar o views/assignment.hbs arquivo para incluir esses dois campos de formulário para cada aluno e um botão de envio:
<h1>Google Classroom Reminders</h1>
<p>
Send your students reminders about <a href="{{ courseWork.alternateLink }}">{{ courseWork.title }}</a>
in <a href="{{ course.alternateLink }}">{{ course.name }}</a>.
</p>
{{#if students}}
<form action="/messages" method="post">
{{#each students}}
<p>
{{#if this.turnedIn}}
<a href="{{ this.alternateLink }}" title="Assignment turned in">✅</a>
{{else}}
<span title="Assignment not turned in">❗️</span>
{{/if}}
<strong>{{ this.profile.name.fullName }}</strong>
</p>
<div>
<label for="message-{{ this.userId }}" style="display: block;">Reminder message</label>
<textarea id="message-{{ this.userId }}" name="messages" maxlength="140" minlength="3" rows="5" cols="30">Hey {{ this.profile.name.givenName }}, don't forget about your assignment for {{ ../course.name }}. It's due on {{ ../courseWork.dueDate.month }}/{{ ../courseWork.dueDate.day }}/{{ ../courseWork.dueDate.year }}</textarea>
</div>
<div>
<label for="telephone-{{ this.userId }}" style="display: block;">Telephone</label>
<input type="tel" id="telephone-{{ this.userId }}" autocomplete="off" name="telephones" />
</div>
{{/each}}
<div style="margin-top: 10px;">
<input type="submit" value="Send Reminders">
</div>
</form>
{{else}}
<p>No students found</p>
{{/if}}
<p><a href="/assignments">↩️ Back to all assignments</a></p>
Nosso aplicativo está praticamente pronto, mas precisamos organizar todas as nossas credenciais da Vonage antes de podermos usá-lo. Primeiro, baixe sua chave privada da API da Vonage e salve-a em um novo arquivo chamado .private_key. Agora, inicie seu aplicativo com todas as variáveis de ambiente das APIs do Google e da Vonage:
GOOGLE_OAUTH_ID=<YOUR GOOGLE OAUTH ID> \
GOOGLE_OAUTH_SECRET=<YOUR GOOGLE OAUTH SECRET> \
GOOGLE_OAUTH_REDIRECT=http://localhost:3000/ \
SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> \
NEXMO_API_KEY=<YOUR VONAGE MESSAGES API KEY> \
NEXMO_API_SECRET=<YOUR VONAGE MESSAGES API SECRET> \
NEXMO_PHONE_NUMBER=<YOUR VONAGE MESSAGES PHONE NUMBER> \
NEXMO_APP_ID=<YOUR VONAGE MESSAGES APP ID> \
NEXMO_PRIVATE_KEY_PATH=./.private_key \
npm startDesta vez, ao fazer login e visualizar uma única tarefa, você poderá inserir um número de telefone para cada um dos seus alunos e personalizar uma mensagem para enviar a eles. Quando estiver pronto, clique em “Enviar lembretes” para testar tudo.
Sending SMS Reminders using Google Classroom and the Vonage Messages API
Próximos passos
Embora este aplicativo de demonstração aborde um caso de uso relativamente simples, é fácil perceber o quão poderosa a Messages API do Vonage pode ser quando integrada a um LMS como o Google Classroom. Existem várias maneiras de continuar aprimorando a experiência do usuário com um aplicativo como este:
Armazenar números de telefone em um banco de dados para que os professores não precisem digitá-los todas as vezes
Permitir que os usuários alterem a mensagem padrão de lembrete
Adicionar suporte a outros canais de mensagens, como Facebook, WhatsApp ou Viber
Armazenamento em cache de dados do Google Classroom para melhorar o desempenho e evitar limites de taxa
Mensagens de erro e sucesso fáceis de entender
Design e estilo personalizados
Se você tiver dúvidas ou outras sugestões para implementar um sistema de lembretes usando o Vonage e o Google Classroom, entre em contato conosco pelo Twitter ou na Slack da Comunidade de Desenvolvedores da Vonage!