Mettre à jour la médiathèque de Kodi

Kodi est un lecteur multimédia libre. Je l’utilise à partir de LibreELEC sur un Raspberry Pi. Au moment d’écrire ces lignes, la version 9 (Leia) est la plus récente. J’ai configuré Kodi pour aller lire des fichiers vidéos sur un ordinateur sur mon réseau par SMB. Donc, il arrive que j’ajoute des fichiers sur ce disque et que je doive mettre à jour manuellement Kodi pour afficher ces nouveaux fichiers. Dans cet article nous allons voir comment provoquer la mise à jour automatiquement à partir d’une application codée avec C++Builder.

Un collègue au travail m’a parlé que Kodi possède un API qui permet d’effectuer plusieurs tâches. Il l’utilise lui-même dans un logiciel de sa conception. Il m’a dit que Kodi Leia avait brisé une des fonctionnalités qu’il utilise. L’API HTTP ne fonctionnait plus pour mettre à jour la médiathèque. Effectivement, sur le Wiki de Kodi on peux y lire le texte suivant.

HTTP (does not work in v18 Leia)

Pour remédier à cette situation il a décidé d’invoquer l’application curl. En général je n’aime pas que mes applications aient des dépendances externes. J’ai donc creusé un peu pour comprendre comment mieux faire cette action.

Tout d’abord avant de débuter il faut activer l’option Autoriser le contrôle à distance via HTTP.

Kodi: Autoriser le contrôle à distance via HTTP

Dans l’image j’ai encerclé où se trouve le port que vous allez devoir utiliser.

Avant d’utiliser mon propre code, je vais tester la fonctionnalité. Sur la page HOW-TO:Remotely update library on y trouve une commande curl que je vais utiliser dans une fenêtre de terminal.

curl --data-binary '{"jsonrpc": "2.0", "method": "VideoLibrary.Scan", "id": "mybash"}' -H 'content-type: application/json;' http://libreelec:8080/jsonrpc

Voici la réponse que j’obtiens.

{"id":"mybash","jsonrpc":"2.0","result":"OK"}

Cette commande fonctionne bien, donc on ne devrait pas avoir de problème à communiquer avec Kodi. Une chose intéressante que l’on peut voir est qu’elle utilise l’étiquette jsonrpc. En cherchant un peu on comprend que Kodi à commencer à migrer de l’API HTTP vers un API JSON-RPC. Voici ce que la documentation dit à ce sujet.

JSON-RPC is a HTTP- and/or raw TCP socket-based interface for communicating with Kodi. It replaces the deprecated HTTP API, and offers a more secure and robust mechanism in the same format.

Voilà maintenant ce qui explique que l’API HTTP ne fonctionne pas complètement sur Leia.

Maintenant que l’on sait quoi faire, débutons l’application. La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TIdHTTP et un contrôle TButton.

Étant donnée que JSON sera utilisé, il faut ajouter cette ligne à votre fichier d’en-tête.

#include <System.JSON.hpp>

J’ai décidé de faire une petite classe minimaliste pour supporter JSON-RPC. Voici un autre bout de code à ajouter dans le fichier d’en-tête.

class TJsonRpc
{
public:
    __fastcall TJsonRpc() : Version("2.0") {}
    inline virtual __fastcall ~TJsonRpc() {}

    String Version;
    String Method;
    String Id;

    String __fastcall ToString()
    {
        TJSONObject* Obj = new TJSONObject();
        Obj->AddPair("jsonrpc", Version);
        Obj->AddPair("method", Method);
        Obj->AddPair("id", Id);
        TJSONObject* Params = new TJSONObject();
        Obj->AddPair("params", Params);
        const String Result = Obj->ToString();
        delete Obj;
        return Result;
    }
};

Finalement, il faut mettre ce code dans l’évènement OnClick du bouton.

    IdHTTP1->Request->ContentType = "application/json";

    TJsonRpc LJsonRpc;
    LJsonRpc.Method = "VideoLibrary.Scan";
    LJsonRpc.Id = "mybash";

    const String LUrl =  "http://libreelec:8080/jsonrpc";

    TStringStream* LData = NULL;
    try
    {
        LData = new TStringStream(LJsonRpc.ToString());

        const String LAnswer = IdHTTP1->Post(LUrl, LData);

        TJSONObject* Obj = static_cast<TJSONObject*>(TJSONObject::ParseJSONValue(LAnswer));
        try
        {
            TJSONPair* Pair;
            if((Pair = Obj->Get("error")) != NULL)
            {
                String LMessage;
                int LCode;
                TJSONObject* ErrorObj = static_cast<TJSONObject*>(Pair->JsonValue);
                if((Pair = ErrorObj->Get("message")) != NULL)
                {
                    TJSONString* Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    LMessage = AnsiDequotedStr(Answer->ToString(), '\"');
                }
                if((Pair = ErrorObj->Get("code")) != NULL)
                {
                    TJSONNumber* Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
                    LCode = Answer->AsInt;
                }
                throw Sysutils::Exception("Erreur " + String(LCode) + ": " + LMessage);
            }
        }
        __finally
        {
            delete Obj;
        }
    }
    __finally
    {
        delete LData;
    }

