| Bots de conversa | Exemples | Dades pràctiques | Recursos CITCEA | |
| Google Apps Script | Projectes | Interacció | Inici |
En aquest exemple gestionarem les inscripcions a un o més actes. Hi haurà una llista dels actes existents en la que s'hi indicarà si tenen les inscripcions obertes o no. Cada acte tindrà un identificador.
Quan un usuari demani la inscripció a un acte, s'anotarà la informació i se li enviarà un correu electrònic perquè confirmi la inscripció. Un cop confirmada la inscripció, l'usuari es podrà baixar un codi QR que serà la seva entrada a l'esdeveniment.
La taula dels actes disponibles (anomenada actes) tindrà, com a mínim, les següents columnes:
| Columna | Contingut | Exemple o valors |
| 0 | Identificador de l'acte (no ha de tenir espais) |
gen2004 |
| 1 | Estat de l'acte | 0 No admet inscripcions 1 Admet inscripcions |
La taula de les inscripcions (anomenada inscrip) tindrà, com a mínim, les següents columnes:
| Columna | Contingut | Exemple o valors |
| 0 | Identificador de l'acte (no ha de tenir espais) |
gen2004 |
| 1 | Identificador de Telegram de l'usuari inscrit | ^^9499826 |
| 2 | Data d'inscripció | 09/12/2003 18:15:29 |
| 3 | Correu electrònic de l'usuari inscrit | usuari@correu.com |
| 4 | Estat de la inscripció | 0 Inscripció no validada 1 Inscripció validada 2 La persona ja ha entrat a l'acte |
| 5 | Codi de seguretat | WDApRdzlla5VXMlIbZaD |
L'script fa servir POST per interaccionar amb Telegram i GET per validar la inscripció (des d'un enllaç en el correu) i per autoritzar l'entrada (des d'un enllaç en un codi QR). Els paràmetres disponibles a POST són:
| Paràmetre | Descripció | Exemple |
| /help | Llista de comandes disponibles | /help |
| /inscri idActe correu | Inscripció a un acte concret | /inscri gen2004 usuari@correu.com |
| /qr@idActe | Obtenir el codi QR per entrar a un acte concret | /qr@gen2004 |
Quan es demana fer la inscripció, es comprova que l'usuari hagi entrat el codi de l'esdeveniement i una adreça de correu mínimament acceptable. No és possible comprovar si l'adreça és vàlida i, per tant, comprovem que tingui una arroba i que hi hagi almenys un punt després de l'arroba. Abans i després dels símbols comprovats hi ha d'haver un mínim de dos caràcters. Cal tenir en compte que hi pot haver adreces que tinguin un o més punts abans de l'arroba i també hi ha terminacions de més de tres lletres. En el cas de la petició de codi QR es comprova que s'hagi indicat l'acte.
A l'hora de fer la inscripció, primer es comprova que l'acte corresponent estigui enregistrat i accepti inscripcions. Després es mira si l'usuari ja està inscrit i, si és el cas, se li diu quan es va inscriure. Si no ho està, es guarden les seves dades i se li envia un correu perquè validi la inscripció. Per evitar problemes amb alguns programes de gestió de correu, s'escurça l'adreça de l'enllaç.
A l'hora d'enviar el codi QR, primer es comprova que l'usuari estigui correctament inscrit a l'acte per al qual demana el codi i que encara no ha entrat. Si tot és correcte, es genera el codi i s'envia.
Per donar una mica de seguretat, en el moment d'inscriure un usuari a un acte es genera un codi alfanumèric de setze caràcters ASCII que es guarda al full de càlcul i s'envia codificat en l'enllaç de correu per poder fer la comprovació en la validació de la inscripció. Quan es valida la inscripció es genera un nou codi alfanumèric, aquest cop de vint caràcters, que formarà part de l'enllaç que es codifica en el codi QR i serà comprovat a l'hora d'entrar.
Quan es valida la inscripció es comprova que l'acte i el codi que figuren a la petició GET corresponen amb una de les peticions existents. No s'envia l'identificador de Telegram per protegir el compte de l'usuari. Una cosa semblant es fa a la validació del codi QR per entrar. El valor de la variable posicio pot ser considerat text i caldrà convertir-lo a numèric.
Atès que l'adreça del generador de codis té paràmetres, pot aparèixer un conflicte si l'adreça que volem codificar conté algun & (o sigui, si també té paràmetres). Caldrà substituir els & de l'adreça que es vol codificar pel seu codi per a http, és a dir %26.
El programa de l'script és el següent:
var token = "^^34328844:AAFIpk-e7j3UZtYQYQaTduf4hEhnDqIcNXI"; // API Token de Telegram
var telegramUrl = "https://api.telegram.org/bot" + token; // URL que comunica el bot de conversa amb Telegram
var ssId = "^^ThsoSjkeMSfwEKy4mn_4QEYH96sxv3VURqE3WHCTswDA"; // Identificador del full de càlcul
// Adreça URL de l'script
var webAppUrl = "https://script.google.com/macros/s/^^fycbzXSTtXkzqyM_sABbEZanVX2EuRAIUB5xct53AXp6-MbLscpLHO/exec";
function doGet(e){ // Per a l'enllaç del correu i per al codi QR
var resposta = "";
// Agafem els paràmetres de l'ordre GET i comprovem que hi ha el contingut esperat
var Accio = e.parameter.accio;
var Codi = e.parameter.codi;
var Acte = e.parameter.acte;
if ((Accio == null) || (Accio == undefined)){
resposta = "Falten paràmetres";
} else {
resposta = "Comanda incorrecta"; // Frase per defecte
if (Accio == "ins"){ // Correspon a l'enllaç del correu
if ((Codi == null) || (Codi == undefined) || (Acte == null) || (Acte == undefined)){
resposta = "Falten paràmetres";
} else {
var sh = SpreadsheetApp.openById(ssId).getSheetByName("inscrip"); // Agafem el full
var dades = sh.getDataRange().getValues(); // Agafem les dades per buscar l'usuari
var posicio = 0; // De moment, no l'hem trobat
var id = ""; // Si el trobem, agafem l'identificador de l'usuari
resposta = "Petició incorrecta"; // Per si no el trobem
for (var i in dades){
if((posicio == 0) && (i > 0)){ // Si ja l'hem trobat, no seguim
var filera = dades[i]; // Agafa una filera
if((filera[0] == Acte) && (filera[5] == Codi)){ // L'hem trobat
if(filera[4] < 1){ // Comprovem que està pendent de validar
posicio = i; // Filera de l'usuari
id = filera[1]; // Identificador de l'usuari
}
}
}
}
posicio = +posicio + 1; // Sumem 1 perquè a l'escriure es compta des d'1
if(posicio > 1){ // Si hem trobat l'usuari
var camps = new Array(2); // Valors per guardar a la taula
camps[0] = 1; // La inscripció està validada
camps[1] = codi(20); // Posem un nou codi, de mida diferent
// Ja tenim les dues caselles que hem d'escriure a la filera
// Les nostres dades han d'escriure en un espai, que seleccionarem més endavant,
// de dues columnes en una filera
var valu = new Array(1); // Matriu de dades a escriure
valu[0] = camps; // Converteix el vector en una matriu d'una filera i dues columnes
// La funció getRange ens permet seleccionar el grup de caselles sobre les que anem a escriure
// El primer paràmetre és la filera on comença el grup
// El segon paràmetre és la columna on comença el grup, en el nostre cas la cinquena (comencen en 1)
// El tercer paràmetre és el nombre de fileres que tindrà el grup, per a nosaltres una
// El quart paràmetre és el nombre de columnes que ocupa el grup, en el nostre cas dues
var rangeVal = sh.getRange(posicio, 5, 1, 2); // Selecciona dues caselles
rangeVal.setValues(valu); // Guarda els valors a les caselles, substituïnt els anteriors
resposta = "Inscripció validada";
// La resposta sortirà al navegador i s'enviarà a Telegram
// També podríem enviar un correu electrònic
// Podríem enviar (per correu o per Telegram) un enllaç per baixar la documentació
var resp = resposta + "\nPer entrar a l'acte hauràs de mostrar un codi QR que ";
resp = resp + "obtindràs amb la comanda /qr@" + Acte;
sendText(id,resp);
}
}
}
if (Accio == "ent"){ // Per quan es valida el codi QR
if ((Codi == null) || (Codi == undefined) || (Acte == null) || (Acte == undefined)){
resposta = "Falten paràmetres";
} else {
var sh = SpreadsheetApp.openById(ssId).getSheetByName("inscrip"); // Agafem el full
var dades = sh.getDataRange().getValues(); // Agafem les dades
var posicio = 0; // De moment, no l'hem trobat
var estat = -1; // Aquí guardarem l'estat de l'usuari
resposta = "Petició incorrecta"; // Per si no el trobem
for (var i in dades){ // Mirem totes les dades
if((posicio == 0) && (i > 0)){ // Si ja l'hem trobat, no seguim
var filera = dades[i]; // Agafa una filera
if((filera[0] == Acte) && (filera[5] == Codi)){ // L'hem trobat
estat = filera[4]; // Agafem el seu estat
posicio = i; // I la seva posició
}
}
}
posicio = +posicio + 1; // Sumem 1 perquè a l'escriure es compta des d'1
if(posicio > 1){ // Si l'hem trobat
if(estat == 0){
resposta = "Aquesta persona no ha validat la inscripció";
}
if(estat == 2){
resposta = "Aquesta persona ja ha entrat";
}
if(estat == 1){
// La funció getRange ens permet seleccionar la casella sobre la que anem a escriure
// El primer paràmetre és la filera
// El segon paràmetre és la casella
var rangeVal = sh.getRange(posicio, 5); // Selecciona la casella
rangeVal.setValue(2); // Guarda el valor
resposta = "Tot correcte. Pot passar!";
}
} else {
resposta = "Aquesta persona no està inscrita";
}
}
}
}
return ContentService.createTextOutput(resposta);
}
function doPost(e){ // Per a les comandes de Telegram
var data = JSON.parse(e.postData.contents); // Llegeix les dades rebudes per JSON i les guarda
var text = data.message.text; // Comanda enviada
var id = data.message.chat.id; // Identificador de la finestra d'on prové el missatge
var id_usuari = data.message.from.id; // Identificador de l'usuari
var id_missatge = data.message.message_id; // Identificador del missatge
var lang = data.message.from.language_code ; // Idioma de l'usuari
var nom_usr = data.message.from.first_name ; // Nom de l'usuari
var location = data.message.location; // Localització de l'usuari (si es sap)
var realitzat = false;
// En la comanda /qr hem posat @ per conservar l'enllaç
// Amb qualsevol altre separador, Telegram talla
text = text.replace("/qr@","/qr "); // Substituïm així per conservar l'adreça de correu
var comanda = text.split(" "); // Separem els paràmetres
var cmd = comanda[0]; // Comanda
var par = comanda[1]; // Identificador de l'acte, no pot tenir espais
var mail = comanda[2]; // Correu de l'usuari
if(cmd == '/inscri'){ // Inscripció a l'acte
var correcte = true;
var resposta = "";
if(par == undefined){
resposta = resposta + "Has d'indicar a quin acte et vols inscriure i el teu correu\n";
resposta = resposta + "/inscri idActe correu\n";
correcte = false;
}
if(mail == undefined){
resposta = resposta + "Has d'indicar a quin acte et vols inscriure i el teu correu\n";
resposta = resposta + "/inscri idActe correu\n";
correcte = false;
}
if(correcte){ // Comprovem que sigui una adreça acceptable
correcte = comprova(mail);
}
if(correcte){
resposta = resposta + inscrip(par, id_usuari, mail); // Fa la inscripció
}
sendText(id,resposta);
realitzat = true;
}
if(cmd == '/qr'){ // Petició del QR
var correcte = true;
var resposta = "";
if(par == undefined){
resposta = resposta + "Has d'indicar a quin acte vols entrar\n";
resposta = resposta + "/qr@idActe\n";
correcte = false;
}
if(correcte){
correcte = envia_qr(par, id_usuari); // Envia el QR
} else {
sendText(id,resposta);
}
if (!correcte){
var resposta = "No estàs inscrit a " + par;
sendText(id,resposta);
}
realitzat = true;
}
if(cmd == '/help'){ // Llista de comandes
var resposta = "Comandes disponibles:\n";
resposta = resposta + "/inscri idActe Inscripció a l'acte indicat\n";
resposta = resposta + "/qr@idActe Descarregar el codi QR necessari per entrar a l'acte\n";
sendText(id,resposta);
realitzat = true;
}
if (!realitzat){
var resposta = "Comanda desconeguda";
sendText(id,resposta);
}
}
function envia_qr(acte, id_usr){ // Funció que envia el codi QR per entrar
var sh = SpreadsheetApp.openById(ssId).getSheetByName("inscrip"); // Agafem el full
var dades = sh.getDataRange().getValues(); // Agafem les dades
var posicio = 0; // De moment, no l'hem trobat
var codi = ""; // Si el trobem, agafem el codi
resposta = "Petició incorrecta"; // Per si no el trobem
for (var i in dades){ // Recorre totes les dades
if((posicio == 0) && (i > 0)){ // Si ja l'hem trobat, no seguim
var filera = dades[i]; // Agafa una filera
if((filera[0] == acte) && (filera[1] == id_usr)){ // L'hem trobat
if(filera[4] < 2){ // Comprovem que encara no ha entrat
posicio = i; // Filera que correspon a l'usuari
codi = filera[5]; // Identificador de l'usuari
}
}
}
}
if(posicio > 0){ // Si l'hem trobat
var caption = "Entrada a " + acte; // Missatge que acompanya al codi
// Quan hem de fer servir & com a paràmetre d'un paràmetre, com en aquest cas, l'hem de codificar
// sinó no ho interpreta correctament
var codiAnd = "%26"; // Codi de substitució per al signe &
// Els signes & de l'adreça de l'script s'han de canviar perquè no es confonguin
// amb els paràmetres de l'adreça del generador de codis QR
// scriptUrl seria l'adreça d'un script que validaria el codi
var url_script = webAppUrl +"?accio=ent" + codiAnd + "acte=" + acte + codiAnd + "codi=" + codi;
var qr = "https://chart.googleapis.com/chart?chs=250x250&cht=qr&chl=" + url_script;
var response = UrlFetchApp.fetch(qr); // Carrega la imatge corresponent a la url generada
var binaryData = response.getContent(); // La guarda com a dades binàries
// La funció newBlob té tres paràmetres: les dades, el tipus de dades i el nom que li volem donar
// carrega la imatge com a dades binaries en una variable
var blob = Utilities.newBlob(binaryData, 'image/jpeg', 'QR'); // Dades BLOB de la imatge
sendBlobFile(id_usr,blob,caption); // Enviem la imatge al Telegram en format binari (BLOB)
return true;
} else {
return false;
}
}
function inscrip(acte, id_usr, correu){ // Funció que fa la inscripció a un acte
// Primer mirem si l'acte demanat existeix i té inscripcions obertes
var sh = SpreadsheetApp.openById(ssId).getSheetByName("actes"); // Agafem el full
var dades = sh.getDataRange().getValues(); // Agafa les dades
var obert = false; // L'acte no té, d'entrada, inscripció oberta
for (var i in dades){ // Recorre totes les dades
if(i > 0){ // Ignorem la filera dels títols
var filera = dades[i]; // Agafa una filera
if(filera[0] == acte){ // Hem trobat l'acte demanat
if(filera[1] > 0){ // L'acte té inscripcions obertes
obert = true;
}
}
}
}
if (obert){
// Comprovem que l'usuari no estigui ja inscrit
var sh = SpreadsheetApp.openById(ssId).getSheetByName("inscrip"); // Agafem el full
var dades = sh.getDataRange().getValues(); // Agafem les dades
var present = false; // L'usuari, en principi, no està inscrit
for (var i in dades){ // Recorre totes les dades
if(i > 0){ // Ignorem la filera dels títols
var filera = dades[i]; // Agafa una filera
if((filera[0] == acte) && (filera[1] == id_usr)){ // L'hem trobat
present = true;
var data_ins = filera[2]; // Data en la qual es va inscriure
}
}
}
if(present){
var resp = "Ja et vas inscriure el dia ";
resp = resp + diahora(data_ins);
} else {
var camps = new Array(6); // Contindrà cinc valors per guardar a la taula
camps[0] = acte; // Acte al qual es fa la inscripció
camps[1] = id_usr; // Usuari que s'inscriu
camps[2] = new Date; // Data en la que s'ha fet la inscripció
camps[3] = correu; // Correu de l'usuari
camps[4] = 0; // De moment, inscripció no validada
camps[5] = codi(16); // Codi aleatori de seguretat
var sh = SpreadsheetApp.openById(ssId).getSheetByName("inscrip"); // Agafem el full
sh.appendRow(camps); // Afegeix una filera amb les dades
// Crea la URL escurçada
var url = escur(webAppUrl + "?accio=ins&acte=" + acte + "&codi=" + camps[5]);
var titol = "Inscripció a " + acte; // Títol del correu
var text = "Benvolgut/da\n\n"; // Text del correu
text = text + "Hem rebut la teva petició d'inscripció a " + acte +".\n\n";
text = text + "Per completar el procés, cal que segueixis el següent enllaç:\n";
text = text + url + "\n\nMoltes gràcies\n\nL'equip organitzador";
MailApp.sendEmail(correu, titol, text); // Envia el correu
var resp = "Inscripció realitzada\n"; // Resposta que s'envia a Telegram
resp = resp + "Rebràs un correu amb les instruccions per confirmar la inscripció";
}
} else {
var resp = "L'acte demanat no té inscripcions obertes";
}
return resp;
}
function comprova(adre){
// Mira si l'adreça de correu compleix uns mínims
// no es pot saber si és correcta sense provar-la
// Comprova que, com a mínim, hi hagi una arroba i un punt
// Un mínim de dos caràcters abans de l'arroba
// Un mínim de dos caràcters a cada banda del punt
var resp = true; // D'entrada, suposem que és una adreça vàlida
var camps = adre.split("@"); // Separem per l'arroba
if(camps[0].length < 2){
resp = false; // No és vàlid, pocs caràcters abans de l'@
}
if(camps[1] == undefined){ // No hi havia res després de l'arroba
resp = false;
} else {
var serv = camps[1].split("."); // Separem pel punt
if(serv[0].length < 2){
resp = false; // No és vàlid, pocs caràcters abans del punt
}
if(serv[1] == undefined){ // No hi havia res després del punt
resp = false;
} else {
if(serv[1].length < 2){
resp = false; // No és vàlid, pocs caràcters després del punt
}
}
}
return resp;
}
function codi(n){ // Funció que genera un codi aleatori de n caràcters
// El codi es genera a partir de xifres (48 a 57), lletres majúscules (65 a 90)
// i lletres minúscules (97 a 122); en total 62 caràcters possibles
// De 0 a 9 és una xifra
// de 10 a 35 una lletra majúscula
// de 36 a 61 una lletra minúscula
var resp = "";
for (var i = 0; i < n; i++){
var aleat = Math.floor(62 * Math.random()); // Valor enter entre 0 i 61
var asc = 65 + aleat - 10; // Per si està entre 10 i 35, majúscules
if (aleat < 10){
asc = 48 + aleat; // Correspon a un dígit
}
if (aleat > 35){
asc = 97 + aleat - 10 - 26; // Minúscules
}
var carac = String.fromCharCode(asc); // Agafa el caràcter corresponent al codi ASCII
resp = resp + carac;
}
return resp;
}
function diahora(data){ // Funció que converteix la data al format d'ús habitual
var resp = data.getDate();
resp = resp + "-" + (data.getMonth() + 1); // Els mesos comencen en zero
resp = resp + "-" + data.getFullYear();
resp = resp + " a les " + data.getHours();
resp = resp + "." + data.getMinutes();
resp = resp + " h";
return resp;
}
function escur(url_llarg){ // Escurça una adreça URL
// Basada en una adaptació feta per Ferran Mas (@FerranMas) d'una funció
// original de @pfelipm
var adre = "https://tinyurl.com/api-create.php?url=" + url_llarg;
var url_curt = UrlFetchApp.fetch(adre).getContentText();
return url_curt;
}
function sendText(chatId,text_env,keyBoard){ // Funció que envia un text o un teclat a Telegram
keyBoard = keyBoard || 0;
if(keyBoard.inline_keyboard || keyBoard.keyboard){
var data = {
method: "post",
payload: {
method: "sendMessage",
chat_id: String(chatId),
text: text_env,
parse_mode: "HTML",
reply_markup: JSON.stringify(keyBoard)
}
}
} else {
var data = {
method: "post",
payload: {
method: "sendMessage",
chat_id: String(chatId),
text: text_env,
parse_mode: "HTML"
}
}
}
UrlFetchApp.fetch( telegramUrl + '/', data);
}
function sendBlobFile(chatId,blob_data,caption){
var payload = {
method: "sendPhoto",
chat_id: String(chatId),
photo: blob_data,
caption: caption,
parse_mode: "HTML"
};
var options = {
method: "POST",
payload: payload,
muteHttpExceptions: true
};
UrlFetchApp.fetch( telegramUrl + '/', options);
}
Atenció: Se suposa que quan es llegeix el codi QR el dispositiu obre el navegador i carrega la pàgina web que correspon a l'enllaç, ja sigui directament o després de prémer un botó. Però hem detectat que alguns lectors de codi QR executen l'enllaç abans que l'usuari pugui dir si carrega la pàgina o no. Atès que no carrega la pàgina no mostra la resposta però, en canvi, sí queda enregistrat l'accés. Quan l'usuari accedeix a que es carregui la pàgina es torna a executar l'script que donarà un avís indicant que l'usuari ja ha entrat. Una forma de solucionar aquest problema, si només hi ha una persona que valida els codis seria que la resposta a la lectura del codi s'enviés al Telegram de la persona que controla l'accés. Una altra opció seria modificar l'script per tal que l'estat pugui arribar fins a 3 i es deixi entrar quan val 2.
Aquest programa admet diferents ampliacions. Per un costat es podria afegir una comanda que permetés generar automàticament un diploma o certificat d'assistència. Podríem posar a la taula d'actes, en columnes addicionals, les dades necessàries per fer el document però també caldria recollir d'alguna manera el nom de l'usuari ja que ara només recollim el correu.
Podríem fer que quan l'usuari valida la inscripció rebés un o més enllaços, ja sigui per Telegram o per correu electrònic, que li permetessin descarregar la documentació corresponent.
En cas que preveiem que els actes puguin ser propers en el temps, com seria el cas de diversos tallers en un congrés o jornada, es podria posar una comanda que permetés a l'usuari obtenir un llistat dels actes als que està inscrit.
També es podria tenir una comanda oculta (que no es mostri en fer /help) que permetés als organitzadors descarregar un llistat de les persones inscrites. En cas que hi hagi, per exemple, diversos tallers en una jornada, es podrien tenir les dades de les persones que imparteixen els tallers i que cada una d'aquestes pogués consultar les persones inscrites en la seva activitat.

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.