Filtri avanzati su gmail

Tags: 

Il sistema di filtri base di Gmail è estremamente limitato: non essendoci i concetti di ordine/priorità tra filtri e di terminazione dell'esecuzione della catena di filtri non è possibile creare dei flussi personalizzati. A questo si aggiunge il sistema di raggruppamento dei messaggi in thread (i filtri si applicano sempre sull'intero thread e non sul singolo messaggio), e la gestione delle espressioni che, per quanto piuttosto flessibile, non sempre riesce a risolvere tutti i problemi.

Se abbiamo tonnellate di mail da smistare, e regole complesse per farlo, i filtri di gmail potrebbero quindi non essere sufficienti.

Ma con un pò di buona volonta possiamo sopperire a questa mancanza attraverso un sistema di filtri "esterno". In particolare, possiamo usare le funzioni di Scripting che ci ci mette a disposizione google con i Google Apps Script.

Questo sistema permette di interagire a basso livello con tantissimi servizi dell'universo google, attraverso degli script javascript. Le possibilità sono enormi, e in questo caso le useremo per smistare la posta come vogliamo noi.

In questo articolo viene proposta una vera e propria libreria di gestione filtri, che chiamo Gmail Advanced Filters.

Installazione

1) Andate nella vostra casella google mail

2) Accedete a Google Drive usando la barra in alto

3) Create un nuovo script premendo su "Crea", "Script", "Crea Script per: Gmail"

4) Cancellate il codice esistente e inserite quello qui sotto:

/**
 * Google Advanced Filters v1.0.20130105
 * by Eric Berdondini
 * http://myhq.it/google-advanced-filters
 */
 
// Label che viene impostata nei nuovi messaggi in ingresso. Es: "Root/Child")
var PROC_LABEL = "_PROC_";
var PROC_DONE_LABEL = false; //"_PROCDONE_";
 
var TEST_MODE = false;
var VERBOSE = true;
 
// Numero massimo di thread elaborati a ogni esecuzione
var MAX_THREADS_PROCESSED = 20;
 
var currentThread = null;
var currentMessage = null;
var currentThreadMessage = null;
var threadLabelsLoaded = {};
var labelsPool = {};
 
var starttime = null;
 
// -------------------------------------------------------------------------------------------
 
function _processThread(thread) {
  // ...
}
 
// -------------------------------------------------------------------------------------------
// FILTRI E AZIONI
 
function fromIs(email) {
  return _emailIs(currentThreadMessage.getFrom(), email);
}
 
function fromMatch(match) {
  return currentThreadMessage.getFrom().match(match);
}
 
function toIs(email) {
  return _emailIs(currentThreadMessage.getTo(), email);
}
 
function toMatch(match) {
  return currentThreadMessage.getTo().match(match);
}
 
function hasLabel(labelname) {
  return _hasGmailLabel(currentThread, labelname);
}
  
function subjectStartsWith(t) {
  return currentThreadMessage.getSubject().indexOf(t) == 0;
}
 
function doArchive() {
  if (!TEST_MODE)
    currentThread.moveToArchive();
  logVerbose("archive");
}
 
function doDelete() {
  if (!TEST_MODE)
    currentThread.moveToTrash();
  logVerbose("delete");
}
 
function doRead() {
  if (!TEST_MODE)
    currentThread.markRead();
  logVerbose("read");
}
 
function doUnimportant() {
  if (currentThread.isImportant()) {
    if (!TEST_MODE)
      currentThread.markUnimportant();
    logVerbose("unimportant");
  }
}
 
function doSetLabel(labelname) {
  _delAllGmailLabel(currentThread);
  _addGmailLabel(currentThread, labelname);
}
 
function doAddLabel(labelname) {
  _addGmailLabel(currentThread, labelname);
}
 
function doRemoveLabel(labelname) {
  _delGmailLabel(currentThread, labelname);
}
 
function doRemoveAllLabels() {
  _delAllGmailLabel(currentThread);
}
 
function doFixLabel(labelname) {
  if (hasLabel(labelname))
    doSetLabel(labelname);
}
 
function _emailIs(messageEmail, match) {
  return messageEmail == match || messageEmail.indexOf("<" + match + ">") >= 0 || messageEmail.indexOf(match) == 0 || messageEmail.indexOf(", " + match) > 0;
}
 
