Scrivere un bot per Twitter con Google Script

L'idea è venuta a mio figlio: è possibile creare un bot di Twitter che ogni giorno pubblichi una nuova cifra del Pi Greco ? Detto, fatto. E pensando che questa cosa posso essere presa come punto di partenza per altre applicazioni simili, ne riporto qui i passaggi essenziali.


Come fare

Scartata a priori la fattibilità di calcolare a mano le cifre del pi greco, abbiamo pensato di utilizzare Wolfram Alpha una AI a cui è possibile sottoporre domande (in linguaggio naturale inglese) in ambito scientifico, come ad esempio "quanto dista la luna" oppure un esempio di ciò che serve a noi "qual è la 20-esima cifra del pi  greco" per avere 4 come risposta.

Poi occorre qualcosa che ci permetta di schedulare uno script ad intervalli regolari e qui la scelta è stata la più semplice: Google Script.  

Da ultimo occorre poter inviare Tweet in modo automatico e quindi sarà necessario creare un account Twitter a cui associare una nostra applicazione sul portale per sviluppatori di Twitter.


Google script

Nel nostro Google Drive creiamo un nuovo Google Apps Script, diamogli un nome, ad esempio PiTwitterBot e iniziamo a scrivere una funzione che ci permetta di interrogare Wolfram Alpha:


var WOLFRAM_ID = "XXXXXXXXXXXXXXXXX";

function askToWolframAlpha(q) {
  try {

    var api = "http://api.wolframalpha.com/v2/query?podindex=2&format=plaintext&appid="
                       + WOLFRAM_ID + "&input=" + encodeURIComponent(q);
    var response = UrlFetchApp.fetch(api, {
      muteHttpExceptiontrue
    });

    // Parse the response (XML format)
    if (response.getResponseCode() == 200) {
      var document = XmlService.parse(response.getContentText());
      var root = document.getRootElement();
      if (root.getAttribute("success").getValue() === "true") {
        return root.getChild("pod").getChild("subpod").getChild("plaintext").getText();
      }
    }
  } catch (f) {}
  return false;
}

questa funzione accetta in ingresso il testo della domanda, interroga l'API di WolframAlpha e estrae la risposta dal testo XML ottenuto. 
Per l'utilizzo della API di WolframAlpha occorre preventivamente ottenere un WOLFRAM_ID registrandosi a questo indirizzo https://products.wolframalpha.com/simple-api/documentation/

Aggiungiamo ora una funzione che ci permetta di inviare Tweet:

function sendTweet(newTweet) {

  try {
    var props = PropertiesService.getScriptProperties(),
    tweet = new Twitterlib.OAuth(props);

    // Twitter access ok ?
    if (tweet.hasAccess()) {
      tweet.sendTweet(newTweet);
    }

  } catch (f) {
    Logger.log("Error: " + f.toString());
  }

}

anche per questa funzione, che accetta in ingresso il testo del tweet che vogliamo pubblicare, sarà necessario procurarsi degli opportuni token di autenticazione, ma li vedremo in seguito. Intanto, per accedere alle funzioni di Twitterlib occorre aggiungere questa libreria al nostro script. Per farlo, è sufficiente cliccare sul simbolo + a destra di Librerie e incollare il seguente codice 11dB74uW9VLpgvy1Ax3eBZ8J7as0ZrGtx4BPw7RKK-JQXyAJHBx98pY-7 , aggiungere la libreria Twitterlib e selezionare la versione desiderata (al momento la 28 è la più recente). Maggiori info qui: https://github.com/airhadoken/twitter-lib

Costruiamo la domanda per Wolfram Alpha

Scriviamo adesso la funzione che ci permette di costruire la domanda da sottoporre a Wolfram Alpha:

var counter = 0;

function PiBot(){

  counter++;

  var question = "get the digit " + counter + " of pi";
  var answer =  askToWolframAlpha(question);
}

ad ogni chiamata questa funzione dovrebbe incrementare il valore della variabile counter. Effettuando qualche test se si prova a schedulare lo script (con il trigger disponibile per i Google Script), però ci si accorge che ciò che viene richiamo ad ogni intervallo è l'intero script (compresa l'inizializzazione della variabile) e non solo la funzione PiBot() con il risultato che l'incremento non funziona come dovrebbe. In altre parole la variabile dovrebbe mantenere il valore tra una sessione è l'altra: il problema si risolve utilizzando una specifica funzionalità resa disponibile in Google Script, quella delle proprietà associate ad ogni script. Per questo il codice va modificato in questo modo:

var props = PropertiesService.getScriptProperties();
function startBot() {
  props.setProperty('counter'0);
}
function PiBot(){

  var counter = props.getProperty('counter');

  counter++;

  var question = "get the digit " + counter + " of pi";
  var answer =  askToWolframAlpha(question);
  

  props.setProperty('counter'counter);
}

inserendo la variabile che ci interessa tra le proprietà dello script in modo da essere persistente attraverso le sessioni. Sarà sufficiente richiamare la nuova funzione startBot() una sola volta, in fase di inizializzazione e utilizzare la coppia il setProperty/getProperty ogni volta.

Costruiamo il testo del tweet

