
Partager:
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.
Créer un outil de rappel par SMS pour les enseignants utilisant Google Classroom
Temps de lecture : 9 minutes
Je travaille dans le domaine des technologies de l'éducation depuis plusieurs années maintenant, et l'un des problèmes que j'entends souvent de la part des enseignants est que les élèves ne consultent pas leurs comptes de messagerie. Il existe des entreprises entières, comme Remindont été créées autour de ce problème de communication.
Dans cette présentation, nous allons créer une application de rappel par SMS qui permet aux enseignants de rappeler à leurs élèves les devoirs à venir dans Google Classroom. Nous utiliserons l Google Classroom API de Google Classroom pour renforcer l'authentification et obtenir des données sur les cours et les devoirs, et l'API Messages de Vonage de Vonage pour alimenter les messages texte que les enseignants envoient à leurs élèves.
Planification de l'application
Avant de commencer, comprenons les fonctionnalités de base et l'architecture de notre application. Dans ce tutoriel, nous aborderons trois histoires d'utilisateurs :
Les enseignants peuvent se connecter à notre application à l'aide de leur compte Google.
Les enseignants peuvent consulter la liste de leurs derniers devoirs et sélectionner celui qu'ils souhaitent rappeler aux élèves.
Les enseignants peuvent rappeler à chaque élève par SMS le devoir à venir.
Examinons le flux de données entre notre application et les deux API de support (Google Classroom et Vonage Messages) :