// -------------------------------------------------------------------------------------------
 
/**
 * Funzione da eseguire via trigger
 */
function process() {
  starttime = Date.now();
  var label = _getGmailLabel(PROC_LABEL);
  //var labelDone = PROC_DONE_LABEL ? _getGmailLabel(PROC_DONE_LABEL) : false;
  var threads = label.getThreads(0, MAX_THREADS_PROCESSED);
  Logger.log("THREADS TO BE PROCESSED: " + threads.length);
  //var st = Date.now();
  var messages = GmailApp.getMessagesForThreads(threads);
  //logVerbose("GmailApp.getMessagesForThreads", st);
  
  for (var i = 0; i < threads.length; i++) {
    currentThread = threads<img class="icon" src="/sites/all/libraries/fugue/icons/information-button.png" width="16" height="16" alt="i" />;
    currentThreadMessage = messages<img class="icon" src="/sites/all/libraries/fugue/icons/information-button.png" width="16" height="16" alt="i" />[0];
    currentMessage = messages<img class="icon" src="/sites/all/libraries/fugue/icons/information-button.png" width="16" height="16" alt="i" />[messages[i].length - 1];
    logInfo("OPEN THREAD: " + currentThreadMessage.getId() + " | " + currentThreadMessage.getSubject() + " | " + currentThreadMessage.getTo());
    _processThread(threads<img class="icon" src="/sites/all/libraries/fugue/icons/information-button.png" width="16" height="16" alt="i" />);
    
    _delGmailLabel(currentThread, PROC_LABEL);
    
    if (PROC_DONE_LABEL) {
      _addGmailLabel(currentThread, PROC_DONE_LABEL);
    }
 
    logInfo("CLOSE THREAD: " + currentThread.getFirstMessageSubject());
  }
  _applyGmailLabels();
};
 
// -------------------------------------------------------------------------------------------
 
function _getGmailLabels(thread) {
  var id = thread.getId();
  if (id in threadLabelsLoaded) {
    var labels = {};
    for (var labelname in labelsPool) {
      if (labelsPool[labelname].threads[id] && labelsPool[labelname].threads[id].current)
        labels[labelname] = labelsPool[labelname].object;
    }
    return labels;
  } else {
    var labels = thread.getLabels();
    var result = {};
    for (var i = 0; i < labels.length; i++) {
      if (!labelsPool[labels[i].getName()])
        labelsPool[labels[i].getName()] = {
          object : labels<img class="icon" src="/sites/all/libraries/fugue/icons/information-button.png" width="16" height="16" alt="i" />,
          threads : {}
        };
      labelsPool[labels[i].getName()].threads[id] = {
        previous : true,
        current : true
      };
      result[labels[i].getName()] = labels<img class="icon" src="/sites/all/libraries/fugue/icons/information-button.png" width="16" height="16" alt="i" />;
    }
    threadLabelsLoaded[id] = thread;
    logVerbose("labels loaded");
  }
}
 
function _getGmailLabel(labelname) {
  if (!(labelname in labelsPool)) {
    //var st = Date.now();
    labelsPool[labelname] = {
      object : GmailApp.getUserLabelByName(labelname),
      threads : {}
    }
    //logVerbose("GmailApp.getUserLabelByName", st);
  }
  return labelsPool[labelname].object;
}
 
function _hasGmailLabel(thread, labelname) {
  var id = thread.getId();
  if (!(id in threadLabelsLoaded))
    _getGmailLabels(thread);
  return labelsPool[labelname] && labelsPool[labelname].threads[id] && labelsPool[labelname].threads[id].current;
}
 
function _addGmailLabel(thread, labelname) {
  var id = thread.getId();
  if (!(id in threadLabelsLoaded))
    _getGmailLabels(thread);
  if (!(labelname in labelsPool))
    _getGmailLabel(labelname);
  if (!labelsPool[labelname].threads[id])
    labelsPool[labelname].threads[id] = {
      previous : false,
      current : true
    };
  else
    labelsPool[labelname].threads[id].current = true;
  logVerbose("add label " + labelname + " (cache)");
}
 