Même si je gère les erreurs d’API, il ne s’agit pas de code de production. Je ne vérifie pas que le ID de retour correspond à celui envoyé. Au lieu de tout mettre dans un évènement de bouton on aurait mieux fait de mettre dans une méthode séparée. On aurait pu aussi gérer la communication dans un thread séparé pour ne pas bloquer l’interface. Mais bon… l’idée c’était de montrer que la meilleure manière de communiquer avec Kodi est l’utilisation de JSON-RPC par HTTP.

J’espère que cet article vous sera utile.

Traduction avec l’API MyMemory et CCAN/JSON

Dans cet article nous allons voir comment traduire du texte avec l’API web MyMemory. Je voulais utiliser celui de Google, mais il est payant. MyMemory n’a pas la même qualité de traduction que Google, mais le but est de faire une démonstration et non un produit commercial.

Pour analyser la réponse en JSON, au lieu d’utiliser la classe TJSONObject de la RTL, c’est la bibliothèque CCAN/JSON qui sera utilisée. Cette bibliothèque à code source ouvert est codée en C. Je l’ai choisie car dans une étude comparative sur 41 bibliothèques JSON elle semblait bien classée.

JSON Parsing Time
Temps d’analyse des bibliothèques JSON

J’ai dû exclure toutes celles en C++11, car j’utilise encore principalement le compilateur bcc32. Le facteur principal était simplement la facilité d’intégration. Si ça ne compilait pas dans les minutes suivant le téléchargement, je passais à la prochaine dans la liste.

La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TRESTClient, un TRESTRequest, un TRESTResponse, trois contrôles TEdit, un TComboBox et un TButton.Vous pouvez donner comme texte à votre bouton le mot « Traduire ».

Voici ce à quoi l’interface devrait ressembler:

Il faut ajouter le fichier json.c qui se trouve trouve dans le dossier \ccan\json au projet pour qu’il soit compilé. Je n’ai pas eu de problème pour l’utiliser avec les plateformes Win32, Win64 et OS X.

Dans votre fichier cpp voici l’en-tête à utiliser.

extern "C"
{
    #include "json.h"
}

Il ne faut pas oublier qu’il s’agit de code en C et non C++, c’est pourquoi il faut ajouter un peu plus de code.

Voici le code à ajouter dans votre constructeur pour initialiser les contrôles:

    TListBoxItem* ListItems;

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Anglais";
    ListItems->TagString = L"en";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Chinois (simplifié)";
    ListItems->TagString = L"zh-CN";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Russe";
    ListItems->TagString = L"ru";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Latin";
    ListItems->TagString = L"la";

    // On sélectionne le premier item par défaut
    ComboBox1->ItemIndex = 0;

    // Message à afficher quand la propriété Text est vide
    Edit1->TextPrompt = L"Texte à traduire";

    // Le nombre de match par défaut est mis à zéro
    Edit3->Text = L"0";

    // On n'écrit pas dans les champs de résultat
    Edit2->ReadOnly = true;
    Edit3->ReadOnly = true;

Voici le code à ajouter dans l’évènement OnClick du bouton:

    if(Edit1->Text.IsEmpty() == true || ComboBox1->ItemIndex < 0)
    {
        Edit2->Text = "";
        Edit3->Text = "0";
        return;
    }

    String LQuery = Edit1->Text;
    String LTarget = ComboBox1->ListItems[ComboBox1->ItemIndex]->TagString;

    RESTClient1->BaseURL = "http://api.mymemory.translated.net/get" \
        "?q=" + Edit1->Text +
        "&langpair=fr|" + LTarget;

    RESTRequest1->Execute();

    // Conversion en UTF-8
    TBytes LUTF8Array = TEncoding::UTF8->GetBytes(RESTResponse1->JSONText + "0");
    LUTF8Array[LUTF8Array.High] = '\0'; // Le caractère nul de terminaison est requis

    JsonNode* LRootNode = json_decode((char *)&LUTF8Array[0]);
    if(LRootNode != NULL)
    {
        JsonNode* LResponseNode = json_find_member(LRootNode, "responseData");
        if(LResponseNode != NULL && LResponseNode->tag == JSON_OBJECT)
        {
            JsonNode* LTextNode = json_find_member(LResponseNode, "translatedText");
            if(LTextNode != NULL && LTextNode->tag == JSON_STRING)
            {
                TBytes LUTFAnswer;
                LUTFAnswer.Length = strlen(LTextNode->string_);
                memcpy(&LUTFAnswer[0], LTextNode->string_, LUTFAnswer.Length);
                Edit2->Text = TEncoding::UTF8->GetString(LUTFAnswer);
            }
            JsonNode* LMatchNode = json_find_member(LResponseNode, "match");
            if(LMatchNode != NULL && LMatchNode->tag == JSON_NUMBER)
            {
                Edit3->Text = LMatchNode->number_;
            }
        }
        json_delete(LRootNode);
    }