Modifichiamo ulteriormente la funzione PiBot() per costruire il testo del tweet da pubblicare, introducendo una nuova variabile che conterrà il valore del pi greco incrementato ad ogni esecuzione dello script:

function startBot() {
  props.setProperty('counter'0);
  props.setProperty('pi'"");
}

function PiBot(){

  var counter = props.getProperty('counter');
  var pi = props.getProperty('pi');

  counter++;

  var question = "get the digit " + counter + " of pi";
  var answer =  askToWolframAlpha(question);
  
  pi = pi + "" + answer;

  sendTweet(pi);
  props.setProperty('counter'counter);
  props.setProperty('pi'pi);
}

Così facendo avremmo una sequenza come questa: 3 31 314 3141 e così via: Possiamo fare di meglio, sicuramente, con un po' di codice aggiuntivo.

var maxDigits = 10;

function PiBot(){

  var counter = props.getProperty('counter');
  var pi = props.getProperty('pi');

  counter++;

  var question = "get the digit " + counter + " of pi";
  var answer =  askToWolframAlpha(question);
  
  if (pi.length == 1pi = pi + ".";
  if (pi.length > maxDigits) {
    pi = pi.substring(0maxDigits+1);

    var padding = counter - maxDigits - 1;
    if (padding > 0) {
      pi = pi + "...(" + padding + ((padding == 1)? " digit"" digits") + ")...";
    }

  }
  pi = pi + "" + answer;

  sendTweet(pi);
  props.setProperty('counter'counter);
  props.setProperty('pi'pi);
}

La nuova funzione permette di costruire una sequenza più leggibile, aggiungendo il punto decimale, limitando il numero di cifre massime che vogliamo esporre ed evidenziando quelle mancanti, con un risultato del tipo: 3.141592653...(1 digit)...9.

L'autenticazione Twitter

La libreria Twitterlib utilizzata richiede un set di 4 chiavi di autenticazione:

  • TWITTER_CONSUMER_KEY: The Consumer Key for your Twitter App
  • TWITTER_CONSUMER_SECRET: The Consumer Secret for your Twitter App
  • TWITTER_ACCESS_TOKEN: The Public part of an Access Token for your Twitter App
  • TWITTER_ACCESS_SECRET: The Secret part of an Access Token for your Twitter App

Per i primi due token, è sufficiente creare un account Twitter (se non ne avete ancora uno o se volete usarne uno nuovo) sul sito https://apps.twitter.com/ . Completate il processo di creazione di una vostra app, assegnandole un nome e quindi generate successivamente la coppia Access Token /Access Secret . Infine, fate attenzione a modificare il livello dei permessi dell'app da ESSENTIAL ad ELEVATED (fornendo una serie di informazioni aggiuntive),  altrimenti l'utilizzo dell'API Twitter sarà impedito.

Ottenute queste informazioni, le possiamo inserire nel nostro script di inizializzazione:

function startBot() {

  props.setProperties({
    TWITTER_CONSUMER_KEY"XXXXXXXXXXXXXXXXXXXXXXXXXXX",
    TWITTER_CONSUMER_SECRET"XXXXXXXXXXXXXXXXXXXXXXXXXXX",
    TWITTER_ACCESS_TOKEN"XXXXXXXXXXXXXXXXXXXXXXXXXXX",
    TWITTER_ACCESS_SECRET"XXXXXXXXXXXXXXXXXXXXXXXXXXX"
  });

  props.setProperty('counter'0);
  props.setProperty('pi'"");

}

La schedulazione

Non ci resta che schedulare il nostro script in modo che venga eseguito ogni giorno. Possiamo farlo manualmente dall'interfaccia di Google Script o modificando la funzione di inizializzazione in questo modo:

function startBot() {

  props.setProperties({
    TWITTER_CONSUMER_KEY"XXXXXXXXXXXXXXXXXXXXXXXXXXX",
    TWITTER_CONSUMER_SECRET"XXXXXXXXXXXXXXXXXXXXXXXXXXX",
    TWITTER_ACCESS_TOKEN"XXXXXXXXXXXXXXXXXXXXXXXXXXX",
    TWITTER_ACCESS_SECRET"XXXXXXXXXXXXXXXXXXXXXXXXXXX"
  });

  props.setProperty('counter'0);
  props.setProperty('pi'"");

  // Delete exiting triggers, if any
  ScriptApp.getProjectTriggers().forEach((trigger) => {
    ScriptApp.deleteTrigger(trigger);
  });

  // Set new trigger    
  ScriptApp.newTrigger("PiBot").timeBased().everyDays(1).create();

  // first execution
  PiBot();
}

La funzione adesso, dopo aver settato le variabili utilizzate, cancella eventuali schedulazioni già presenti e ne imposta una da eseguirsi ogni giorno e lancia per la prima volta il codice in modo da avere il primo tweet.

Per l'esecuzione del bot, sarà sufficiente eseguire la funzione startBot() dall'interfaccia di Google Script:


Conclusioni

Il codice è semplice ed esegue un compito semplice, ma può essere facilmente ampliato, magari per interagire con i tweet o per contenuti un po' più elaborati.

Chi è interessato, può duplicare il mio script da qui: PiTwitterBot

I tweet creati con questo bot li potete seguire qui: Even More Pi (e il primo ovviamente è stato pubblicato alle 03:14 :-)) ) 

Commenti