function _delGmailLabel(thread, labelname) {
  var id = thread.getId();
  if (!(id in threadLabelsLoaded))
    _getGmailLabels(thread);
  if (labelsPool[labelname].threads[id] && labelsPool[labelname].threads[id].current)
    labelsPool[labelname].threads[id].current = false;
  logVerbose("del label " + labelname + " (cache)");
}
 
function _delAllGmailLabel(thread) {
  var id = thread.getId();
  if (!(id in threadLabelsLoaded))
    _getGmailLabels(thread);
  for (var labelname in labelsPool) {
    if (labelsPool[labelname].threads[id] && labelsPool[labelname].threads[id].current)
      labelsPool[labelname].threads[id].current = false;
  }
  logVerbose("del all labels (cache)");
}
 
function _applyGmailLabels() {
  for (var labelname in labelsPool) {
    if (labelsPool[labelname].object) {
      var threadsAdd = [];
      var threadsAddLog = "";
      var threadsRemove = [];
      var threadsRemoveLog = "";
      for (var tid in labelsPool[labelname].threads) {
        var data = labelsPool[labelname].threads[tid];
        if (data.current && !data.previous) {
          threadsAdd.push(threadLabelsLoaded[tid]);
          threadsAddLog += tid + " ";
        }
        else if (!data.current && data.previous) {
          threadsRemove.push(threadLabelsLoaded[tid]);
          threadsRemoveLog += tid + " ";
        }
      }
      if (!TEST_MODE) {
        if (threadsAdd.length)
          labelsPool[labelname].object.addToThreads(threadsAdd);
        if (threadsRemove.length)
          labelsPool[labelname].object.removeFromThreads(threadsRemove);
      }
      if (threadsAdd.length)
        logVerbose("label " + labelname + " added to " + threadsAddLog);
      if (threadsRemove.length)
        logVerbose("label " + labelname + " removed from " + threadsRemoveLog);
    }
  }
}
 
// -------------------------------------------------------------------------------------------
 
function logInfo(message) {
  Logger.log("i: " + message + " [" + (Date.now() - starttime) + "ms]");
}
 
function logVerbose(message, st) {
  if (VERBOSE)
    Logger.log("d:   " + message + " [" + (Date.now() - starttime) + "ms]" + (st ? " [" + (Date.now() - st) + "ms]" : " -"));
}

5) Impostate un trigger del processo (menu "Risorse", "Trigger del processo"), con i parametri "process", "basato sul tempo", "timer minuti", "ogni minuto"

6) Impostate il nome "Google Advanced Filter" e salvate. (Più tardi ci occuperemo di configurare i filtri)

7) Tornate su Google Mail, andate in "Impostazioni", "Filtri" e aggiungete questo filtro:

