| Bots de conversa | Exemples | Dades pràctiques | Recursos CITCEA | |
| Google Apps Script | Projectes | Interacció | Inici |
En aquest exemple gestionarem uns calendaris des de Telegram. Partim de la base que fem servir calendaris de Google per gestionar les reserves d'unes aules (un calendari per aula). En la primera versió, el bot de conversa ens permetrà consultar les reserves que té una aula durant el dia actual i també si en el moment actual hi ha una aula que estigui disponible en els següents seixanta minuts. Evidentment, el nombre de minuts podria ser qualsevol altre o, fins i tot, ser una dada que es passi com a paràmetre.
El programa d'exemple treballa amb només dos calendaris però està previst que pugui treballar amb un nombre indeterminat. Si el nombre de calendaris (i, per tant, d'aules) és una mica gros, pot ser convenient guardar la informació en un full de càlcul i així facilitar canvis com afegir o treure alguna aula o els canvis de nom.
Els tres paràmetres del nostre exemple són:
| Paràmetre | Descripció |
| /help | Llista de comandes disponibles |
| /lliures | Aules lliures en els propers 60 minuts |
| /p-a | Reserves per avui a l'aula indicada
Exemple: /3-1 |
El primer que cal fer és crear els calendaris, si no els tenim ja creats, i obtenir l'identificador de cada un d'ells.
La funció que mira quines aules estan lliures recorre tots els calendaris disponibles mirant si hi ha reserves en la franja horària indicada. Si el nombre d'esdeveniments recollits és zero vol dir que no n'hi ha cap. Cal tenir present que quan demanem els esdeveniments en una franja horària concreta ens torna tots els que coincideixin total o parcialment amb la franja, encara que comencin abans o acabin més tard.
La funció que mira les dades d'una aula concreta agafa tots els esdeveniments des d'ara fins al final del dia i en llista les dades principals.
El codi per a aquest primer 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 nostre bot amb Telegram
var nomsAules = new Array(2); // Vector on guardem els noms de les aules
nomsAules[0] = "1-1";
nomsAules[1] = "1-2";
var idCal = new Array(2); // Vector on guardem els identificadors dels calendaris
idCal[0] = "^^3c9nt5cvf9aqw9k6qfco3ous@group.calendar.google.com";
idCal[1] = "^^jc6lqve1ktjhp0r0o3dlkh3o@group.calendar.google.com";
var realitzat = false;
function doPost(e){
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 comanda = text.split(" "); // Separem els paràmetres
var cmd = comanda[0];
if(cmd == '/help'){ // Llista de comandes
realitzat = true;
var resposta = "Comandes disponibles:\n";
resposta = resposta + "/lliures Aules lliures en els propers 60 minuts\n";
resposta = resposta + "/p-a (exemple: /1-1 ) Reserves per avui a l'aula indicada\n";
sendText(id,resposta);
}
if(cmd == '/lliures'){ // Aules lliures en els propers 60 minuts
realitzat = true;
lliure(id);
}
if(!realitzat){ // Si no és cap de les comandes anteriors, potser és la disponibilitat d'una aula
var nomAula = cmd.substring(1,cmd.length); // Li traiem la / del davant
punter = -1; // Si no trobem el nom de l'aula valdrà -1
for (var i = 0; i < idCal.length; i++){
if (nomsAules[i] == nomAula){
punter = i; // Punter de l'aula demanada
}
}
if (punter > -1){
aula(id, punter); // Mirem l'ocupació de l'aula
} else {
var resposta = "Comanda desconeguda";
sendText(id,resposta);
}
}
}
function lliure(id_xat){ // Aules lliures en els propers 60 minuts
var resultat = "<b>Aules lliures en els propers 60 minuts:</b>\n"; // Títol en negreta
var compta = 0;
var ara = new Date(); // Data i hora actuals
var ms_hora = 1000 * 60 * 60; // Mil·lisegons que té una hora
var final = new Date(ara.getTime() + ms_hora); // Data i hora passats 60 minuts
for(var i = 0; i < idCal.length; i++){ // Mirem tots els calendaris de la llista
var nom = nomsAules[i]; // Nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[i]); // Obrim el calendari
var esdev = cal.getEvents(ara, final); // Agafem els esdeveniements
if (esdev.length == 0){ // Si no n'hi ha cap
resultat = resultat + "L'aula " + nom + " està lliure\n";
compta++; // Compta les aules lliures
}
}
if(compta == 0){ // Si no n'hi ha cap de lliure
resultat = resultat + "Cap de les aules està lliure\n";
}
sendText(id_xat,resultat);
}
function aula(id_xat, pntr){ // Mira l'ocupació d'una aula
var resultat = "";
var nom = nomsAules[pntr]; // Agafa el nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[pntr]); // Obre el calendari
if (!cal) { // Comprova que existeixi
resultat = "Calendari no trobat!";
} else {
var ara = new Date(); // Data i hora actuals
var final = new Date();
final.setHours(23);
final.setMinutes(59); // Data i hora al final del dia
resultat = resultat + "<b>Reserves per a avui a l'aula " + nom + ":</b>\n"; // Títol en negreta
var esdev = cal.getEvents(ara, final); // Agafa els esdeveniments
var numEsdev = esdev.length; // Nombre d'esdeveniments trobats
if(numEsdev > 0){ // Si n'hi ha algun
for(var i = 0; i < numEsdev; i++){ // Els recorre tots
var esdAct = esdev[i]; // Agafa un esdeveniment
var dataIni = esdAct.getStartTime(); // Data i hora d'inici
var horariIni = dosCar(dataIni.getHours()) + "." + dosDigits(dataIni.getMinutes());
var dataFi = esdAct.getEndTime(); // Data i hora d'acabament
var horariFi = dosCar(dataFi.getHours()) + "." + dosDigits(dataFi.getMinutes());
// Genera la informació de l'esdeveniment
resultat = resultat + horariIni + " - " + horariFi + " - ";
resultat = resultat + esdAct.getTitle() + "\n";
}
} else { // Si no hi ha cap esdeveniment
resultat = resultat + "No hi ha reserves per a avui\n";
}
}
sendText(id_xat,resultat);
}
function sendText(chatId,text_env,keyBoard){ // Funció que prepara per enviar 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 dosDigits(valor){
var sortida = "";
if (valor < 10) {
sortida = sortida + "0";
}
sortida = sortida + valor;
return sortida;
}
function dosCar(valor){
var sortida = "";
if (valor < 10) {
sortida = sortida + " ";
}
sortida = sortida + valor;
return sortida;
}
En aquesta segona versió donarem la possibilitat que l'usuari faci reserves en el calendari. Calen unes quantes dades per fer la reserva. El format de la comanda serà:
/reserva p-a dd-mm-aaaa hi.mi hf.mf NomActe
Per exemple, per reservar l'aula 1-1 per a un acte cultural el dia 23-10-2020 entre les 11.00 h i les 13.30 h escriuríem:
/reserva 1-1 23-10-2020 11.00 13.30 Acte cultural
El programa comprova que hi hagi els cinc paràmetres necessaris però no s'entreté a comprovar que tot sigui correcte. Per evitar problemes, la part on es tracten els paràmetres està dins d'un bloc try que ens permetrà gestionar els errors que fan aturar el programa. Els errors que no fan aturar el programa no s'han contemplat, doncs, i poden portar a resultats curiosos; per exemple si com a data posem 32-10-2020 ens farà la reserva per al dia 1-10-2020 i no es produirà cap error. Es podria complicar el programa per tenir en compte totes les opcions d'error però allargaria molt el programa ja que el fet que no tots els mesos tinguin el mateix nombre de dies introdueix força complexitat.
Quan la funció ja té tots els paràmetres que necessita per crear el nou esdeveniment, fa primer la comprovació de si l'aula està disponible en la franja horària triada i només fa la reserva si no hi ha cap reserva prèvia que se solapi.
El programa é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 nostre bot amb Telegram
var nomsAules = new Array(2); // Llista dels noms de les aules
nomsAules[0] = "1-1";
nomsAules[1] = "1-2";
var idCal = new Array(2); // Llista dels identificadors dels calendaris
idCal[0] = "^^3c9nt5cvf9aqw9k6qfco3ous@group.calendar.google.com";
idCal[1] = "^^jc6lqve1ktjhp0r0o3dlkh3o@group.calendar.google.com";
var realitzat = false;
function doPost(e){
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 comanda = text.split(" "); // El signe @ separa la comanda dels paràmetres
var cmd = comanda[0];
if(cmd == '/help'){ // Llista de comandes
realitzat = true;
var resposta = "Comandes disponibles:\n";
resposta = resposta + "/lliures Aules lliures en els propers 60 minuts\n";
resposta = resposta + "/p-a (exemple: /1-1 ) Reserves per avui a l'aula indicada\n";
resposta = resposta + "/reserva p-a dd-mm-aaaa hi.mi hf.mf NomActe Reservar l'aula\n";
sendText(id,resposta);
}
if(cmd == '/reserva'){ // Reservar una aula
realitzat = true;
reserva(id, comanda);
}
if(cmd == '/lliures'){ // Buscar aules lliures
realitzat = true;
lliure(id);
}
if(!realitzat){ // Si no és cap de les comandes anteriors, potser és la disponibilitat d'una aula
var nomAula = cmd.substring(1,cmd.length); // Li traiem la / del davant
punter = -1; // Si no trobem el nom de l'aula valdrà -1
for (var i = 0; i < idCal.length; i++){ // Mirem tots els elements de la llista
if (nomsAules[i] == nomAula){
punter = i; // Punter de l'aula demanada
}
}
if (punter > -1){ // Si s'ha trobat l'aula demanada
aula(id, punter); // Mirem l'ocupació de l'aula
} else {
var resposta = "Comanda desconeguda";
sendText(id,resposta);
}
}
}
function reserva(id_xat, param){ // Reservar l'aula
if(param.length < 6){ // Comprova que hi són tots els paràmetres
var resposta = "Falten paràmetres";
sendText(id_xat,resposta);
return;
}
// Anem a tractar els paràmetres
var aula = param[1];
var dat = param[2].split("-");
var hi = param[3].split(".");
var hf = param[4].split(".");
// Si el nom de l'acte tenia espais haurà quedat trossejat
var acte = "";
for(var i = 5; i < param.length; i++){
acte = acte + param[i] + " ";
}
// Busquem l'aula
var punter = -1;
for(var i = 0; i < idCal.length; i++){
if(nomsAules[i] == aula){
punter = i; // Punter de l'aula demanada
}
}
if(punter == -1){ // Si no s'ha trobat l'aula demanada
var resposta = "Aula desconeguda";
sendText(id_xat,resposta);
return;
}
try{ // Fem un try per detectar si les dades no són correctes
var resultat = "";
var inici = new Date();
inici.setDate(dat[0]);
inici.setMonth(dat[1] - 1); // Restem 1 perquè codifica els mesos de 0 a 11
inici.setFullYear(dat[2]);
inici.setHours(hi[0]);
inici.setMinutes(hi[1]);
inici.setSeconds(0);
var final = new Date();
final.setDate(dat[0]);
final.setMonth(dat[1] - 1); // Restem 1 perquè codifica els mesos de 0 a 11
final.setFullYear(dat[2]);
final.setHours(hf[0]);
final.setMinutes(hf[1]);
final.setSeconds(0);
var cal = CalendarApp.getCalendarById(idCal[punter]);
if (!cal) { // Comprova que el calendari existeix
resultat = "Calendari no trobat!";
} else {
// Comprova si hi ha solapaments
var esdev = cal.getEvents(inici, final);
if (esdev.length == 0){ // Si està disponible
cal.createEvent(acte, inici, final);
resultat = "Reserva feta";
} else {
resultat = "El calendari està ocupat en la franja indicada";
}
}
sendText(id_xat,resultat);
} catch(err) { // Si falla
var resposta = "No s'ha pogut fer la reserva. Revisa les dades\nEl format és:\n";
resposta = resposta + "/reserva p-a dd-mm-aaaa hi.mi hf.mf NomActe";
sendText(id_xat,resposta);
}
}
function lliure(id_xat){ // Aules lliures en els propers 60 minuts
var resultat = "<b>Aules lliures en els propers 60 minuts:</b>\n"; // Títol en negreta
var compta = 0;
var ara = new Date(); // Data i hora actuals
var ms_hora = 1000 * 60 * 60; // Mil·lisegons que té una hora
var final = new Date(ara.getTime() + ms_hora); // Data i hora passats 60 minuts
for(var i = 0; i < idCal.length; i++){ // Mirem tots els calendaris de la llista
var nom = nomsAules[i]; // Nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[i]); // Obrim el calendari
var esdev = cal.getEvents(ara, final); // Agafem els esdeveniements
if (esdev.length == 0){ // Si no n'hi ha cap
resultat = resultat + "L'aula " + nom + " està lliure\n";
compta++; // Compta les aules lliures
}
}
if(compta == 0){ // Si no n'hi ha cap de lliure
resultat = resultat + "Cap de les aules està lliure\n";
}
sendText(id_xat,resultat);
}
function aula(id_xat, pntr){ // Mira l'ocupació d'una aula
var resultat = "";
var nom = nomsAules[pntr]; // Agafa el nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[pntr]); // Obre el calendari
if (!cal) { // Comprova que existeixi
resultat = "Calendari no trobat!";
} else {
var ara = new Date(); // Data i hora actuals
var final = new Date();
final.setHours(23);
final.setMinutes(59); // Data i hora al final del dia
resultat = resultat + "<b>Reserves per a avui a l'aula " + nom + ":</b>\n"; // Títol en negreta
var esdev = cal.getEvents(ara, final); // Agafa els esdeveniments
var numEsdev = esdev.length; // Nombre d'esdeveniments trobats
if(numEsdev > 0){ // Si n'hi ha algun
for(var i = 0; i < numEsdev; i++){ // Els recorre tots
var esdAct = esdev[i]; // Agafa un esdeveniment
var dataIni = esdAct.getStartTime(); // Data i hora d'inici
var horariIni = dosCar(dataIni.getHours()) + "." + dosDigits(dataIni.getMinutes());
var dataFi = esdAct.getEndTime(); // Data i hora d'acabament
var horariFi = dosCar(dataFi.getHours()) + "." + dosDigits(dataFi.getMinutes());
// Genera la informació de l'esdeveniment
resultat = resultat + horariIni + " - " + horariFi + " - ";
resultat = resultat + esdAct.getTitle() + "\n";
}
} else { // Si no hi ha cap esdeveniment
resultat = resultat + "No hi ha reserves per a avui\n";
}
}
sendText(id_xat,resultat);
}
function sendText(chatId,text_env,keyBoard){ // Funció que prepara per enviar 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 dosDigits(valor){
var sortida = "";
if (valor < 10) {
sortida = sortida + "0";
}
sortida = sortida + valor;
return sortida;
}
function dosCar(valor){
var sortida = "";
if (valor < 10) {
sortida = sortida + " ";
}
sortida = sortida + valor;
return sortida;
}
La comanda de reserva d'una aula necessita diversos paràmetres. En la versió anterior calia posar-los tots, un darrere l'altre, a la línia d'entrada. Però fent-ho així és fàcil que l'usuari cometi algun error en l'ordre o la sintaxi dels paràmetres. En aquesta versió hem implementat un sistema per demanar els paràmetres a l'usuari de manera seqüencial, un darrere l'altre. Això evita equivocar-se en l'ordre dels paràmetres i facilita informar l'usuari sobre com ha d'escriure cada paràmetre.
La idea es basa en fer servir un full de càlcul per anar guardant els paràmetres que envia cada usuari. Així, doncs, hem creat un full de càlcul amb les següents columnes:
| Columna | Contingut |
| 1 | Identificador de l'usuari |
| 2 | Comptador de paràmetres rebuts |
| 3 | Aula que es vol reservar |
| 4 | Dia de la reserva |
| 5 | Hora d'inici de l'activitat |
| 6 | Hora d'acabament de l'activitat |
| 7 | Nom de l'activitat |
A la funció doPost hem canviat algunes coses. Per un costat, quan es rep la comanda /reserva es crida a la funció que prepara la filera del full de càlcul, corresponent a l'usuari, per rebre els paràmetres. Si l'usuari no hi surt (no havia fet servir mai aquesta comanda) es crea una filera nova. Si es rep un text que no comença per / (no és una comanda) suposarem que és un paràmetre i el tractarem com a tal.
La funció param és la que processa els paràmetres. També hi ha la funció iniParam que és la que prepara el full de càlcul per rebre els paràmetres i la funció fiParam que és la que ho deixa tot a punt per a la vegada següent.
Un detall important a tenir en compte és com es guarden els paràmetres al full de càlcul. Si no hem configurat les caselles amb un format determinat, el full de càlcul interpreta el que rep i ens podem trobar sorpreses. Per exemple, quan enviem 1-1 (el nom de l'aula) ho pot interpretar com 1 de gener i guardar-ho en format de data; això faria que la creació de l'esdeveniment al calendari fallés. Podríem resoldre-ho configurant cada casella amb el tipus de dades que ha de rebre però és fàcil equivocar-se o oblidar-se'n i sempre hi poden haver situacions no previstes. En lloc d'això, hem agafat una solució alternativa que ens permet prescindir de configurar els formats al full de càlcul. El que fem és guardar els paràmetres (en el cas de l'identificador de l'usuari i el comptador no cal) amb un caràcter al davant que faci que el full de càlcul ho interpreti com a text. Cal evitar els caràcters que poden fer que s'equivoqui (%, $, €, etc.) i els que és possible que formin part dels paràmetres (lletres i alguns símbols). Nosaltres hem fet servir ^. Abans d'enviar les dades a la funció que executa la comanda, ens hem de recordar de retirar aquest símbol de cada paràmetre.
var token = "^^34328844:AAFIpk-e7j3UZtYQYQaTduf4hEhnDqIcNXI"; // API Token de Telegram
var telegramUrl = "https://api.telegram.org/bot" + token; // URL que comunica el nostre bot amb Telegram
var ssId = "^^ThsoSjkeMSfwEKy4mn_4QEYH96sxv3VURqE3WHCTswDA"; // Identificador del full de càlcul
var nomsAules = new Array(2); // Llista dels noms de les aules
nomsAules[0] = "1-1";
nomsAules[1] = "1-2";
var idCal = new Array(2); // Llista dels identificadors dels calendaris
idCal[0] = "^^3c9nt5cvf9aqw9k6qfco3ous@group.calendar.google.com";
idCal[1] = "^^jc6lqve1ktjhp0r0o3dlkh3o@group.calendar.google.com";
var missatges = new Array(5); // Missatges que enviarem a l'usuari en cada pas del procés de reserva
missatges[0] = "Quina aula vols reservar?\nFormat: p-a";
missatges[1] = "Per a quin dia?\nFormat: dd-mm-aaaa";
missatges[2] = "A quina hora comença la reserva?\nFormat: hi.mi";
missatges[3] = "A quina hora acaba la reserva?\nFormat: hf.mf";
missatges[4] = "Per a quina activitat?";
var realitzat = false;
function doPost(e){
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)
if(text.indexOf("/") == 0){ // Si comença per / és una comanda
if(text == '/help'){ // Llistat de comandes
realitzat = true;
var resposta = "Comandes disponibles:\n";
resposta = resposta + "/lliures Aules lliures en els propers 60 minuts\n";
resposta = resposta + "/p-a (exemple: /1-1 ) Reserves per avui a l'aula indicada\n";
resposta = resposta + "/reserva Reservar una aula\n";
sendText(id,resposta);
}
if(text == '/reserva'){ // Iniciar una reserva
realitzat = true;
iniParam(id);
}
if(text == '/lliures'){ // Aules lliures en els propers 60 minuts
realitzat = true;
lliure(id);
}
if(!realitzat){ // Si no és cap de les anteriors, serà la consulta de l'ocupació d'una aula
var nomAula = text.substring(1,text.length); // Li traiem la / del davant
punter = -1; // Si no trova l'aula el punter serà -1
for (var i = 0; i < idCal.length; i++){
if (nomsAules[i] == nomAula){
punter = i; // Si troba l'aula, guardem el punter
}
}
if (punter > -1){ // Si l'hem trobat
aula(id, punter); // Busquem les reserves de l'aula
} else {
var resposta = "Comanda desconeguda";
sendText(id,resposta);
}
}
} else { // Si no comença per / potser és un paràmetre
param(id, text);
}
}
function param(id_usr, comand){ // Processa els paràmetres
var sh = SpreadsheetApp.openById(ssId).getSheetByName("param"); // Obrim el full de càlcul
var dades = sh.getDataRange().getValues(); // Agafem el contingut
var dar_fil = sh.getLastRow(); // Índex de la darrera filera
// Mirem si l'usuari consta a la llista
var fila = 0;
for(var i = 0; i < dar_fil; i++){ // Mirem totes les dades
var row = dades[i]; // Agafa una filera
var num_usuari = row[0]; // Agafa el número d'usuari de la filera
if(num_usuari == id_usr){ // Mira si és l'usuari actual
fila = i; // No sumem 1 perquè al vector es compta a partir de 0
}
}
if(fila == 0){ // Si l'usuari és nou, no ens està enviant cap paràmetre
var resposta = "Comanda desconeguda";
sendText(id_usr,resposta);
return;
}
var row = dades[fila]; // Agafem les dades de l'usuari
// A la segona casella tenim l'estat de l'enviament de paràmetres
var estat = row[1];
if (estat < 5){ // Si encara no hem rebut tots els paràmetres
// Sumem 1 a la fila perquè els rangs van a partir d'1
var rang = sh.getRange(fila+1, 2); // Selecciona la casella de l'estat
rang.setValue(estat + 1); // Passem a l'estat següent
var rang = sh.getRange(fila+1, estat+3); // Selecciona la casella següent
// Posem ^ al davant perquè ho interpreti com a text
rang.setValue("^" + comand); // Guarda el valor a la casella
}
if (estat == 4){ // Si ja tenim tots els paràmetres
row[6] = "^" + comand; // Ho afegim aquí per no tornar a llegir del full
// Abans d'enviar-ho al calendari retirem els ^
for (var i = 2; i < row.length; i++){
row[i] = row[i].replace("^","");
}
reserva(id_usr, row, fila);
} else {
sendText(id_usr,missatges[estat + 1]);
}
}
function iniParam(id_usr){ // Es prepara per rebre paràmetres
var sh = SpreadsheetApp.openById(ssId).getSheetByName("param"); // Agafa el full de càlcul
var dades = sh.getDataRange().getValues(); // Agafa els valors
var dar_fil = sh.getLastRow(); // Índex de la darrera filera
// Mirem si l'usuari ja consta a la llista
var fila = 0;
for(var i = 0; i < dar_fil; i++){ // Recorrem les dades
var row = dades[i]; // Llegim una filera
var num_usuari = row[0]; // Identificador de l'usuari
if(num_usuari == id_usr){ // Si és el que correspon
fila = i + 1; // Cal sumar 1 perquè comença a comptar des d'1
}
}
// Si l'usuari és nou el donarem d'alta
// Si no és nou, esborrarem les dades perquè entenem que vol tornar a començar
var camps = new Array(7); // Contindrà els valors per guardar a la taula
camps[0] = id_usr; // Identificador de l'usuari
camps[1] = 0; // Encara no tenim cap paràmetre
for(var i = 2; i < 7; i++){
camps[i] = ""; // No hi ha paràmetres
}
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
if(fila == 0){ // Si l'usuari és nou
sh.appendRow(camps); // Afegeix una filera amb les dades
} else { // Si no és nou, sobrescrivim
// 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, en el nostre cas la que hem trobat abans
// El segon paràmetre és la columna on comença el grup, en el nostre exemple la primera (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 set
var rang = sh.getRange(fila, 1, 1, 7);
rang.setValues(valu); // Guarda els valors a les caselles, substituïnt els anteriors
}
sendText(id_usr,missatges[0]);
}
function fiParam(id_usr,fila){ // Acaba el procés de recepció de paràmetres
var sh = SpreadsheetApp.openById(ssId).getSheetByName("param"); // Obre el full de càlcul
// Tornem l'usuari a l'estat inicial
var camps = new Array(7); // Contindrà els valors per guardar a la taula
camps[0] = id_usr; // Identificador de l'usuari
camps[1] = 0; // Encara no tenim cap paràmetre
for(var i = 2; i < 7; i++){
camps[i] = "";
}
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
var rang = sh.getRange(fila+1, 1, 1, 7);
rang.setValues(valu); // Guarda els valors a les caselles, substituïnt els anteriors
}
function reserva(id_xat, param, fila_usr){ // Fa la reserva
var aula = param[2];
var dat = param[3].split("-"); // Serà un vector
var hi = param[4].split("."); // Serà un vector
var hf = param[5].split("."); // Serà un vector
var acte = param[6];
// Busquem l'aula
var punter = -1; // Si no troba l'aula, el punter serà -1
for(var i = 0; i < idCal.length; i++){
if(nomsAules[i] == aula){
punter = i; // Punter de l'aula demanada
}
}
if(punter == -1){ // Si no l'ha trobat
fiParam(id_xat,fila_usr);
var resposta = "Aula desconeguda";
sendText(id_xat,resposta);
return;
}
try{ // Fem un try per detectar si les dades són correctes
var resultat = "";
// Defineix l'hora d'inici
var inici = new Date();
inici.setDate(dat[0]);
inici.setMonth(dat[1] - 1); // Restem 1 perquè codifica els mesos de 0 a 11
inici.setFullYear(dat[2]);
inici.setHours(hi[0]);
inici.setMinutes(hi[1]);
inici.setSeconds(0);
// Defineix l'hora d'acabament
var final = new Date();
final.setDate(dat[0]);
final.setMonth(dat[1] - 1); // Restem 1 perquè codifica els mesos de 0 a 11
final.setFullYear(dat[2]);
final.setHours(hf[0]);
final.setMinutes(hf[1]);
final.setSeconds(0);
var cal = CalendarApp.getCalendarById(idCal[punter]); // Obrim el calendari
if (!cal) { // Comprova que existeixi
resultat = "Calendari no trobat!";
} else {
var esdev = cal.getEvents(inici, final); // Mira si hi ha solapaments
if (esdev.length == 0){ // Si no n'hi ha
cal.createEvent(acte, inici, final);
resultat = "Reserva feta";
} else {
resultat = "L'aula està ocupada en la franja indicada";
}
fiParam(id_xat,fila_usr);
}
sendText(id_xat,resultat);
} catch(err) { // Si falla
fiParam(id_xat,fila_usr);
var resposta = "No s'ha pogut fer la reserva";
sendText(id_xat,resposta);
}
}
function lliure(id_xat){ // Aules lliures en els propers 60 minuts
var resultat = "<b>Aules lliures en els propers 60 minuts:</b>\n";
var compta = 0;
var ara = new Date(); // Data i hora actuals
var ms_hora = 1000 * 60 * 60; // Mil·lisegons que té una hora
var final = new Date(ara.getTime() + ms_hora);
for(var i = 0; i < idCal.length; i++){ // Mira tots els calendaris
var nom = nomsAules[i]; // Nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[i]); // Agafa el calendari
var esdev = cal.getEvents(ara, final); // Agafa els esdeveniments
if (esdev.length == 0){ // Si no n'hi ha cap és que està lliure
resultat = resultat + "L'aula " + nom + " està lliure\n";
compta++;
}
}
if(compta == 0){ // Si no n'ha trobat cap
resultat = resultat + "Cap de les aules està lliure\n";
}
sendText(id_xat,resultat);
}
function aula(id_xat, pntr){ // Reserves d'una aula concreta
var resultat = "";
var nom = nomsAules[pntr]; // Nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[pntr]); // Agafa el calendari
if (!cal) { // Si no el troba
resultat = "Calendari no trobat!";
} else {
var ara = new Date(); // Data i hora actuals
var final = new Date();
final.setHours(23);
final.setMinutes(59); // Final del dia
resultat = resultat + "<b>Reserves per a avui a l'aula " + nom + ":</b>\n";
var esdev = cal.getEvents(ara, final); // Agafa els esdeveniments
var numEsdev = esdev.length; // Nombre d'esdeveniments
if(numEsdev > 0){ // Si n'hi ha algun
for(var i = 0; i < numEsdev; i++){ // Els recorre tots
var esdAct = esdev[i]; // Agafa un esdeveniment
// Dades de l'esdeveniment
var dataIni = esdAct.getStartTime();
var horariIni = dosCar(dataIni.getHours()) + "." + dosDigits(dataIni.getMinutes());
var dataFi = esdAct.getEndTime();
var horariFi = dosCar(dataFi.getHours()) + "." + dosDigits(dataFi.getMinutes());
resultat = resultat + horariIni + " - " + horariFi + " - ";
resultat = resultat + esdAct.getTitle() + "\n";
}
} else {
resultat = resultat + "No hi ha reserves per a avui\n";
}
}
sendText(id_xat,resultat);
}
function sendText(chatId,text_env,keyBoard){ // Funció que prepara per enviar 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 dosDigits(valor){ // Si cal, posa un zero per forçar que siguin dos dígits
var sortida = "";
if (valor < 10) {
sortida = sortida + "0";
}
sortida = sortida + valor;
return sortida;
}
function dosCar(valor){ // Si cal, posa un espai per forçar que siguin dos caràcters
var sortida = "";
if (valor < 10) {
sortida = sortida + " ";
}
sortida = sortida + valor;
return sortida;
}
Una forma alternativa per a recollir els paràmetres de manera seqüencial és emprar la memòria cau per guardar-les. Amb aquest mètode l'script és més curt i senzill, a més s'executa de manera més ràpida. Però hi ha el risc de perdre les dades ja que Google no en garanteix la persistència.
Hem de guardar sis paràmetres per a cada possible usuari i hem de preveure que hi hagi dos o més usuaris fent simultàniament alguna reserva. Si la memòria cau permetés guardar correctament vectors o objectes podríem posar totes les dades en una d'aquestes estructures de dades i guardar-la sencera sota la clau corresponent a l'identificador de l'usuari de Telegram. Atès que això no és possible, farem servir sis claus per a cada usuari. Cada clau serà el resultat d'afegir una lletra (corresponent a l'índex del valor guardat) al final de l'identificador de l'usuari. Quan comenci el procés de reserva crearem les sis claus i les anirem omplint a mesura que avanci el procés. A l'acabar la reserva esborrarem totes les claus d'aquell usuari. En el nostre cas, no definim el temps màxim en el que es guarden les dades i, per tant, en principi serà de deu minuts. Els sis paràmetres amb l'índex per a la clau corresponent es llisten a la taula següent.
| Índex | Contingut |
| p | Comptador de paràmetres rebuts |
| a | Aula que es vol reservar |
| d | Dia de la reserva |
| i | Hora d'inici de l'activitat |
| f | Hora d'acabament de l'activitat |
| e | Nom de l'activitat |
L'estructura del programa és la mateixa que en la versió anterior. Atès que ara no fem servir un full de càlcul, no hem de patir perquè les dades es puguin interpretar de manera incorrecta i, per tant, les podem guardar de manera senzilla. El valor de la variable pas pot ser considerat text i caldrà convertir-lo a numèric.
var token = "^^34328844:AAFIpk-e7j3UZtYQYQaTduf4hEhnDqIcNXI"; // API Token de Telegram
var telegramUrl = "https://api.telegram.org/bot" + token; // URL que comunica el nostre bot amb Telegram
var nomsAules = new Array(2); // Llista dels noms de les aules
nomsAules[0] = "1-1";
nomsAules[1] = "1-2";
var idCal = new Array(2); // Llista dels identificadors dels calendaris
idCal[0] = "^^3c9nt5cvf9aqw9k6qfco3ous@group.calendar.google.com";
idCal[1] = "^^jc6lqve1ktjhp0r0o3dlkh3o@group.calendar.google.com";
var missatges = new Array(5); // Llista dels missatges que, a cada pas, s'enviaran a l'usuari
missatges[0] = "Quina aula vols reservar?\nFormat: p-a";
missatges[1] = "Per a quin dia?\nFormat: dd-mm-aaaa";
missatges[2] = "A quina hora comença la reserva?\nFormat: hi.mi";
missatges[3] = "A quina hora acaba la reserva?\nFormat: hf.mf";
missatges[4] = "Per a quina activitat?";
var ind = ["p", "a", "d", "i", "f", "e"]; // Llista d'identificadors de paràmetres
var realitzat = false;
function doPost(e){
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)
if(text.indexOf("/") == 0){ // Si comença per / és una comanda
if(text == '/help'){ // Llista de comandes
realitzat = true;
var resposta = "Comandes disponibles:\n";
resposta = resposta + "/lliures Aules lliures en els propers 60 minuts\n";
resposta = resposta + "/p-a (exemple: /1-1 ) Reserves per avui a l'aula indicada\n";
resposta = resposta + "/reserva Reservar una aula\n";
sendText(id,resposta);
}
if(text == '/reserva'){ // Reservar una aula
realitzat = true;
iniParam(id);
}
if(text == '/lliures'){ // Mirar quines aules estan lliures
realitzat = true;
lliure(id);
}
if(!realitzat){ // Si no era cap de les anteriors, deu ser la consulta de les reserves d'una aula
var nomAula = text.substring(1,text.length); // Li traiem la / del davant
punter = -1; // Si no troba l'aula el punter serà -1
for (var i = 0; i < idCal.length; i++){ // Mira tots els calendaris
if (nomsAules[i] == nomAula){
punter = i; // Ha trobat l'aula demanada
}
}
if (punter > -1){
aula(id, punter); // Demana la informació de l'aula
} else {
var resposta = "Comanda desconeguda";
sendText(id,resposta);
}
}
} else { // Si no comença per / potser és un paràmetre
param(id, text);
}
}
function param(id_usr, comand){ // Processa els paràmetres
var cache = CacheService.getScriptCache(); // Agafa la memòria cau
var pas = cache.get(id_usr+ind[0]); // Mira quin és el pas actual
// Mirem si l'usuari consta a la llista
if(pas == null){ // Si l'usuari és nou, no ens està enviant cap paràmetre
var resposta = "Comanda desconeguda";
sendText(id_usr,resposta);
return;
}
var estat = +pas; // Converteix a valor numèric
if (estat < 5){ // Si encara no hem rebut tots els paràmetres
cache.put(id_usr+ind[0], estat+1); // Guarda el nou pas
cache.put(id_usr+ind[estat+1], comand); // Les dades es guarden, en principi, durant deu minuts
}
if (estat == 4){ // Si ja tenim tots els paràmetres
reserva(id_usr); // Fa la reserva
} else {
sendText(id_usr,missatges[estat+1]); // Envia el missatge corresponent al pas actual
}
}
function iniParam(id_usr){ // Es prepara per rebre paràmetres
var cache = CacheService.getScriptCache(); // Agafa la memòria cau
// Si l'usuari és nou el donarem d'alta
// Si no és nou, esborrarem les dades perquè entenem que vol tornar a començar
cache.put(id_usr+ind[0], 0); // Guarda el número de pas
for(var i = 1; i < ind.length; i++){
cache.put(id_usr+ind[i], ""); // Buida les altres dades
}
sendText(id_usr,missatges[0]); // Envia el missatge que correspon
}
function fiParam(id_usr){ // Acaba la recepció de paràmetres
var cache = CacheService.getScriptCache(); // Agafa la memòria cau
for(var i = 0; i < ind.length; i++){ // Per a tots els valors possibles
cache.remove(id_usr+ind[i]); // Esborra les dades
}
}
function reserva(id_xat){ // Fa una reserva
var cache = CacheService.getScriptCache(); // Agafa la memòria cau
// Llegeix tots els paràmetres
var aula = cache.get(id_xat+ind[1]);
var dat = cache.get(id_xat+ind[2]).split("-");
var hi = cache.get(id_xat+ind[3]).split(".");
var hf = cache.get(id_xat+ind[4]).split(".");
var acte = cache.get(id_xat+ind[5]);
// Busquem l'aula
var punter = -1; // Si l'aula no hi és el punter serà -1
for(var i = 0; i < idCal.length; i++){ // Recorre tots els calendaris
if(nomsAules[i] == aula){
punter = i; // Punter de l'aula demanada
}
}
if(punter == -1){
// Si l'aula no s'ha trobat anul·lem la petició i tornem error
fiParam(id_xat); // Esborra els paràmetres
var resposta = "Aula desconeguda";
sendText(id_xat,resposta);
return;
}
try{ // Fem un try per detectar si les dades són correctes
var resultat = "";
// Data i hora d'inici
var inici = new Date();
inici.setDate(dat[0]);
inici.setMonth(dat[1] - 1); // Restem 1 perquè codifica els mesos de 0 a 11
inici.setFullYear(dat[2]);
inici.setHours(hi[0]);
inici.setMinutes(hi[1]);
inici.setSeconds(0);
// Data i hora d'acabament
var final = new Date();
final.setDate(dat[0]);
final.setMonth(dat[1] - 1); // Restem 1 perquè codifica els mesos de 0 a 11
final.setFullYear(dat[2]);
final.setHours(hf[0]);
final.setMinutes(hf[1]);
final.setSeconds(0);
var cal = CalendarApp.getCalendarById(idCal[punter]); // Agafa el calendari
if (!cal) { // Si no el troba
resultat = "Calendari no trobat!";
} else {
var esdev = cal.getEvents(inici, final); // Mira si està lliure
if (esdev.length == 0){ // Si ho està
cal.createEvent(acte, inici, final); // Fa la reserva
resultat = "Reserva feta";
} else { // Si no ho està
resultat = "L'aula està ocupada en la franja indicada";
}
fiParam(id_xat);
}
sendText(id_xat,resultat);
} catch(err) { // Si ha fallat
fiParam(id_xat);
var resposta = "No s'ha pogut fer la reserva";
sendText(id_xat,resposta);
}
}
function lliure(id_xat){ // Aules lliures en els propers 60 minuts
var resultat = "<b>Aules lliures en els propers 60 minuts:</b>\n";
var compta = 0;
var ara = new Date(); // Data i hora actuals
var ms_hora = 1000 * 60 * 60; // Mil·lisegons que té una hora
var final = new Date(ara.getTime() + ms_hora); // D'aquí a una hora
for(var i = 0; i < idCal.length; i++){ // Mira tots els calendaris
var nom = nomsAules[i]; // Nom de l'aula
var cal = CalendarApp.getCalendarById(idCal[i]); // Agafa el calendari
var esdev = cal.getEvents(ara, final); // Agafa els esdeveniments
if (esdev.length == 0){ // Si no n'hi ha cap
resultat = resultat + "L'aula " + nom + " està lliure\n";
compta++;
}
}
if(compta == 0){ // Si no se n'ha trobat cap
resultat = resultat + "Cap de les aules està lliure\n";
}
sendText(id_xat,resultat);
}
function aula(id_xat, pntr){ // Mira les reserves d'una aula
var resultat = "";
var nom = nomsAules[pntr]; // Agafa el nom
var cal = CalendarApp.getCalendarById(idCal[pntr]); // Obre el calendari
if (!cal) { // Si no el troba
resultat = "Calendari no trobat!";
} else {
var ara = new Date(); // Data i hora actuals
var final = new Date();
final.setHours(23);
final.setMinutes(59); // Final del dia
resultat = resultat + "<b>Reserves per a avui a l'aula " + nom + ":</b>\n";
var esdev = cal.getEvents(ara, final); // Agafa els esdeveniments
var numEsdev = esdev.length; // Nombre d'esdeveniments
if(numEsdev > 0){ // Si n'hi ha algun
for(var i = 0; i < numEsdev; i++){ // Els mira tots
var esdAct = esdev[i]; // Agafa un esdeveniment
var dataIni = esdAct.getStartTime(); // Hora d'inici
var horariIni = dosCar(dataIni.getHours()) + "." + dosDigits(dataIni.getMinutes());
var dataFi = esdAct.getEndTime(); // Hora d'acabament
var horariFi = dosCar(dataFi.getHours()) + "." + dosDigits(dataFi.getMinutes());
resultat = resultat + horariIni + " - " + horariFi + " - ";
resultat = resultat + esdAct.getTitle() + "\n";
}
} else { // Si no n'hi ha
resultat = resultat + "No hi ha reserves per a avui\n";
}
}
sendText(id_xat,resultat);
}
function sendText(chatId,text_env,keyBoard){ // Funció que prepara per enviar 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 dosDigits(valor){ // Si cal, posa un zero per forçar que siguin dos dígits
var sortida = "";
if (valor < 10) {
sortida = sortida + "0";
}
sortida = sortida + valor;
return sortida;
}
function dosCar(valor){ // Si cal, posa un espai per forçar que siguin dos caràcters
var sortida = "";
if (valor < 10) {
sortida = sortida + " ";
}
sortida = sortida + valor;
return sortida;
}

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