Nous utiliserons nœud et Express pour cette démonstration, mais Google et Vonage proposent tous deux des clients API dans la plupart des principaux langages de programmation. Si vous souhaitez aller plus loin, vous pouvez télécharger le code sur Github et suivre la section "Quick Start" dans le Readme pour démarrer l'application.
Conditions préalables
A Compte Google APIs et un projet avec des informations d'identification de client Les informations d'identification du client OAuth 2.0
A Compte Google Classroom avec une classe comportant plusieurs élèves et au moins un devoir
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
Création de l'application
Étape 1 : Création d'une nouvelle application Express
Tout d'abord, créons une nouvelle application Express qui utilise Handlebars pour la création de modèles et express-session pour le stockage des sessions. Nous utiliserons le générateur d'applications générateur d'applications Express pour faciliter les choses :
npx express-generator --hbs --git classroom-remindersCette commande créera un nouveau répertoire appelé classroom-reminders avec une application Express standard à l'intérieur. Naviguons dans ce répertoire et installons le paquetage de stockage de session Express ainsi que les autres paquetages fournis par Express :
npm i --save express-session && npm i
Afin d'utiliser le paquet session, nous devons l'ajouter à notre fichier app.js à notre fichier Ajoutez les lignes suivantes aux endroits indiqués dans le commentaire :
// 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,
}));
...Si vous voulez vous assurer que tout fonctionne jusqu'à présent, lancez SESSION_SECRET=<A SECURE STRING FOR PROTECTING SESSIONS> npm start et visitez localhost:3000 dans votre navigateur. Vous devriez voir la page d'accueil par défaut d'Express.
Étape 2 : Ajout de l'authentification Google
Maintenant que nous avons une nouvelle application Express, ajoutons l'authentification en utilisant le client OAuth de Google.
Si vous ne l'avez pas encore fait, créez un nouveau projet sur le portail Google API et ajoutez-y des informations d'identification OAuth 2.0. ajoutez-y des informations d'identification OAuth 2.0. Veillez à définir votre URL de rappel OAuth sur localhost:3000 pour ce tutoriel, mais si vous déployez cette application dans un environnement de production, vous voudrez modifier l'URL de rappel.
Creating a Client ID in the Google Developer Console
Google génère un identifiant et un secret client que nous utiliserons tout au long de ce tutoriel.
Ensuite, nous allons installer le paquetage npm Google APIs npm package:
npm i --save googleapisPour mieux organiser notre code, nous allons créer un nouveau fichier dans notre projet classroom-reminders uniquement pour le code de l'API Google. Créez un nouveau dossier appelé helpers/ et un fichier appelé google-api.js. Ajoutez les éléments suivants à ce fichier :
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 };
};
Je n'entrerai pas dans les détails du fonctionnement d OAuthmais les trois fonctions exportées font toutes partie d'un flux de travail OAuth standard côté serveur. La fonction loginUrl génère une URL de connexion unique que les utilisateurs verront avant de s'authentifier. La fonction getToken échange un code à usage unique généré par les serveurs de Google contre un jeton d'accès à longue durée de vie. La fonction getCurrentUser utilise ce jeton d'accès pour obtenir les informations de l'utilisateur actuellement authentifié à partir de l'API Google.
Il convient également de prendre en considération les scopes que nous demandons :
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",
],Les champs d'application limitent les données auxquelles notre application peut accéder. En général, vous devriez demander le moins d'accès possible pour construire votre application, donc nous ne demandons que les informations du profil de l'utilisateur et l'accès en lecture à Google Classroom.
Ensuite, nous allons mettre à jour le fichier routes/index.js de notre application Express. Cette route vérifiera la présence d'un code dans la chaîne de requête, et si elle est trouvée, elle utilisera le fichier d'aide google-api que nous avons créé pour échanger ce code contre un jeton d'autorisation. Ensuite, elle enregistre ce jeton (ainsi qu'un jeton de rafraîchissement et une date d'expiration) dans le stockage de la session. Enfin, il redirigera les utilisateurs vers la page /assignments une fois qu'ils sont connectés :
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;
Nous devrons également modifier le fichier views/index.hbs pour afficher ce lien de connexion :
<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>Si nous voulons tester notre application jusqu'à présent, nous devrons la démarrer avec notre identifiant Google OAuth, notre secret et notre URL de redirection :
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 startCette fois-ci, lorsque nous naviguons vers localhost:3000nous verrons un lien de connexion :
Login screen for Google Classroom Reminders application
Après avoir cliqué sur connexion, Google nous guidera dans le processus d'approbation de notre nouvelle application :
Permissions approval for Google Classroom Reminders application
Après avoir approuvé notre application, vous serez redirigé vers localhost:3000/assignmentsmais cette URL n'existe pas encore. Nous la créerons dans la section suivante.
Étape 3 : Affichage des devoirs et des cours d'un enseignant à partir de Google Classroom
Maintenant que nous avons construit un processus de connexion pour notre application, nous devons utiliser le jeton d'accès de l'utilisateur pour obtenir ses cours et ses devoirs à partir de l'API de Google Classroom.
Tout d'abord, nous devons ajouter deux nouvelles fonctions au fichier helpers/google-api.js deux nouvelles fonctions :
...
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] : [];
};
Ils nous permettront de demander une liste de cours et de CoursTravaux (nom donné par Google Classroom aux devoirs) à partir de l'API Google Classroom au nom de l'utilisateur actuel. Ensuite, créez un nouveau fichier d'itinéraires à l'adresse 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;
Cela permettra de parcourir tous les cours de l'utilisateur et d'obtenir les derniers devoirs pour chacun d'entre eux. Nous devrons également ajouter cette route au fichier app.js au fichier
var assignmentsRouter = require('./routes/assignments');
...
app.use('/', indexRouter);
app.use('/assignments', assignmentsRouter);
...Enfin, nous créerons un nouveau fichier de vue (views/assignments.hbs) pour afficher tous les cours et devoirs de l'utilisateur actuel :
<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}}
Si vous démarrez l'application comme vous l'avez fait à l'étape précédente et que vous vous y connectez à nouveau, vous devriez voir apparaître une liste de vos cours et devoirs Google Classroom :
Viewing Google Classroom courses and assignments
À ce stade, les utilisateurs peuvent se connecter à notre application à l'aide de leur compte Google et consulter la liste des devoirs les plus récents de leurs cours Google Classroom. Ensuite, nous allons permettre aux utilisateurs d'effectuer des recherches et d'afficher les étudiants de chaque cours et de savoir s'ils ont rendu un devoir particulier ou non.
Étape 4 : Affichage de la liste des enseignants et des travaux des élèves pour un devoir Google Classroom particulier
Pour afficher la liste des étudiants inscrits à un cours et vérifier s'ils ont rendu ce devoir particulier, nous devons accéder à quelques nouveaux points de terminaison de l'API Google Classroom.
Ajoutons ces nouvelles fonctions au fichier google-api.js fichier d'aide :
...
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] : [];
};
Ensuite, créons une nouvelle route dans le fichier routes/assignments.js pour obtenir ce qui suit :
Un seul cours
La liste des étudiants pour ce cours
Un seul objet de travail de cours
Soumissions des étudiants pour ce travail de cours
Il est intéressant de noter que l'API Google Classroom ne nous permet pas d'obtenir un travail de cours unique sans l'ID du cours et l'ID du travail de cours. Afin de transmettre les deux identifiants sous la forme d'un seul paramètre de route, nous les avons concaténés à l'aide d'un : à l'étape précédente. D'où cette ligne dans le fichier views/assignments.hbs d'où cette ligne dans le fichier
...
<a href="/assignments/{{ ../id }}:{{ this.id }}">{{ this.title }}</a>
...Nous devons maintenant analyser ces deux ID dans notre nouvelle route et les passer dans les fonctions appropriées que nous avons créées dans le fichier google-api.js dans le fichier Ajoutez les lignes suivantes à votre fichier routes/assignments.js les lignes suivantes :
...
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,
});
});
});
...
Enfin, nous aurons besoin d'une nouvelle vue pour voir tous les étudiants et leur statut de soumission pour un travail particulier. Créez un fichier à l'adresse views/assignment.hbs et ajoutez-y ce qui suit :
<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>Maintenant, si nous démarrons l'application et nous connectons à nouveau, nous pouvons explorer un travail particulier et voir l'état de soumission de chaque étudiant (indiqué par ou ❗️).
Dans la dernière étape, nous allons permettre aux utilisateurs d'envoyer des SMS aux élèves en utilisant l'API Messages de Vonage.
Étape 5 : Ajouter des rappels par message texte à l'aide de l'API Messages de Vonage
L'API Messages de Vonage permet d'envoyer et de recevoir des messages sur plusieurs canaux, mais pour cette application, nous n'utiliserons que les messages texte SMS.
En supposant que vous avez déjà créé une application API VonageVonage, l'étape suivante consiste à installer le client JavaScript. En plus de ce client, nous ajouterons également le fichier google-libphonenumber pour faciliter le formatage des numéros de téléphone :
npm i --save nexmo@beta google-libphonenumberEnsuite, créons un autre fichier d'aide pour notre code qui formate les numéros de téléphone et envoie des SMS via la bibliothèque Numbers. Créez un nouveau fichier à l'adresse 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 }
);
};
Afin de traiter les entrées d'un utilisateur et d'appeler la fonction sendSms que nous venons de créer, créons un nouveau fichier de route à l'adresse 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;
Ce fichier itère sur un tableau de numéros de téléphone et de messages et appelle la méthode sendSms pour chaque message comportant un numéro de téléphone. Nous devrons également mettre à jour notre fichier app.js pour utiliser ce nouvel itinéraire :
var messagesRouter = require('./routes/messages');
...
app.use('/', indexRouter);
app.use('/assignments', assignmentsRouter);
app.use('/messages', messagesRouter);
...Comme l'API de Google ne nous donne pas accès aux numéros de téléphone des élèves, nous devrons demander aux enseignants de saisir ces numéros dans notre interface, ainsi qu'un message pour chaque élève. Modifions le fichier views/assignment.hbs pour inclure ces deux champs de formulaire pour chaque élève et un bouton d'envoi :
<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>
Notre application est pratiquement terminée, mais nous devons mettre en ordre toutes nos informations d'identification Vonage avant de pouvoir l'utiliser. Tout d'abord, téléchargez votre clé privée API Vonage et enregistrez-la dans un nouveau fichier appelé .private_key. Maintenant, démarrez votre application avec toutes les variables d'environnement de Google et de l'API 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 startCette fois, lorsque vous vous connectez et affichez un seul devoir, vous pourrez saisir un numéro de téléphone pour chacun de vos élèves et personnaliser le message à leur envoyer. Lorsque vous êtes prêt, cliquez sur "Envoyer des rappels" pour tester le tout.
Sending SMS Reminders using Google Classroom and the Vonage Messages API
Prochaines étapes
Bien que cette application de démonstration couvre un cas d'utilisation relativement simple, il est facile de voir à quel point l'API Messages de Vonage peut être puissante lorsqu'elle est associée à un SGL comme Google Classroom. Il y a plusieurs façons de continuer à améliorer l'expérience de l'utilisateur avec une application comme celle-ci :
Stocker les numéros de téléphone dans une base de données afin que les enseignants n'aient pas à les saisir à chaque fois.
Permettre aux utilisateurs de modifier le message de rappel par défaut
Ajout de la prise en charge d'autres canaux de messagerie comme Facebook, What's App ou Viber.
Mise en cache des données de Google Classroom pour améliorer les performances et éviter les limitations de débit
Messages d'erreur et de réussite conviviaux
Conception et stylisme sur mesure
Si vous avez des questions ou d'autres idées pour mettre en œuvre un système de rappel en utilisant Vonage et Google Classroom, contactez-nous sur Twitter ou sur Communauté des développeurs de Vonage Slack!
