| Bots de conversa | Exemples | Dades pràctiques | Recursos CITCEA | |
| Google Apps Script | Projectes | Interacció | Inici |
Quan una comanda de Telegram necessita diversos paràmetres es pot optar per 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, la separació o la sintaxi dels paràmetres.
Quan creem un nou bot amb el BotFather ens trobem que ens va demanar els paràmetres 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. No sabem com ho fa el BotFather, però podem intentar imitar-lo. A continuació explicarem dues maneres de fer-ho
Un mètode, que hem posat en pràctica en aquest exemple, es basa en fer servir un full de càlcul per anar guardant els paràmetres que envia cada usuari. La primera columna del full de càlcul conté l'identificador de l'usuari, la segona conté el nombre de paràmetres que ha enviat per a la comanda en curs i després hi ha tantes columnes com paràmetres s'han de rebre. Quan un usuari envia la comanda, es mira si ja surt al full de càlcul o no (seria la primera vegada que fa servir la comanda); si no hi és, se l'afegeix. En qualsevol dels dos casos, es posa el comptador de paràmetres a zero i se li demana el primer paràmetre. Quan arriba un paràmetre, es guarda, s'incrementa el comptador i se li demana el següent. Quan arriba el darrer paràmetre s'executa la comanda, s'esborren els paràmetres i es torna a posar el comptador a zero.
Quan s'executa la comanda es posa el comptador a zero, en lloc d'esborrar l'usuari. Si s'esborrés l'usuari podríem tenir problemes. Imaginem-nos que hi ha dos usuaris fent servir la comanda simultàniament. Hi podria haver problemes si quan un usuari acaba s'esborrés la seva línia al full de càlcul i les fileres dels altres usuaris canviessin de posició a mig processar l'arribada d'un paràmetre.
Pot passar que l'usuari torna a enviar la comanda abans d'haver-la acabat de processar. En aquest cas, hem considerat que vol tornar a començar i ignorem els paràmetres rebuts i posem a zero el comptador.
Per implementar el sistema, hem creat tres funcions. Una prepara la filera del full de càlcul quan es rep la comanda, una és la que processa les comandes i la tercera neteja les dades quan ja s'ha processat la comanda.
La funció que prepara el full de càlcul comença cercant l'usuari, si el troba es guarda la posició on està. Tant si hi és com si no, posa el comptador a zero i deixa buides les caselles dels paràmetres. Després envia un missatge a l'usuari demanant el primer paràmetre.
function iniParam(id_usr){
var sh = SpreadsheetApp.openById(ssId).getSheetByName("param"); // Obre 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 totes les fileres
var row = dades[i]; /7 Agafa una filera
var num_usuari = row[0]; // Número d'usuari en la filera actual
if(num_usuari == id_usr){ // Mira si és el que cerquem
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] = ""; // Esborra tots els altres 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 set 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]); // Envia el corresponent missatge a l'usuari
}
Quan l'usuari envia un text que no porta la / al davant, s'interpreta que és un paràmetre i es passa a la segona funció. El primer que fa la funció és tornar a cercar l'usuari al full de càlcul. Si no hi és, vol dir que no està enviant cap paràmetre i se li diu que la comanda és desconeguda. Si surt al full, es mira el valor del comptador de paràmetres. Si encara no és el darrer paràmetre, s'afegeix a la casella corresponent i s'incrementa el comptador. Si encara no tenim tots els paràmetres li demana el següent. Si ja tenim tots els paràmetres, es crida a la funció que processa la comanda (reserva, en aquest cas); aquesta funció cridarà a la funció que torna a deixar el full de càlcul en la situació inicial.
En aquest cas, guardem només el paràmetre però es podria afegir que es comprovés que el paràmetre rebut es correspon amb el que s'està esperant en cada moment.
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.
function param(id_usr, comand){
var sh = SpreadsheetApp.openById(ssId).getSheetByName("param"); // Agafa el full de càlcul
var dades = sh.getDataRange().getValues(); // llegeix els valors
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++){ // Mira totes les dades
var row = dades[i]; // Agafa una filera
var num_usuari = row[0]; // Identificador d'usuari de la filera actual
if(num_usuari == id_usr){ // Mira si és el que busquem
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]; // Agafa la filera 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); // Executa la comanda
} else {
sendText(id_usr,missatges[estat + 1]);
}
}
La tercera funció és força senzilla. Només torna a posar a zero el comptador i elimina els paràmetres rebuts. Des del punt de vista del funcionament del programa, no seria necessari esborrar els paràmetres però ens pot ser útil si en algun moment hem d'entrar manualment a fer alguna comprovació al full de càlcul.
function fiParam(id_usr,fila){
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] = ""; // Esborra tots els 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
var rang = sh.getRange(fila+1, 1, 1, 7); // Defineix el rang de caselles a escriure
rang.setValues(valu); // Guarda els valors a les caselles, substituïnt els anteriors
}
Un mètode, que hem posat en pràctica en aquest exemple, es basa en fer servir la memòria cau per anar guardant els paràmetres que envia cada usuari. 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 diversos paràmetres per a cada possible usuari i hem de preveure que hi hagi dos o més usuaris fent ús simultàniament del bot. 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 diverses claus per a cada usuari. Cada clau serà el resultat d'afegir una lletra (corresponent al valor guardat) al final de l'identificador de l'usuari. Quan comenci el procés de recollida de paràmetres crearem les claus i les anirem omplint a mesura que avanci el procés. A l'acabar la comanda esborrarem totes les claus d'aquell usuari. Si no definim el temps màxim en el que es guarden les dades, en principi serà de deu minuts. Quan un usuari envia la comanda es crea el registre corresponent a la memòria cau, es posa el comptador de paràmetres a zero i se li demana el primer paràmetre. Quan arriba un paràmetre, es guarda, s'incrementa el comptador i se li demana el següent. Quan arriba el darrer paràmetre s'executa la comanda i s'esborren les dades guardades.
Pot passar que l'usuari torna a enviar la comanda abans d'haver-la acabat de processar. En aquest cas, hem considerat que vol tornar a començar i ignorem els paràmetres rebuts i posem a zero el comptador.
Per implementar el sistema, hem creat tres funcions. Una crea el registre a la memòria cau quan es rep la comanda, una és la que processa les comandes i la tercera neteja les dades quan ja s'ha processat la comanda.
La funció que prepara el registre guardarà l'índex 0 (cap paràmetre rebut) i posarà a zero els altres registres. El vector ind és el que guarda els indicadors dels diferents registres. L'indicador (un caràcter) s'afegeix al final de l'identificador d'usuari per crear les diferents claus. Després envia un missatge a l'usuari demanant el primer paràmetre.
function iniParam(id_usr){
var cache = CacheService.getScriptCache(); // Llegeix 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); // Nombre de paràmetres
for(var i = 1; i < ind.length; i++){
cache.put(id_usr+ind[i], ""); // Paràmetres
}
sendText(id_usr,missatges[0]);
}
Quan l'usuari envia un text que no porta la / al davant, s'interpreta que és un paràmetre i es passa a la segona funció. El primer que fa la funció és llegir les dades a la memòria cau. Si l'usuari no hi és, vol dir que no està enviant cap paràmetre i se li diu que la comanda és desconeguda. Si hi ha dades, es mira el valor del comptador de paràmetres. Si encara no és el darrer paràmetre, s'afegeix a la clau corresponent i s'incrementa el comptador. Si encara no tenim tots els paràmetres li demana el següent. Si ja tenim tots els paràmetres, es crida a la funció que processa la comanda (reserva, en aquest cas); aquesta funció, quan hagi acabat, cridarà a la que esborra les dades rebudes.
En aquest cas, guardem només el paràmetre però es podria afegir que es comprovés que el paràmetre rebut es correspon amb el que s'està esperant en cada moment. El valor de la variable pas pot ser considerat text i caldrà convertir-lo a numèric.
function param(id_usr, comand){
var cache = CacheService.getScriptCache(); // Llegeix la memòria cau
var pas = cache.get(id_usr+ind[0]); // Nombre de paràmetres rebuts
// 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; // Passem al pas següent
if (estat < 5){ // Si encara no hem rebut tots els paràmetres
cache.put(id_usr+ind[0], estat+1); // Guarda el nou estat
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); // Executa la comanda
} else {
sendText(id_usr,missatges[estat+1]); // Demana el paràmetre següent
}
}
La tercera funció és molt senzilla. Només esborra de la memòria cau les dades corresponents a l'usuari.
function fiParam(id_usr){
var cache = CacheService.getScriptCache(); // Llegeix la memòria cau
for(var i = 0; i < ind.length; i++){
cache.remove(id_usr+ind[i]); // Esborra les dades
}
}

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