Fuori programma
21-02-2010 14.08.07 GMT+02La battaglia di aNobii (parte 1)aNobii è un bellissimo servizio, sotto tutti i punti di vista, ma ha delle potenzialità non ancora completamente sfruttate; forse è proprio questo il bello dell'web...

Vorrei farmi perdonare la lunga assenza dal blog con qualcosa che spero almeno per qualcuno possa risultare utile.

La storia comincia con il fatto che ho una libreria su aNobii che è composta ancora da pochi libri di informatica, rispetto al resto (sempre pochi comunque, perchè non ho tempo, non tanto per leggerli, quanto per caricarli)...volevo filtrare questi libri in base all'etichetta di classificazione (tag) allo scopo di presentarli in pagine dal tema specifico (ad es. la mia pagina personale che tratta appunto della mia professione). Insomma, pensavo fosse carino che insieme al mio CV si potessero trovare anche le mie letture informatiche (se non avete pazienza di aspettare, praticamente volevo che il risultato fosse questo).

Ho cominciato a guardarmi intorno se ci fosse in giro qualcosa adatto allo scopo e non mi è parso...da parte di aNobii da qualche settimana era uscita una weblet che consente di visualizzare i libri dello scaffale personale su una propria pagina...peccato che non ci sia alcuna possibilità di filtrare per tag.

Sono passato allora alle aNobii API, sembravano promettenti, ho anche realizzato un piccolo webservice nella speranza di raggiungere il mio scopo chiamando prima anobii.shelf.getSimpleShelf per avere la lista dei miei libri, purtroppo non filtrabile.
Non mi sono perso d'animo, ho pensato che l'avrei scansita ed avrei selezionato gli item con il tag richiesto...solo che il metodo anobii.item.getInfo chiamabile una volta in possesso dell'item_id restituisce solo informazioni generali sul libro, non quelle personali associate dall'utente, come il voto, lo stato (in lettura, letto, etc...), nè tantomeno i tag.
Faccio notare che la chiamata anobii.shelf.getSimpleShelf ha un bug antipatico: nonostante si possa specificare il numero di item da scaricare per ogni lista, ne vengono sempre restituiti 10.

Trovate in fondo al post il codice che avevo sviluppato in questo senso, che però non è risultato utile allo scopo.

Ero daccapo, ma non mi sono perso d'animo perchè ormai era divenuta una questione di principio. La funzionalità doveva esistere nel motore di aNobii perchè come si vede nell'immagine, nella colonna di sinistra c'è il riquadro dei tag (etichette in italiano). Cliccando su una di queste, la pagina viene filtrata ed era da questo che avevo tratto ispirazione. L'ultima strada percorribile era perciò fare reverse engineering delle chiamate Ajax fatte dalla pagina...speriamo che quelli di aNobii non si arrabbino ;-)

Mi sono messo perciò "in ascolto" delle chiamate fatte in corrispondenza di questo tipo di richieste usando uno sniffer come Wireshark ed ho trovato quello che volevo...

...to be continued

Titoli di coda

Ecco il codice C# per un webservice (o altro se volete) che interroga le api di aNobii e restituisce i libri dell'utente formattati in una lista HTML.
Cominciamo con il definire delle chiavi nel web.config (o app.config), ricordatevi di richiedere ad aNobii la vostra chiave per l'uso delle api e copiarla in AnobiiSecretKey al posto delle "x"

  <appSettings>
    <add key ="AnobiiApiKey" value ="79cae1b3db33e6d56af29b719dd54180"/>  
    <add key ="AnobiiSecretKey" value ="xxxxxxxxx"/>
    <add key ="AnobiiAppName" value ="FilterLabel"/>
    <add key ="AnobiiGetSimpleShelf"
     value="http://api.anobii.com/shelf/getSimpleShelf"/>
    <add key ="AnobiiGetInfo" value="http://api.anobii.com/item/getInfo"/>
  </appSettings>

poi definiamo queste costanti che saranno utili per ripescare le chiavi all'interno della nostra classe

    private const string AnobiiApiKey_KName = "AnobiiApiKey";
    private const string AnobiiSecretKey_KName = "AnobiiSecretKey";
    private const string AnobiiAppName_KName = "AnobiiAppName";
    private const string AnobiiGetSimpleShelf_KName = "AnobiiGetSimpleShelf";
    private const string AnobiiGetInfo_KName = "AnobiiGetInfo";

Il metodo che segue prepara e codifica la chiave secondo le specifiche in modo da doverla solo copiare nella chiamata

    /// <summary>
    /// Prepares the hashed signature for the aNobii API composing
    /// the apiKey and the secret key.
    /// </summary>
    /// <returns>Hashed signature.</returns>
    private string _getSignature()
    {
        string toBeEncoded = ConfigurationManager.AppSettings[AnobiiApiKey_KName] + 
            ConfigurationManager.AppSettings[AnobiiSecretKey_KName];

        System.Security.Cryptography.MD5 md5Hasher = 
            System.Security.Cryptography.MD5.Create();

        // Converts the input string to a byte array and compute the hash.
        byte[] data = md5Hasher.ComputeHash(
            System.Text.Encoding.Default.GetBytes(toBeEncoded));

        // Creates a new Stringbuilder to collect the bytes and creates a string.
        System.Text.StringBuilder sBuilder = new System.Text.StringBuilder ();

        // Loops through each byte of the hashed data and formats 
        // each one as a hexadecimal string.
        for (int i = 0; i < data.Length; i++)
        {
            sBuilder.Append(data[i].ToString("x2"));
        }

        // Return the hashed string.
        return sBuilder.ToString();
    }

