Bots de conversa de Telegram amb Google Apps Script

Bots de conversa Exemples Dades pràctiques   Recursos CITCEA
Google Apps Script Projectes Interacció   Inici

Gestionem reserves en uns calendaris

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;
}

Segona versió

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;
}

Tercera versió

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;
}

Quarta versió

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;
}

 

 

 

 

 

 

 

 

 

 

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