Da "*", Applica l'etichetta "_PROC"
(Nota: Dopo aver messo il filtro "Da" vi mostrerà un popup di avvertimento, andate avanti. L'etichetta "_PROC_" dovrà essere creata.)

L'installazione è terminata!

 

  • Cosa abbiamo ottenuto? Ogni nuovo messaggio che arriverà a Gmail avrà l'etichetta "_PROC_", che in pratica è un "flag" che serve a far capire allo script quali sono i messaggi nuovi.
  • Lo script, eseguito una volta al minuto, prende tutti i messaggi nuovi (etichettati con "_PROC_"), li filtra e quindi toglie l'etichetta "_PROC_".
  • Alcuni avvertimenti:
  • Per come funziona il sistema, potrà capitare che quando andate nella caselle vedrete dei messaggi con etichetta "_PROC_" non ancora smistati. Ignorateli (o leggeteli se volete), dopo al massimo 1 minuti verranno smistati.
    Lo script ogni tanto esce con degli errori "innocui" (nel senso che non pregiudicano il funzionamento). Ve ne accorgerete perchè vi arriveranno delle notifiche in mail (di base 1 al giorno di notte, quando capita). Francamente non ho ancora capito perchè (immagino problemi di multi-threading o qualche bug temporaneo di google scripts). Eventualmente potete disabilitare le notifiche dai trigger dello script.

     

    Configurazione

    Editando lo script è possibile configurare i vari filtri.

    Nella prima parte ci sono alcune opzioni che in genere possono essere lasciate così come sono (ma se volete potete giocarci).

    Nella seconda parte, all'interno di "function _processThread(thread) {", potete inserire il codice dei filtri.

    Il codice non è altro che javascript, che ha a disposizione diverse funzioni di filtraggio e di azioni a disposizione. L'elenco di queste funzioni lo potete vedere nella terza parte dello script, subito sotto a _processThread, e i nomi spero siano auto-documentali :)

  • Da notare che non tutte le funzioni sono testatissime, se avete qualche problema fate sapere.
  • Ecco un esempio di filtro, per capire come funziona:

    function _processThread(thread) {
      if (subjectStartsWith("[LIST]") || subjectStartsWith("List Message:")) {
        doSetLabel("Debug/Lists");
        doArchive();
        doRead();
      }
      if (toIs("my@address.com") && (subjectStartsWith("[LIST1]") || subjectStartsWith("[LIST2]") || subjectStartsWith("[LIST3]")))
        doSetLabel("Varie/Lists");
      doFixLabel("Varie/Lists");
      doFixLabel("Varie");
    }

    Mi soffermo sulla sola funzione doFixLabel, che da sola potrebbe risolvere uno dei problemi più importanti della gestione filtri base: l'assenza di priorità.

    La funzione in se è molto semplice: se il thread ha la label specificata, elimina tutte le altre label.

    Può venirci in aiuto se continuiamo a usare i filtri Gmail, ma abbiamo dei messaggi che soddisfano più regole associate a delle label (di solito delle regole gerarchiche, una piu' restrittiva dell'altra), e noi vogliamo che alla fine il messaggio abbia una sola label (quella più restrittiva). In pratica vogliamo smistare i messaggi in un albero di label in maniera esclusiva (se un messaggio fa parte di una label non fa parte di quella sopra).

    In questo caso e' sufficiente impostare tra i filtri avanzati dei "doFixLabel" ordinati dal più restrittivo al meno restrittivo (Nell'esempio prima "Varie/Lists", e poi il più generico "Varie").

    Grazie a questo sistema in uno dei miei account Gmail continuo a usare i filtri base, e tramite dei soli doFixLabel ordinati per bene ottengo il risultato voluto (i messaggi ben smistati in maniera esclusiva tra le varie label).

    4 Comments

    Errori paiono ovvi. Codice che non capisco? Riordinare filtri?

    Non potrebbe non dare errore, visto che nelle funzioni di debug utilizzi variabili che sono state commentate (come ad esempio, st). Inoltre, vedo che usi una label che però è stata in parte commentata nel codice (PROC_DONE se non sbaglio), inoltre non dai istruzioni di definirla. A parte questo, che sintassi è la seguente: threads<codice Html> oppure: messages<codice html>[index]? Sbaglio o non mi sembra JavaScript standard? In ogni caso le tue funzioni, per me sarebbero pericolose, in quanto io uso combinare più label per riordinare la mia posta, non vorrei mai averne solo una. A me in realtà servirebbe solo riuscire a riordinare i filtri già inseriti (scambiarli di posto), e poter specificare uno stop alla valutazione della singola regola.

    ok ok :-)

    Caro Eric, in effetti, riguardando un po' meno velocemente il codice, direi che hai ragione tu. Sono vere le cose che notavo, ma poi l'errore nella funzione viene previsto ed evitato. Quindi, non dovrebbe essere quello il punto. Stavo solo cercando di aiutarti a trovare il punto che, secondo la tua stessa descrizione, potrebbe causare quei messaggi di errore che dicevi, ma sono stato un po' superficiale e affrettato, probabilmente sarebbe necessario un debug che ora non posso fare. Dettaglio: Quando LogVerbose viene richiamata, la possibilità che la variabile st non sia definita sembra essere gestito correttamente dalla espressione: (st ? " [" + (Date.now() - st) + "ms]" : " -")) Quindi in questo caso effettivamente, non dovrebbe dare errore. Ricostruire un sistema alternativo completo di filtri, comunque, mi sembra un po' uno spreco di risorse, non sarebbe meglio tentare di convincere Google a migliorare il sistema esistente? Le tue soluzioni e il tuo codice comunque sono molto interessanti, andrò a cercarmi la documentazione degli oggetti messi a disposizione da Gmail per vedere se c'è qualche altra possibilità che consenta di risolvere il mio problema. Bye Bye e grazie, Bert64It

    Aggiungi un commento

    Scrivi la risposta in lettere (ad esempio "tre" e non "3")