Le texte utilisé par CCAN/JSON utilise l’encodage UTF8, c’est pourquoi il y a des conversions. Sinon le code ressemble étrangement à ce que RAD Studio nous a habitués avec sa classe JSON.

Maintenant il ne reste qu’à exécuter l’application pour la tester.
Traduction macOS

L’API PageSpeed v2

Dans cet article je vais revisiter l’article que j’avais écrit en mai 2012 sur l’utilisation de l’API PageSpeed de Google. Cette fois-ci c’est C++Builder XE7 qui sera utilisé avec la version 2 de l’API. Tout comme la première fois, les résultats seront présentés dans un diagramme circulaire à l’aide du contrôle TChart. En plus du code qui sera légèrement différent, un composant TImage sera utilisé pour y afficher une capture d’écran du site web.

La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TRESTClient, un TRESTRequest, un TRESTResponse, un TChart, un TImage et un TLayout dans lequel il y aura un TEdit un TButton. On aligne le contrôle TLayout à Bottom et vous pouvez donner comme texte à votre bouton le mot « Analyser ».

Voici ce à quoi l’interface devrait ressembler:
Analyseur de site web

Dans votre fichier cpp voici la liste de fichier d’en-tête à utiliser ainsi que la macro qui va contenir votre clef d’API. N’essayez pas d’utiliser celle-ci, il s’agit de caractères écrits de façon aléatoire.

#include <System.JSON.hpp>
#include <FMXTee.Series.hpp>
#include <System.NetEncoding.hpp>
#define GOOGLEAPIKEY "dskk1j3sW4WBYdkjds8sSDSD" // Clef d'API

Voici le code à ajouter dans votre constructeur:

    Chart1->Title->Text->Text = "Statistique de la page";
    Chart1->Legend->Title->Text->Text = "Ressource en octets";
    Chart1->Legend->Alignment = TLegendAlignment::laBottom;
    Chart1->BevelOuter = TPanelBevel::bvNone;
    Series::TPieSeries *Series = new Series::TPieSeries(this);
    Series->Marks->Visible = false;
    Chart1->AddSeries(Series);

Étant donné que nous accéderons à un site web qui utilise SSL (https), les fichiers ssleay32.dll et libeay32.dll devront être distribués avec votre application Windows. Une version Win32 et Win64 des fichiers est disponible sur le site web suivant: http://indy.fulgan.com/SSL.

