Come ottenere un feed RSS con le Twitter API in versione 1.1
Da circa una decina di giorni, Twitter ha apportato grossi cambiamenti alle proprie API, cambiamenti che hanno afflitto me – come developer di app per Windows Phone e Windows 8 – e non solo me. Basta una googlata per venire a sapere che chiunque abbia sviluppato app (per OS mobile, quindi iOS ed Android, ma non solo) che accedono a Twitter sia rimasto pesantemente fregato.
Prima infatti era possibile effettuare una chiamata via http al seguente url:
http://api.twitter.com/1/statuses/user_timeline.rss?screen_name=IgorDamiani
Con questo url si otteneva un bel feed RSS dell’utente Twitter passato come parametro, in questo caso “IgorDamiani”, cioè me stesso. Il feed era quindi consumabile dalle app per visualizzare tutti i tweet di un certo utente. Questa cosa era valida fino a 10 giorni fa, appunto. Adesso dà un messaggio inequivocabile: “The Twitter REST API v1 is no longer active. Please migrate to API v1.1.”. Morale: bisogna usare le API 1.1, che sostanzialmente hanno introdotto due cambiamenti:
- autenticazione obbligatoria via OAuth
- Json-only: questo significa che le API Twitter vi rispondono sempre e solo in formato JSON, mentre il feed RSS (che chiaramente è XML) non è più supportato
- il nuovo url è nel formato http://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=IgorDamiani
Bella fregatura, vero? Ovviamente la soluzione c’è, basta adeguarsi, e fortunatamente io sono partito con qualche mese di anticipo, facendo prima di tutto in modo di far puntare tutte le mie app ad un unico url, hostato sui miei server, in modo tale da diventare trasparente rispetto ai cambiamenti apportati da Twitter. Quindi, diciamo, se ho 60 app che utilizzano Twitter, non ho cablato in ciascuna app l’url di Twitter, ma un mio url, che sarà sempre valido, dal momento che sono io a gestirlo e farò di tutto per non cambiarlo mai! Quindi, supponiamo, io ho un url nel formato seguente:
http://mioserver/Dammi_Il_FeedRssTwitter?id=<TwitterUsername>
Ho rimosso il collegamento perchè comunque non sarebbe valido. Questo url è un entry-point per tutte le mie app che devono mostrare un feed Twitter. Quindi, l’app “I Love Milan” per Windows Phone chiamerà l’url usando “acmilan”, l’app “Nove Colli” chiamerà l’url con “novecolli”, mentre l’app “Piloti Virtuali Italiani” (per Windows Phone e Windows 8) chiamerà l’url usando “pvi_org”. Questo è un mio servizio, che fa da adapter tra ciò che Twitter espone con le sue API e ciò che le mie app si aspettano. Quindi: con le API 1.0 invocavo Twitter, ottenevo un RSS e lo passavo così com’era alle mie app. Cone le API 1.1 invoco Twitter con l’autenticazione, ottengo un JSON, costruisco da zero un feed RSS in formato XML e lo restituisco a chi di dovere.
Passo 1 – Come ottenere il JSON
Il primo passo è utilizzare questa classe JsonDownloader che ho scritto questa mattina (e basandomi su questo articolo su CodeProject), molto semplice, che fa la chiamata verso Twitter, in modo autenticato, che vi restituisce una string, che in pratica è il JSON da parserizzare.
Lo zip contiene un file JsonDownloader.cs, da includere nel verso progetto e modificare come necessario. L’unica accortezza – cosa che prima non era necessaria – è che dovete necessariamente registrare la vostra app su https://dev.twitter.com/, per ottenere quattro key da inserire nel codice per l’autenticazione OAuth, ovvero:
- Consumer Key
- Consumer Secret
- Access Token
- Access Token Secret
Nel .cs che scaricate gli oggetti string ci sono già, pronti all’uso, vanno solo valorizzati correttamente.
Passo 2 – Parserizzare lo JSON ed ottenere l’XML del feed RSS
Usando la libreria Json.NET, reperibile su Twitter, le cose sono semplici, ma mica tanto. Innanzitutto, immaginiamo una chiamata del genere:
string rss = RssParser.GetRss(json, id);
Da questo si evince che abbiamo una classe statica RssParser, che espone un metodo pubblicato GetRss. Il cui codice è il seguente:
public static string GetRss(string json, string screenName) { try { if (!string.IsNullOrEmpty(json)) { json = "{"root": " + json + "}"; var result = JObject.Parse(json); var elements = (JArray)result.Children().Children().FirstOrDefault(); var first = ((JObject)elements.FirstOrDefault()).Property("created_at").Value.ToString(); var firstDate = RssItem.ParseDateTime(first); StringBuilder bld = new StringBuilder(); string feed = GetRssHeader(); string itemBlock = GetRssItemBlock(); bld.AppendFormat(feed, screenName, firstDate, screenName); foreach (JObject elem in elements) { RssItem item = RssItem.Get(elem); if (item != null) { bld.AppendFormat(itemBlock, item.Title, screenName, item.Id, item.FormattedDate, item.FormattedDate, screenName, item.Id); } } bld.Append("</feed>"); return bld.ToString(); } else { return string.Empty; } } catch (Exception exc) { return exc.ToString(); } }
Questo metodo esplora lo JSON e restituisce la string del feed RSS. Navigando sugli oggetti JObject e JArray, e nelle relative proprietà, costruisco di fatto. Ci sono due metodi GetRssHeader() e GetRssItemBlock() che non fanno altro che recuperare delle embedded resource del progetto: di fatto contegono rispettivamente lo XML di intestazione del feed XML:
<?xml version="1.0" encoding="utf-8" ?> <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> <title>Twitter / {0}</title> <id>tag:twitter.com,2007:Status</id> <link type="text/html" rel="alternate" href="http://twitter.com"/> <updated>{1}</updated> <subtitle>Twitter updates from {2}.</subtitle>
e del blocco da ripetere per ciascun tweet:
<item> <title>{0}</title> <content type="html">content</content> <id>tag:twitter.com,2013:http://twitter.com/{1}/status/{2}</id> <pubDate>{3}</pubDate> <updated>{4}</updated> <link type="text/html" rel="alternate" href="http://twitter.com/{5}/status/{6}" /> </item>
All’interno del ciclo foreach viene costruito un oggetto RssItem:
public class RssItem { static CultureInfo culture = new CultureInfo("en-US"); public string Id { get; set; } public string Title { get; set; } public DateTime Date { get; set; } public string FormattedDate { get; set; } public static RssItem Get(JObject obj) { RssItem result = new RssItem(); result.Id = obj.Property("id").Value.ToString(); result.Title = obj.Property("text").Value.ToString(); result.Date = ParseDateTime(obj.Property("created_at").Value.ToString()); result.FormattedDate = result.Date.ToString("dd/MM/yyyy HH.mm.ss"); return result; } public static DateTime ParseDateTime(string value) { // Fri Jun 21 10:22:17 +0000 2013 try { int index = value.IndexOf('+'); if (index != -1) { // Devo rimuovere il "+0000 " string partToRemove = value.Substring(index, 6); value = value.Replace(partToRemove, string.Empty); } return DateTime.ParseExact(value, "ddd MMM dd H:mm:ss yyyy", culture, System.Globalization.DateTimeStyles.None); } catch (Exception) { return DateTime.Now; } } }
Il metodo pubblico Get restituisce un’istanza di RssItem, valorizzata in base al JObject corrente. All’interno ci sono un po’ di operazioni di parsing, soprattutto sulle date, che andrebbero magari riviste per essere ottimizzate, ma per adesso funzionano!
Passo 3 – Fine!
E fine. A questo punto abbiamo una string che in pratica è il feed RSS da dare in pasto alle nostre applicazioni. Se avete qualche decina di app che improvvisamente avevano smesso di funzionare a causa di questa breaking change di Twitter, le vedrete riprendere vita di colpo! La cosa molto interessante che il tutto è gestito in modo centralizzato, e non sarete obbligati a rilasciare nuove versioni delle vostre app, inseguendo i cambiamenti di Twitter. Qualsiasi modifica che si renderà necessaria è centralizzata con questo codice, e per le app è del tutto trasparente.
Guida carina, ma temo che sia solo per veri smanettoni… 🙁
Io dopo qualche passaggio mi sono drammaticamente perso.
Possibile che non esista qualcosa di più… automatico?
Pingback: VS2012, Web Api, Twitter e Feed RSS | Technology Experience (Reborn 4)