Bots de conversa de Telegram amb Google Apps Script

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

Generem un diagrama de línies que es pot consultar per GET i per Telegram

En aquest exemple agafarem unes dades que tenim en un full de càlcul i generarem una imatge vectorial en forma de diagrama de barres amb els valors del full de càlcul. En un exemple anterior hem suposat que el nostre full de càlcul sempre té el mateix nombre de dades i aquestes es mouen dins un rang definir. En l'exemple actual el nombre de dades pot ser qualsevol (superior a 1, per descomptat) i aquestes poden prendre qualsevol valor positiu.

La següent imatge mostra el contingut del full de càlcul en un moment donat:

Full de càlcul

El resultat que obtindrem serà el gràfic següent:

Gràfic

En el nostre cas podrem obtenir el gràfic de dues maneres. Una d'elles serà a l'executar l'script des del navegador (funció doGet) i obtindrem una pàgina web que contindrà el gràfic. L'altra serà accedint des de Telegram (funció doPost) i, en cas de fer servir la comanda /grafic obtindrem una imatge del gràfic i un enllaç per descarregar-lo en format PDF.

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 bot de conversa amb Telegram 
var ssId = "^^ThsoSjkeMSfwEKy4mn_4QEYH96sxv3VURqE3WHCTswDA";  // Identificador del full de càlcul
var id_carpeta = "^^Hlod5Y4qEsLF9HrIsE6oYQa32f6NHeo";  // Carpeta on guardarem el fitxer
// Definim els colors que surten al gràfic
var negre = "#000000";
var blau = "#0000FF";
var gris = "#CCCCCC";
var transparent = "null";
function doGet(e){
  var resp = grafic();  // Creem el gràfic
  var nomFitxer = "grafic";  // Nom del fitxer
  // Creem el fitxer
  var fitxerNou = DriveApp.getFolderById(IdCarpeta).createFile(nomFitxer + ".svg",resp);
  var idFitxer = fitxerNou.getId();  // Identificador del fitxer
  var urlDescFitxer = fitxerNou.getDownloadUrl();  // Adreça per descarregar el fitxer
  var urlFitxer = "https://drive.google.com/uc?export=view&id=" + idFitxer;  // Adreça per veure el fitxer
  // Codi HTML de la pàgina web
  var pagina = "<!DOCTYPE HTML>" + "\n";
  pagina = pagina + "<html>" + "\n";
  pagina = pagina + "<head>" + "\n";
  pagina = pagina + "<meta charset='UTF-8'>" + "\n";
  pagina = pagina + "</head>" + "\n";
  pagina = pagina + "<body>" + "\n";
  pagina = pagina + "<h1>Valors de la setmana passada</h1>" + "\n";
  pagina = pagina + resp + "\n";
  pagina = pagina + "</body>" + "\n";
  pagina = pagina + "</html>" + "\n";
  return HtmlService.createHtmlOutput(pagina);  // Retorna la pàgina web
}
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 realitzat = false;
  if(text == '/grafic'){
    var resposta = "Estem preparant el gràfic. Espera un moment.";  // Missatge que s'envia
    sendText(id,resposta);  // mentre es prepara el gràfic, perquè triga una mica
    var resp = grafic();  // Genera el gràfic
    var nomFitxer = "grafic";  // Nom del fitxer
    var fitxerNou = DriveApp.getFolderById(IdCarpeta).createFile(nomFitxer + ".svg",resp);  // Crea el fitxer
    var idFitxer = fitxerNou.getId();  // Identificador del fitxer
    var urlDescFitxer = fitxerNou.getDownloadUrl();  // Enllaç per descarregar el fitxer
    // Enllaç per veure el fitxer al navegador
    var urlFitxer = "https://drive.google.com/uc?export=view&id=" + idFitxer;
    var blob = UrlFetchApp.fetch(urlFitxer).getBlob();  // Agafa el gràfic en format binari
    var b64 = blob.getContentType() + ';base64,'+ Utilities.base64Encode(blob.getBytes());
    // Creem una pàgina web de mínims que serà la base del PDF
    var htmlEst = '<style type="text/css" media="print">@page {size: landscape;} </style>';
    var htmlInst = htmlEst + '<img src="data:' + b64 + '" width=900 alt="Gràfic">';
    var html = HtmlService.createHtmlOutput(htmlInst);  // Crea la pàgina web
    // Crea el PDF
    var pdf = DriveApp.createFile(html.getAs("application/pdf").setName(nomFitxer + ".pdf"));
    var urlDescPDF = pdf.getDownloadUrl();  // Enllaç per descarregar el fitxer
    var pdfId = pdf.getId();  // Identificador del fitxer
    var repetir = true;
    // Hem d'esperar que estigui llesta la miniatura
    var cnt = 0;  // Comptador de dècimes de segon
    while(repetir){  // Mentre no estigui
      var thumb = DriveApp.getFileById(pdfId).getThumbnail();  // Mirem si ja s'ha creat la miniatura
      Utilities.sleep(100);  // Esperem una dècima de segon
      cnt++;
      if((cnt > 100) || (thumb)){  // Si ja està o han passat deu segons
        repetir = false;  // Sortim del bucle
      }
    }
    // El títol conté l'enllaç
    var descrip = "Pots descarregar el PDF aquí: " + urlDescPDF;
    sendBlobFile(id,thumb,descrip);  // Enviem la imatge
    realitzat = true;
  }
  if (!realitzat){
    var resposta = "Comanda desconeguda";
    sendText(id,resposta);
  }
}
function grafic(){
  var resp = "";
  var sh = SpreadsheetApp.openById(ssId).getSheetByName("linia");  // Agafem el full
  var dades = sh.getDataRange().getValues();  // Llegim les dades
  // Guardem els valors en un vector i ens quedem amb el màxim
  var valors = new Array();
  var maxY = 0;  // El màxim, de moment, és zero
  for (var i in dades){  // Mirem totes les dades
    if (i > 0){  // Ignorem la línia dels títols
      var row = dades[i];  // Llegeix una filera
      valors.push(row[0]);  // Guardem els valors
      if (row[0] > maxY){  // Busquem el màxim
        maxY = row[0];  // Guardem el valor més alt
      }
    }
  }
  var numVal = valors.length;  // Nombre de punts
  // Decidim quantes marques tindrà cada eix i amb quines separacions
  maxY = arrod(maxY, 2);
  var divY = maxY / 10;  // Arrodoneix, per excés, a dues xifres
  // Creem la capçalera
  var grafic =  cap(640, 480, "Capa 1")+ '\n';
  // Fem els eixos
  var zeroX = 50;
  var zeroY = 435;
  var fiX = 620;
  var fiY = 25;
  grafic = grafic + linia(zeroX-5, zeroY, fiX, zeroY, 3, negre, "eixX") + '\n';
  grafic = grafic + linia(zeroX, fiY, zeroX, 440, 3, negre, "eixY") + '\n';
  // Posem les divisions de l'eix Y
  for (var i = 0; i <= 10; i++){  // Sempre són 10 divisions
    var ym = zeroY - ((zeroY - fiY) /  10) * i;
    var valor = divY * i;
    var noml = 'm' + i;
    var nomt = 'tm' + i;
    var nomq = 'q' + i;
    grafic = grafic + linia(zeroX-5, ym, zeroX, ym, 2, negre, noml) + '\n';
    grafic = grafic + text(zeroX-10, ym+5, valor, 16, negre, "d", 0, 0, nomt) + '\n'; 
  } 
  // Posem les divisions de l'eix X
  var xm;
  var val;
  for(var i = 0; i < 5; i++){
    val = Math.ceil(i * numVal / 4);
    if (i == 0){
      val = 1;
    }
    xm = zeroX + ((fiX - zeroX) / (numVal - 1)) * (val - 1);
    var nomlh = 'mx' + i;
    var nomth = 'tx' + i;
    var nomqh = 'qx' + i;
    grafic = grafic + linia(xm, zeroY+5, xm, zeroY, 2, negre, nomlh) + '\n';
    grafic = grafic + text(xm, zeroY + 25, val, 16, negre, "c", 0, 0, nomth) + '\n'; 
  }
  // Dibuixem la línia del gràfic
  for (var i = 1; i < numVal; i++){
    var nomlg = 'L' + i;
    var xp = zeroX + ((fiX - zeroX) / (numVal - 1)) * (i - 1);
    var xm = zeroX + ((fiX - zeroX) / (numVal - 1)) * i;
    var yp = zeroY - ((zeroY - fiY) /  maxY) * valors[i-1];
    var ym = zeroY - ((zeroY - fiY) /  maxY) * valors[i];
    grafic = grafic + linia(xp, yp, xm, ym, 4, blau, nomlg) + '\n';
  } 
  // Posem el final del fitxer svg
  grafic = grafic + peu(); 
  return grafic;
}
function linia(xi, yi, xf, yf, gruix, color, ident){
  var resp = '<line id="' + ident + '" x1="' + xi + '" y1="' + yi + '" x2="' + xf + '" y2="' + yf;
  resp = resp + '" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="';
  resp = resp + gruix + '" stroke="' + color + '" fill="none"/>';
  return resp;
}
function text(x, y, txt, mida, color, alin, negreta, cursiva, ident){
  if (alin == "e"){  // Esquerra
    var aln = "start";
  }
  if (alin == "c"){  // Centre
    var aln = "middle";
  }
  if (alin == "d"){  // Dreta
    var aln = "end";
  }
  var resp = '<text id="' + ident + '" xml:space="preserve" text-anchor="' + aln;
  resp = resp + '" font-family="serif" font-size="' + mida + '" x="' + x + '" y="' + y +'"'; 
  if (negreta == 1){
    resp = resp + ' font-weight="bold"';
  }
  if (cursiva == 1){
    resp = resp + ' font-style="italic"';
  }
  resp = resp + ' stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null"';
  resp = resp + ' stroke-width="0" stroke="';
  resp = resp + color + '" fill="' + color + '">' + txt + '</text>';
  return resp;
}
function cap(ample, alt, titol){
  var resp = '<svg width="' + ample + '" height="' + alt;
  resp = resp + '" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"';
  resp = resp + ' xmlns:xlink="http://www.w3.org/1999/xlink">' + '\n';
  resp = resp + '<g>' + '\n' + '<title>' + titol + '</title>';
  return resp;
}
function peu(){
  var resp = '</g>' + '\n' + '</svg>';
  return resp;
}
function arrod(valor, numxifsig){
  //  Arrodoneix, per excés, a dues xifres significatives
  var val = valor;
  var iter = 0;
  if(val!=0){
    if(val > 10**numxifsig){
      while(val > 10**numxifsig){
        iter++;
        val = val/10;
      }
      val = Math.ceil(val);
      val = val * 10**iter;
    } else {
      while(val < 10**(numxifsig-1)){
        iter++;
        val = val*10;
      }
      val = Math.ceil(val);
      val = val / 10**iter;
    }
  }
  return val;
}
function sendText(chatId,text_env,keyBoard){  // Funció que envia un text o un teclat a Telegram 
  keyBoard = keyBoard || 0;
  if(keyBoard.inline_keyboard || keyBoard.keyboard){
    var data = {
      method: "post",
      payload: {
        method: "sendMessage",
        chat_id: String(chatId),
        text: text_env,
        parse_mode: "HTML",
        reply_markup: JSON.stringify(keyBoard)
      }
    }
  } else {
    var data = {
      method: "post",
      payload: {
        method: "sendMessage",
        chat_id: String(chatId),
        text: text_env,
        parse_mode: "HTML"
      }
    }
  }
  UrlFetchApp.fetch( telegramUrl + '/', data);
}
function sendBlobFile(chatId,blob_data,caption){
  var payload = {
    method: "sendPhoto",
    chat_id: String(chatId),
    photo: blob_data,
    caption: caption,
    parse_mode: "HTML"
  };
  var options = {
    method: "POST",
    payload: payload,
    muteHttpExceptions: true
  };
  UrlFetchApp.fetch( telegramUrl + '/', options);
}