La prochaine étape est d’ajouter le code dans l’événement OnClick du bouton.

    Chart1->Series[0]->Clear(); // Efface le contenu du diagramme

    RESTClient1->BaseURL =
        "https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url=" +
        Edit1->Text + "&key=" GOOGLEAPIKEY + "&screenshot=true";
    RESTRequest1->Execute();

    TJSONObject* Obj = static_cast<TJSONObject*>(RESTResponse1->JSONValue);
    TJSONPair* Pair = Obj->Get("pageStats");
    if(Pair)
    {
        TJSONNumber* Answer;
        TJSONObject* PageStats = static_cast<TJSONObject*>(Pair->JsonValue);
        if((Pair = PageStats->Get("htmlResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
            Chart1->Series[0]->Add(Answer->AsInt, "HTML", TAlphaColor(claGreen));
        }
        if((Pair = PageStats->Get("cssResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
            Chart1->Series[0]->Add(Answer->AsInt, "CSS", TAlphaColor(claOrange));
        }
        if((Pair = PageStats->Get("imageResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
            Chart1->Series[0]->Add(Answer->AsInt, "Image", TAlphaColor(claYellow));
        }
        if((Pair = PageStats->Get("javascriptResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
            Chart1->Series[0]->Add(Answer->AsInt, "JavaScript", TAlphaColor(claRed));
        }
        if((Pair = PageStats->Get("otherResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
            Chart1->Series[0]->Add(Answer->AsInt, "Autre", TAlphaColor(claBlue));
        }
    }
    Pair = Obj->Get("screenshot");
    if(Pair)
    {
        TJSONObject* Screenshot = static_cast<TJSONObject*>(Pair->JsonValue);
        if((Pair = Screenshot->Get("data")) != NULL)
        {
            TJSONString* Answer = static_cast<TJSONString*>(Pair->JsonValue);
            String Value = Answer->Value();
            // On change de base64url à base64
            Value = ReplaceStr(ReplaceStr(Value, "_", "/"), "-", "+");
            // On transforme de base64 vers stream
            TBytes LDAta = TNetEncoding::Base64->DecodeStringToBytes(Value);
            TBytesStream* LStream = new TBytesStream(LDAta);
            try
            {
                Image1->Bitmap->LoadFromStream(LStream);
            }
            __finally
            {
                delete LStream;
            }
        }
    }

Un petit mot sur la ligne 49. La documentation de l’API PageSpeed à propos des données de l’image dit:

Base64-encoded screenshot of the page that was analyzed.

Les données peuvent comporter des caractères moins (-) et souligné (_), donc la documentation est erronée si on se fie à RFC 4648 section 5:

This encoding may be referred to as « base64url ». This encoding should not be regarded as the same as the « base64 » encoding and should not be referred to as only « base64 ». Unless clarified otherwise, « base64 » refers to the base 64 in the previous section.

This encoding is technically identical to the previous one, except for the 62:nd and 63:rd alphabet character, as indicated in Table 2.

Google n’aurait donc pas dû dire qu’il s’agit de base64. Il aurait dû dire que c’est du base64url. C’est pour cette raison que je remplace les deux caractères.

Pourtant, dans d’autres API comme celui de Gmail, Google met la bonne information:

The entire email message in an RFC 2822 formatted and base64url encoded string.

En terminant, je vous rappelle qu’un nouvel outil dans RAD Studio XE7 vous permet de tester les API REST. Il se trouve Tools / Rest Debugger. Voici une capture d’écran du logiciel:
RESTDebugger

Utiliser l’API Graph de Facebook avec C++Builder

Facebook se passe sans doute de présentation, par contre son API est peut-être moins connu. Dans cet article nous irons chercher les informations publiques d’un utilisateur qui ne nécessitent aucune autorisation.

La première étape est de créer un nouveau projet FireMonkey HD. Dans la Form il faut insérer un TIdHTTP, un TIdSSLIOHandlerSocketOpenSSL, un TStringGrid, un TImage, un contrôle TEdit, un TLabel et finalement un TButton. Vous pouvez donner comme texte à votre bouton le mot « Rechercher » et pour le TLabel vous pouvez y inscrire « Nom d’utilisateur: ». Je vous propose de placer les composants dans la fenêtre de la manière suivante:
Pour ceux qui se le demande j’ai utilisé le style Air.Style. Ça change un peu des fenêtres Windows que l’on voit tout le temps.

Dans votre fichier cpp voici le fichier d’en-tête à ajouter:

#include <Data.DBXJSON.hpp>

Voici le code à ajouter dans votre constructeur:

    IdHTTP1->IOHandler = IdSSLIOHandlerSocketOpenSSL1;

    // Ceci est nécessaire pour les redirections
    IdHTTP1->HandleRedirects = true;

    // Propriété par défaut pour le contrôle grille
    StringGrid1->ShowSelectedCell = false;
    StringGrid1->ReadOnly = true;
    StringGrid1->RowCount = 0;

    // Ajout de la première colonne
    StringGrid1->AddObject(new TStringColumn(this));
    StringGrid1->Columns[0]->Header = L"Nom";
    StringGrid1->Columns[0]->Width = 150;

    // Ajout de la deuxième colonne
    StringGrid1->AddObject(new TStringColumn(this));
    StringGrid1->Columns[1]->Header = L"Valeur";
    StringGrid1->Columns[1]->Width = 150;

Étant donné que nous accéderons à un site web qui utilise SSL (https), la première ligne de code est critique. Sans elle, une exception dans la classe EIdIOHandlerPropInvalid produira le message « IOHandler value is not valid ». Parce que nous utilisons OpenSSL, les fichiers ssleay32.dll et libeay32.dll devront être distribués avec votre application.

La prochaine étape est d’ajouter le code dans l’événement OnClick du bouton.

    System::Classes::TMemoryStream* ResponseContent = new System::Classes::TMemoryStream;

    try
    {
        // On vide la liste avant d'ajouter les valeurs
        StringGrid1->RowCount = 0;
        Image1->Bitmap = NULL;

        String URL = "https://graph.facebook.com/" + Edit1->Text;

        String Response = IdHTTP1->Get(URL);

        TJSONObject* Obj = static_cast<TJSONObject*>(TJSONObject::ParseJSONValue(Response));
        TJSONPair* Pair;
        TJSONString* Answer;

        if((Pair = Obj->Get("id")) != NULL)
        {   // ID Facebook
            const int Pos = StringGrid1->RowCount;
            StringGrid1->RowCount++;
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            StringGrid1->Cells[0][Pos] = "ID";
            StringGrid1->Cells[1][Pos] = AnsiDequotedStr(Answer->ToString(), '\"');
        }
        if((Pair = Obj->Get("name")) != NULL)
        {   // Nom complet
            const int Pos = StringGrid1->RowCount;
            StringGrid1->RowCount++;
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            StringGrid1->Cells[0][Pos] = "Nom";
            StringGrid1->Cells[1][Pos] = AnsiDequotedStr(Answer->ToString(), '\"');
        }
        if((Pair = Obj->Get("first_name")) != NULL)
        {   // Prénom
            const int Pos = StringGrid1->RowCount;
            StringGrid1->RowCount++;
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            StringGrid1->Cells[0][Pos] = "Prénom";
            StringGrid1->Cells[1][Pos] = AnsiDequotedStr(Answer->ToString(), '\"');
        }
        if((Pair = Obj->Get("last_name")) != NULL)
        {   // Nom de famille
            const int Pos = StringGrid1->RowCount;
            StringGrid1->RowCount++;
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            StringGrid1->Cells[0][Pos] = "Nom de famille";
            StringGrid1->Cells[1][Pos] = AnsiDequotedStr(Answer->ToString(), '\"');
        }
        if((Pair = Obj->Get("gender")) != NULL)
        {   // Sexe (female ou male)
            const int Pos = StringGrid1->RowCount;
            StringGrid1->RowCount++;
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            StringGrid1->Cells[0][Pos] = "Sexe";
            StringGrid1->Cells[1][Pos] = AnsiDequotedStr(Answer->ToString(), '\"');
        }

        // Téléchargement de l'image
        IdHTTP1->Get(URL + "/picture", ResponseContent);

        Image1->Bitmap = new Fmx::Types::TBitmap(ResponseContent);
    }
    catch(...)
    {
    }

    delete ResponseContent;

Dans le code on insère dans la liste seulement quelques informations, mais il en existe plusieurs autres qui sont disponibles.

À présent, vous connaissez le minimum requis pour commencer à vous amuser avec cette interface API .

Utiliser l’API de VirusTotal avec C++Builder

VirusTotal est un service gratuit qui permet la détection de virus, vers, chevaux de Troie ou tout autre type de programme malveillant. En visitant le site web de cette application, il est possible de choisir un fichier sur votre disque dur et de le soumettre à une analyse. Par la suite, plusieurs antivirus vérifieront si les données comportent des partie suspectes. Le résultat sera ensuite disponible en ligne.

Cet outil web est vraiment génial, mais ce qui l’est encore plus c’est qu’il existe une interface API publique que l’on peut utiliser avec des requêtes POST par HTTP. Les réponses seront en format JSON (JavaScript Object Notation).

Avant de commencer, il faudra vous inscrire pour obtenir une clef d’API. Elle sera affichée dans la section API de votre profil. Il est à noter qu’avec cette clef vous ne pourrez effectuer que quatre requêtes par minute.

Maintenant, on peut commencer à coder. La première étape est de créer un nouveau projet FireMonkey HD. Dans la Form, il faut insérer un TIdHTTP, un TIdSSLIOHandlerSocketOpenSSL, un TOpenDialog, un contrôle TEdit deux TButton et un TMemo.

Je vous propose de placer les composants dans la fenêtre de la manière suivante:
VirusTotal API

Vous pourrez aussi utiliser des textes similaires à ceux de l’image pour les différents contrôles.

Dans votre fichier cpp voici la liste de fichiers d’en-tête à utiliser ainsi que la macro qui va contenir votre clef d’API. N’essayez pas d’utiliser celle-ci, il s’agit de caractères écrits de façon aléatoire.

#define APIKEY "g9898984d0909534e1230958567f098090928712a8034cb0809786201854f678"
#include <Data.DBXJSON.hpp>
#include <Idmultipartformdata.hpp>

Voici le code à ajouter dans votre constructeur:

    IdHTTP1->IOHandler = IdSSLIOHandlerSocketOpenSSL1;

    OpenDialog1->Filter = "Application (*.exe)|*.EXE|Tous les fichiers|*.*";
    OpenDialog1->Options = System::Uitypes::TOpenOptions()
        << TOpenOption::ofHideReadOnly << TOpenOption::ofEnableSizing <<
        TOpenOption::ofFileMustExist;

Étant donné que nous accéderons à un site web qui utilise SSL (https), la première ligne de code est critique. Parce que nous utilisons OpenSSL, les fichiers ssleay32.dll et libeay32.dll devront être distribués avec votre application.

La prochaine étape est d’ajouter le code dans l’événement Button1Click.

    if(OpenDialog1->Execute() == false)
    {
        return;
    }

    Idmultipartformdata::TIdMultiPartFormDataStream* Params =
        new Idmultipartformdata::TIdMultiPartFormDataStream();
    Params->AddFormField("apikey", APIKEY);
    Params->AddFile("file", OpenDialog1->FileName, "application/octet-stream");

    System::UnicodeString Response = IdHTTP1->Post(
        "https://www.virustotal.com/vtapi/v2/file/scan", Params);

    TJSONObject* Obj = static_cast<TJSONObject*>
        (TJSONObject::ParseJSONValue(Response));
    if(Obj)
    {
        TJSONPair* Pair;
        TJSONString* Answer;
        if((Pair = Obj->Get("response_code")) != NULL)
        {
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            if(Answer->ToString() == "1")
            {
                if((Pair = Obj->Get("resource")) != NULL)
                {
                    Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    Edit1->Text = AnsiDequotedStr(Answer->ToString(), '\"');
                }
            }
            else
            {
                Edit1->Text = "Erreur";
            }
        }
    }

    delete Params;

Ce code est utilisé pour la soumission du fichier choisi avec le TOpenDialog vers le serveur de VirusTotal. Si le code de la réponse est 1, ce qui signifie donc que la requête à réussi, la ressource sera affichée dans le TEdit.

Finalement, il faut ajouter ce code dans l’événement Button2Click.

    Idmultipartformdata::TIdMultiPartFormDataStream* Params =
        new Idmultipartformdata::TIdMultiPartFormDataStream();
    Params->AddFormField("apikey", APIKEY);
    Params->AddFormField("resource", Edit1->Text);

    System::UnicodeString Response = IdHTTP1->Post(
        "https://www.virustotal.com/vtapi/v2/file/report", Params);

    TJSONObject* Obj = static_cast<TJSONObject*>
        (TJSONObject::ParseJSONValue(Response));
    if(Obj)
    {
        TJSONPair* Pair = Obj->Get("scans");;
        if(Pair)
        {
            TJSONObject* Scans = static_cast<TJSONObject*>(Pair->JsonValue);
            TJSONPairEnumerator* Enumerator = Scans->GetEnumerator();
            while(Enumerator->MoveNext())
            {
                String AntivirusName =
                    AnsiDequotedStr(Enumerator->Current->JsonString->ToString(), '\"');
                Memo1->Lines->Add(AntivirusName);

                TJSONObject* AntivirusData =
                    static_cast<TJSONObject*>(Enumerator->Current->JsonValue);

                String Value;
                TJSONString* Answer;
                if((Pair = AntivirusData->Get("detected")) != NULL)
                {
                    Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    Value = AnsiDequotedStr(Answer->ToString(), '\"');
                    Memo1->Lines->Add("   Detected: " + Value);
                }
                if((Pair = AntivirusData->Get("version")) != NULL)
                {
                    Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    Value = AnsiDequotedStr(Answer->ToString(), '\"');
                    Memo1->Lines->Add("   Version: " + Value);
                }
                if((Pair = AntivirusData->Get("result")) != NULL)
                {
                    Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    Value = AnsiDequotedStr(Answer->ToString(), '\"');
                    Memo1->Lines->Add("   Result: " + Value);
                }
                if((Pair = AntivirusData->Get("update")) != NULL)
                {
                    Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    Value = AnsiDequotedStr(Answer->ToString(), '\"');
                    Memo1->Lines->Add("   Update: " + Value);
                }
            }
        }
    }
    delete Params;

Cette méthode servira à aller chercher le résultat d’une analyse pour la ressource entrée dans le TEdit. Le tout sera affiché dans le contrôle TMemo.

Il est important de savoir que les fichiers soumis par l’API ont la priorité d’analyse la plus basse. Dépendant de la charge de travail de VirusTotal, il pourrait être possible d’attendre plusieurs heures avant que le fichier soit analysé. Il est donc important d’appeler ce code à intervalle régulier pour s’assurer que tous les résultats sont disponibles.

La réponse de la requête POST ressemble à ceci:

{
   "scans":{
      "AVG":{
         "detected":false,
         "version":"10.0.0.1190",
         "result":null,
         "update":"20120826"
      },
      "Panda":{
         "detected":false,
         "version":"10.0.3.5",
         "result":null,
         "update":"20120826"
      }
   },
   "scan_id":"51a16f4a4cd8089b559a9fe694c6ac67eb0d4ad05ef564b797e5e8caf3c18813-1346036118",
   "sha1":"189f6287cd36c2f49bc828a556a70d960b7547b3",
   "resource":"51a16f4a4cd8089b559a9fe694c6ac67eb0d4ad05ef564b797e5e8caf3c18813",
   "response_code":1,
   "scan_date":"2012-08-27 02:55:18",
   "permalink":"https://www.virustotal.com/file/51a16f4a4cd8089b559a9fe694c6ac67eb0d4ad05ef564b797e5e8caf3c18813/analysis/1346036118/",
   "verbose_msg":"Scan finished, scan information embedded in this object",
   "total":2,
   "positives":0,
   "sha256":"51a16f4a4cd8089b559a9fe694c6ac67eb0d4ad05ef564b797e5e8caf3c18813",
   "md5":"463b0fe60365c7cc84325646aabeb907"
}

Pour plus de détail sur l’API vous pouvez consulter la page web suivante: https://www.virustotal.com/documentation/public-api

Utiliser l’API URL Shortener de Google avec C++Builder

Dans mon dernier article j’avais expliqué l’utilisation l’API PageSpeed de Google. Pour continuer dans cette direction, j’aborderai dans celui-ci l’API URL Shortener.

L’utilisation de cet API comporte beaucoup de similitude avec le précédent. Encore une fois l’information sera prise sur une page web utilisant SSL et la réponse sera dans le format JSON (JavaScript Object Notation). Par contre, au lieu d’utiliser la méthode GET, ce sera la méthode POST qui sera utilisée par le protocole HTTP.

La première étape est de créer un nouveau projet FireMonkey HD. Dans la Form il faut insérer un TIdHTTP, un TIdSSLIOHandlerSocketOpenSSL, deux contrôles TEdit et un TButton. Vous pouvez donner comme texte à votre bouton le mot « Réduire ». Je vous propose de placer les composants dans la fenêtre de la manière suivante:
Réducteur d’URLLa capture d’écran a été faite sous MAC OS X. C’est effectivement l’un des avantages de FireMonkey, l’application pourra fonctionner avec un système d’exploitation Windows ou MAC OS.

Dans votre fichier cpp voici le fichier d’en-tête à ajouter:

#include <Data.DBXJSON.hpp>

Voici le code à ajouter dans votre constructeur:

    IdHTTP1->IOHandler = IdSSLIOHandlerSocketOpenSSL1;

    Edit2->ReadOnly = true; // L'URL réduit ne peut pas être changé

La prochaine étape est d’ajouter le code dans l’événement OnClick du bouton.

    Edit2->Text = ""; // On efface le texte
    Application->ProcessMessages();

    System::Classes::TStringStream* SourceFile =
        new System::Classes::TStringStream("{\"longUrl\": \"" + Edit1->Text + "\"}");

    IdHTTP1->Request->ContentType = "application/json";
    System::UnicodeString Response = IdHTTP1->Post(
        "https://www.googleapis.com/urlshortener/v1/url", SourceFile);

    TJSONObject* Obj = static_cast<TJSONObject*>(TJSONObject::ParseJSONValue(Response));
    if(Obj)
    {
        TJSONPair* Pair = Obj->Get("id");
        if(Pair)
        {
            TJSONString* Answer = static_cast<TJSONString*>(Pair->JsonValue);
            Edit2->Text = AnsiDequotedStr(Answer->ToString(), '\"');
        }
    }

    delete SourceFile;

La première ligne de code efface le texte dans le contrôle de saisie monoligne. Par contre cette action ne sera pas exécutée sur le champ parce que le reste du code est bloquant car une requête HTTP au serveur doit s’effectuer. Cette requête pourrait prendre quelques secondes donc à la deuxième ligne on laisse du temps pour exécuter l’action précédente qui est dans la file d’attente des messages. Le contrôle TEdit sera alors redessiné sans le texte.

La méthode Post sert à appeler l’API et mettre la réponse dans la variable Response, en voici un exemple:

{
 "kind": "urlshortener#url",
 "id": "http://goo.gl/bT3bc",
 "longUrl": "http://imgs.xkcd.com/comics/time_management.png"
}

Voilà, vous savez maintenant utiliser l’API URL Shortener avec C++Builder. Si vous préférez utiliser les services de TinyURL au lieu de Google, alors voici le code dont il faut vous servir dans l’événement OnClick du bouton:

    Edit2->Text = ""; // On efface le texte
    Application->ProcessMessages();

    Edit2->Text = IdHTTP1->Get("http://tinyurl.com/api-create.php?url=" + Edit1->Text);

Utiliser l’API PageSpeed de Google avec C++Builder

PageSpeed est un outil développé par Google qui sert à analyser une page Web. Dans cet article il sera utilisé pour obtenir le nombre d’octets de chaque élément qui composent une page web. Les résultats seront présentés dans un diagramme circulaire à l’aide du contrôle TChart.

Avant de débuter, il faut aller sur le site de Google pour activer l’API Page Speed Online dans la section Services. Il vous faudra créer une clef d’API qui sera utilisée à chaque requête. Il est à noter que Google vous donne le droit d’exécuter 2500 requêtes par jour pour cet API.

Page Speed Online API ON
Page Speed Online API Activé

La première étape est de créer un nouveau projet FireMonkey HD. Dans la Form il faut insérer un TIdHTTP, un TIdSSLIOHandlerSocketOpenSSL, un TChart et un TLayout dans lequel il y aura un TEdit un TButton. On aligne le contrôle TLayout à alBottom et le TChart à alClient. Vous pouvez donner comme texte à votre bouton le mot « Analyser ».

Avant même de voir le code, je vous présente le résultat final. Ceci vous permettra de mieux comprendre vers où nous nous dirigeons.
Analyseur de site web

Dans votre fichier cpp voici la liste de fichier d’en-tête à utiliser ainsi que la macro qui va contenir votre clef d’API. N’essayer pas d’utiliser celle-ci, il s’agit de caractères écrits de façon aléatoire.

#include <Data.DBXJSON.hpp>
#include <FMXTee.Series.hpp>
#define GOOGLEAPIKEY "dskk1j3sW4WBYdkjds8sSDSD" // Clef d'API

Voici le code à ajouter dans votre constructeur:

    IdHTTP1->IOHandler = IdSSLIOHandlerSocketOpenSSL1;

    Chart1->Title->Text->Text = "Statistique de la page"; // Titre du diagramme
    Chart1->Legend->Title->Text->Text = "Ressource en octets"; // Titre de la légende
    Series::TPieSeries *Series = new Series::TPieSeries(this); // Diagramme circulaire
    Series->Marks->Visible = false; // Ce n'est pas nécessaire car on a déjà une légende
    Chart1->AddSeries(Series);

Étant donné que nous accéderons à un site web qui utilise SSL (https), la première ligne de code est critique. Sans elle, une exception dans la classe EIdIOHandlerPropInvalid produira le message « IOHandler value is not valid ». Parce que nous utilisons OpenSSL, les fichiers ssleay32.dll et libeay32.dll devront être distribués avec votre application.

Les lignes 3 à 7 servent à ajouter des informations au graphique.

La prochaine étape est d’ajouter le code dans l’événement OnClick du bouton.

    Chart1->Series[0]->Clear(); // Efface le contenu du diagramme

    String Response = IdHTTP1->Get(
        "https://www.googleapis.com/pagespeedonline/v1/runPagespeed?url=" +
        Edit1->Text + "&key=" GOOGLEAPIKEY);

    TJSONObject* Obj = static_cast<TJSONObject*>(TJSONObject::ParseJSONValue(Response));
    TJSONPair* Pair = Obj->Get("pageStats");
    if(Pair)
    {
        String Value;
        TJSONString* Answer;
        TJSONObject* PageStats = static_cast<TJSONObject*>(Pair->JsonValue);
        if((Pair = PageStats->Get("htmlResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            Value = AnsiDequotedStr(Answer->ToString(), '\"');
            Chart1->Series[0]->Add(Value.ToIntDef(0), "HTML", claGreen);
        }
        if((Pair = PageStats->Get("cssResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            Value = AnsiDequotedStr(Answer->ToString(), '\"');
            Chart1->Series[0]->Add(Value.ToIntDef(0), "CSS", claOrange);
        }
        if((Pair = PageStats->Get("imageResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            Value = AnsiDequotedStr(Answer->ToString(), '\"');
            Chart1->Series[0]->Add(Value.ToIntDef(0), "Image", claYellow);
        }
        if((Pair = PageStats->Get("javascriptResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            Value = AnsiDequotedStr(Answer->ToString(), '\"');
            Chart1->Series[0]->Add(Value.ToIntDef(0), "JavaScript", claRed);
        }
        if((Pair = PageStats->Get("otherResponseBytes")) != NULL)
        {
            Answer = static_cast<TJSONString*>(Pair->JsonValue);
            Value = AnsiDequotedStr(Answer->ToString(), '\"');
            Chart1->Series[0]->Add(Value.ToIntDef(0), "Autre", claBlue);
        }
    }

Les lignes 3 à 5 servent à appeler l’API et mettre la réponse dans la variable Response. Cette réponse utilise le format JSON (JavaScript Object Notation). Voici la section qui nous intéresse pour notre projet:

{
 "pageStats": {
  "numberResources": 89,
  "numberHosts": 11,
  "totalRequestBytes": "18788",
  "numberStaticResources": 72,
  "htmlResponseBytes": "511838",
  "cssResponseBytes": "127217",
  "imageResponseBytes": "399484",
  "javascriptResponseBytes": "396089",
  "otherResponseBytes": "4023",
  "numberJsResources": 19,
  "numberCssResources": 4
 }
}

Comme on peut le voir certaines valeurs numériques utilisent des guillemets doubles, c’est pour cette raison que nous utiliserons la fonction AnsiDequotedStr pour les enlever.

En terminant, je voulais juste vous faire une petite mise en garde. Faites attention de protéger votre clef d’API car il est très facile pour n’importe qui de la retrouver en regardant le contenu de votre fichier exécutable. Même pas besoin d’éditeur hexadécimal compliqué, il suffit du Bloc-notes comme démontré ici:
Fichier exécutable ouvert dans le Bloc-notes