Questo metodo effettua la chiamata HttpRequest e ne restituisce il risultato

    /// <summary>
    /// Executes an HTTP request to a specified URL using GET method.</summary>
    /// <param name="postData">Data to post.</param>
    /// <param name="url">Url to post.</param>
    /// <returns>Returns the result of the request.
    /// If an error occours, an empty string is returned.</returns>
    /// <remarks>It's assumed that you have already URL encoded data.</remarks>
    private string _httpData(string url, string postData)
    {
        Uri uri = new Uri(url + "?" + postData);
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
        request.Method = "GET";

        // executes the call
        HttpWebResponse response = null;
        try
        {
            response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode.Equals(HttpStatusCode.OK))
            {
                try
                {
                    Stream responseStream = response.GetResponseStream();
                    StreamReader readStream = new StreamReader(
                        responseStream, System.Text.Encoding.UTF8);
                    return readStream.ReadToEnd();
                }
                catch 
                {
                    // NOP or put here some error handling code.
                }

            }
        }
        catch 
        {
            // NOP or put here some error handling code.
        }

        return string.Empty;
    }

Ecco il corpo del metodo del webservice

    /// <summary>
    /// Cycles on all items of the shelf specified and returns their titles.
    /// </summary>
    /// <param name="userName">The aNobii user name to query for.</param>
    /// <returns>Title list formatted as HTML &lt;ul&gt; block.</returns>
    [WebMethod]
    public string AnobiiTestCall(string userName) 
    {
        int itemsPerPage = 10;

        string baseQuery = "api_key=" + 
            ConfigurationManager.AppSettings[AnobiiApiKey_KName] + 
            "&api_sig=" + _getSignature();

        // cycles paged requests until all items are parsed
        int start = 0;
        string res;
        System.Xml.XmlDocument docList;
        System.Xml.XmlDocument docItem;
        XmlNodeList objListNodes;
        XmlNodeList objItemNodes;
        string parameters;

        string outHTML = "<ul>"; // final result
        while (true)
        {
            // query for item list
            parameters = baseQuery + "&user_id=" + userName + "&start=" + 
                start.ToString() + "&limit=" + itemsPerPage.ToString();

            res = _httpData(
                ConfigurationManager.AppSettings[AnobiiGetSimpleShelf_KName], 
                parameters);

            // parses the result
            docList = new System.Xml.XmlDocument();
            docList.LoadXml(res);
            objListNodes = docList.SelectNodes("/shelf/items/item");
            
            foreach (XmlNode node in objListNodes)
            {   
                // composes item query and perform the request
                parameters = baseQuery + "&item_id=" + 
                    node.Attributes["id"].Value;

                res = _httpData(
                    ConfigurationManager.AppSettings[AnobiiGetInfo_KName], 
                    parameters);

                // parses the result
                docItem = new System.Xml.XmlDocument();
                docItem.LoadXml(res);
                objItemNodes = docItem.SelectNodes("/items/item");
                
                outHTML += "<li>" + 
                    objItemNodes.Item(0).Attributes["title"].Value + "</li>";
            }

            if (objListNodes.Count == 0)
                break;

            start += itemsPerPage;
        }

        outHTML += "</ul>";

        return outHTML; 
    }

Infine consentitemi di ringraziare manoli.net di cui ho utilizzato l'ottimo formattatore di codice C# che ha prodotto il risultato visto sopra.

data modifica 25-02-2010 15.11.42 GMT+02

#

CommentiFeed RSS 2.0 di : Fuori programma, commenti a "La battaglia di aNobii (parte 1)"
1) Francesco Celiento (www.francescoceliento.com)23-03-2012 10.43.53Sei molto bravo e hai scritto un bell'articolo, ma quel "dell'web..." mi ha sconvolto :P Ciao :)
2) Cosimo Carbonelli23-03-2012 12.28.07Ahahaha, si è vero ;-) mi sono basato sull'assonanza e non sulla grammatica, devo dire che l'ho fatto quasi apposta, nel senso che mi sono chiesto a suo tempo se sarebbe stato il caso e poi, mi sono detto sì, facciamolo và...in effetti fa un certo effetto anche a me rileggendolo...
Aggiungi un commento
Mittente : *
Email : 
Web : 
Testo del messaggio : * 
Codice di validazione : * 

otherbit, by Cosimo Carbonelli m. info@otherbit.com p.i. 11743080159
made with OtherWeb