Fixem-nos que hem creat la funció arrod que ens arrodoneix un valor a dues xifres significatives. Això ens permetrà que si el valor màxim és, per exemple, 143 l'eix vagi de 0 a 150.

Ens pot interessar que ens dibuixi també una quadrícula, com a la imatge següent.

Gràfic

Per aconseguir-ho, caldria canviar la funció grafic per la següent:

function grafic(){
  var resp = "";
  var sh = SpreadsheetApp.openById(ssId).getSheetByName("linia");  // Agafem el full
  var dades = sh.getDataRange().getValues();  // Llegim els valors
  // Guardem els valors en un vector i ens quedem amb el màxim
  var valors = new Array();
  var maxY = 0;  // El màxim, de moment, és zero
  for (var i in dades){  // Mirem totes les dades
    if (i > 0){  // Ignorem la filera dels títols
      var row = dades[i];  // Agafem una filera
      valors.push(row[0]);  // Guardem els valors
      if (row[0] > maxY){  // Busquem el màxim
        maxY = row[0];  // Guardem el valor més alt
      }
    }
  }
  var numVal = valors.length;  // Nombre de valors
  // Decidim quantes marques tindrà cada eix i amb quines separacions
  maxY = arrod(maxY, 2);
  var divY = maxY / 10;  // Arrodoneix, per excés, a dues xifres
  // Creem la capçalera
  var grafic =  cap(640, 480, "Capa 1")+ '\n';
  // Fem els eixos
  var zeroX = 50;
  var zeroY = 435;
  var fiX = 620;
  var fiY = 25;
  grafic = grafic + linia(zeroX-5, zeroY, fiX, zeroY, 3, negre, "eixX") + '\n';
  grafic = grafic + linia(zeroX, fiY, zeroX, 440, 3, negre, "eixY") + '\n';
  // Posem les divisions de l'eix Y
  for (var i = 0; i <= 10; i++){  // Sempre són 10 divisions
    var ym = zeroY - ((zeroY - fiY) /  10) * i;
    var valor = divY * i;
    var noml = 'm' + i;
    var nomt = 'tm' + i;
    var nomq = 'q' + i;
    grafic = grafic + linia(zeroX-5, ym, zeroX, ym, 2, negre, noml) + '\n';
    grafic = grafic + text(zeroX-10, ym+5, valor, 16, negre, "d", 0, 0, nomt) + '\n'; 
    if(i > 0){
      grafic = grafic + linia(zeroX+1, ym, fiX, ym, 1, gris, nomq) + '\n';
    }
  } 
  // Posem les divisions de l'eix X
  var xm;
  var val;
  for(var i = 0; i < 5; i++){
    val = Math.ceil(i * numVal / 4);
    if (i == 0){
      val = 1;
    }
    xm = zeroX + ((fiX - zeroX) / (numVal - 1)) * (val - 1);
    var nomlh = 'mx' + i;
    var nomth = 'tx' + i;
    var nomqh = 'qx' + i;
    grafic = grafic + linia(xm, zeroY+5, xm, zeroY, 2, negre, nomlh) + '\n';
    grafic = grafic + text(xm, zeroY + 25, val, 16, negre, "c", 0, 0, nomth) + '\n'; 
    if(i > 0){
      grafic = grafic + linia(xm, zeroY-1, xm, fiY, 1, gris, nomqh) + '\n';
    }
  }
  // Dibuixem la línia del gràfic
  for (var i = 1; i < numVal; i++){
    var nomlg = 'L' + i;
    var xp = zeroX + ((fiX - zeroX) / (numVal - 1)) * (i - 1);
    var xm = zeroX + ((fiX - zeroX) / (numVal - 1)) * i;
    var yp = zeroY - ((zeroY - fiY) /  maxY) * valors[i-1];
    var ym = zeroY - ((zeroY - fiY) /  maxY) * valors[i];
    grafic = grafic + linia(xp, yp, xm, ym, 4, blau, nomlg) + '\n';
  } 
  // Posem el final del fitxer svg
  grafic = grafic + peu(); 
  return grafic;
}

 

 

 

 

 

 

 

 

 

 

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