Cómo escribir un cliente nativo de Twitter para MetaTrader 4 y MetaTrader 5 sin usar DLL

Soewono Effendi | 11 septiembre, 2020

Introducción

Twitter es una plataforma gratuita para el intercambio público de mensajes. El usuario puede escribir lo que quiera, ya sean consejos financieros u opiniones personales sobre personas famosas y no tan famosas. No obstante, en este artículo no hablaremos del contenido de los mensajes, sino del trabajo con los tweets.

Antes de nada, tenemos que registrarnos, para obtener así acceso al conjunto de tokens necesarios para trabajar con Tweeter API. Al principio, puede resultar confuso trabajar con estos tokens: son muchos y sus nombres se parecen bastante.

En general, para usar Twitter API, necesitaremos los siguientes tokens:

Observación: necesitaremos ciertos conocimientos sobre el trabajo con las claves públicas y privadas utilizadas para la firma digital.

Los dos primeros tokens, customer_token y customer_token_secret, son precisamente las claves pública y privada para identificar nuestra aplicación de Twitter. La aplicación de Twitter es, simplemente, la identificación de nuestro servicio y/o acceso utilizando la API de Twitter. 

Los tokens access_token y access_token_secret son las claves pública y privada para identificarnos como usuarios de Twitter. En el lenguaje de Twitter, esto se llama user auth o user context. Basándonos en este token, la API access_token puede determinar quién está accediendo a ella.

Además, hay otro token, bearer_token, con cuya ayuda podemos obtener el llamado acceso anónimo usando la Twitter API. Este método de acceso se llama app auth o app context. No es posible obtener acceso a algunas Twitter API sin user context. Encontrará más información sobre ello en la Guía de usuario de Twitter API

Si usted sabe programar en otros lenguajes, las bibliotecas de Twitter ya listas le resultarán de gran utilidad. Estas ofrecen una imagen clara sobre ciertos detalles de la implementación que pueden no resultar tan obvios al trabajar solo con la documentación de la API.


Tweeter API y autorización

Utilizaremos los tokens indicados más arriba:

En YouTube hay bastantes instrucciones y materiales ilustrativos sobre las formas de obtener estos tokens.

OAuth es un estándar común y ampliamente utilizado para la autenticación y autorización en la web API, usado también en Twitter API. En pocas palabras, OAuth es una firma digital, un método para firmar contenido digital, con el que cualquier intento de manipulación invalida este.

Para comprobar correctamente el contenido y su firma, se seguirán al detalle los métodos y procesos específicos de creación de ese contenido.

El proceso completo se describe perfectamente en la documentación de Twitter API.

Twitter API requiere un método de codificación determinado de las solicitudes de HTTP. Describiremos este método en el siguiente capítulo.

Codificación URL y clasificación de parámetros

Para garantizar que el contenido marcado con la firma digital sea correcto, el propio contenido deberá estar definido con claridad. Para estos cometidos, al realizar la firma digital, Twitter API (para ser más exactos, el método OAuth) necesita los parámetros HTTP POST y/o HTTP GET para dar los pasos obligatorios.

Es muy importante codificar los parámetros de HTTP POST / HTTP GET de la forma siguiente:

Podrá leer más sobre la codificación correcta de los símbolos "%" y "+", en la Guía de usuario y la documentación adicional.

Asimismo, deberemos prestar atención a la clasificación de los parámetros indicados de la documentación de referencia:

Una implementación simple de este requisito sería la siguiente:

string hex(int i)
  {
   static string h="0123456789ABCDEF";
   string ret="";
   int a = i % 16;
   int b = (i-a)/16;
   if(b>15)
      StringConcatenate(ret,ret,hex(b),StringSubstr(h,a,1));
   else
      StringConcatenate(ret,ret,StringSubstr(h,b,1),StringSubstr(h,a,1));
   return (ret);
  }

string URLEncode(string toCode)
  {
   int max=StringLen(toCode);

   string RetStr="";
   for(int i=0; i<max; i++)
     {
      string c = StringSubstr(toCode,i,1);
      ushort asc = StringGetCharacter(c, 0);

      if((asc >= '0' && asc <= '9')
         || (asc >= 'a' && asc <= 'z')
         || (asc >= 'A' && asc <= 'Z')
         || (asc == '-')
         || (asc == '.')
         || (asc == '_')
         || (asc == '~'))
         StringAdd(RetStr,c);
      else
        {
         StringConcatenate(RetStr,RetStr,"%",hex(asc));
        }
     }
   return (RetStr);
  }

string arrayEncode(string &array[][2])
  {
   string ret="";
   string key,val;
   int l=ArrayRange(array,0);
   for(int i=0; i<l; i++)
     {
      key = URLEncode(array[i,0]);
      val = URLEncode(array[i,1]);
      StringConcatenate(ret,ret,key,"=",val);
      if(i+1<l)
         StringConcatenate(ret,ret,"&");
     }
   return (ret);
  }

void sortParam(string&arr[][2])
  {
   string k1, k2;
   string v1, v2;
   int n = ArrayRange(arr,0);

// bubble sort
   int i, j;
   for(i = 0; i < n-1; i++)
     {
      // Last i elements are already in place
      for(j = 0; j < n-i-1; j++)
        {
         int x = j+1;
         k1 = arr[j][0];
         k2 = arr[x][0];
         if(k1 > k2)
           {
            // swap values
            v1 = arr[j][1];
            v2 = arr[x][1];
            arr[j][1] = v2;
            arr[x][1] = v1;
            // swap keys
            arr[j][0] = k2;
            arr[x][0] = k1;
           }
        }
     }
  }

void addParam(string key,string val,string&array[][2])
  {
   int x=ArrayRange(array,0);
   if(ArrayResize(array,x+1)>-1)
     {
      array[x][0]=key;
      array[x][1]=val;
     }
  }


Ejemplo de uso de las funciones analizadas más arriba:

   string params[][2];

   addParam("oauth_callback", "oob", params);
   addParam("oauth_consumer_key", consumer_key, params);

   sortParam(params);


Preste atención: para simplificar el código, la clasificación aquí no está completa, en ella no se tienen en cuenta los parámetros con la misma clave. Podmos mejorar el código, si utilizamos los parámetros con la misma clave. Por ejemplo, podemos añadir botones de radio o banderas como html.


Sobre HMAC-SHA1 de la forma más sencilla

Otra cosa incómoda al crear la firma de OAuth es la ausencia de soporte incorporado de HMAC-SHA1 en MQL. La función MQL CryptEncode() solo ofrece soporte al algoritmo SHA1-HASH. De aquí, obtenemos la bandera: CRYPT_HASH_SHA1.

Bien, vamos a codificar nuestro HMAC-SHA1 con la ayuda de CryptEncode()


string hmac_sha1(string smsg, string skey, uchar &dstbuf[])
  {
// Descripción de HMAC:
// https://es.wikipedia.org/wiki/HMAC
//
   uint n;
   uint BLOCKSIZE=64;
   uchar key[];
   uchar msg[];
   uchar i_s[];
   uchar o_s[];
   uchar i_sha1[];
   uchar keybuf[];
   uchar i_key_pad[];
   uchar o_key_pad[];
   string s = "";

   if((uint)StringLen(skey)>BLOCKSIZE)
     {
      uchar tmpkey[];
      StringToCharArray(skey,tmpkey,0,StringLen(skey));
      CryptEncode(CRYPT_HASH_SHA1, tmpkey, keybuf, key);
      n=(uint)ArraySize(key);
     }
   else
      n=(uint)StringToCharArray(skey,key,0,StringLen(skey));

   if(n<BLOCKSIZE)
     {
      ArrayResize(key,BLOCKSIZE);
      ArrayFill(key,n,BLOCKSIZE-n,0);
     }

   ArrayCopy(i_key_pad,key);
   for(uint i=0; i<BLOCKSIZE; i++)
      i_key_pad[i]=key[i]^(uchar)0x36;

   ArrayCopy(o_key_pad,key);
   for(uint i=0; i<BLOCKSIZE; i++)
      o_key_pad[i]=key[i]^(uchar)0x5c;

   n=(uint)StringToCharArray(smsg,msg,0,StringLen(smsg));
   ArrayResize(i_s,BLOCKSIZE+n);
   ArrayCopy(i_s,i_key_pad);
   ArrayCopy(i_s,msg,BLOCKSIZE);

   CryptEncode(CRYPT_HASH_SHA1, i_s, keybuf, i_sha1);
   ArrayResize(o_s,BLOCKSIZE+ArraySize(i_sha1));
   ArrayCopy(o_s,o_key_pad);
   ArrayCopy(o_s,i_sha1,BLOCKSIZE);
   CryptEncode(CRYPT_HASH_SHA1, o_s, keybuf, dstbuf);

   for(int i=0; i < ArraySize(dstbuf); i++)
      StringConcatenate(s, s, StringFormat("%02x",(dstbuf[i])));

   return s;
  }



Para comprobar que el resultado sea correcto, vamos a compararlo con el hash creado según la documentación de Twitter API:

   uchar hashbuf[];
   string base_string = "POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521";
   string signing_key = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE";  
   string hash = hmac_sha1(base_string, signing_key, hashbuf);

   Print(hash); // 842b5299887e88760212a056ac4ec2ee1626b549

   uchar not_use[];
   uchar base64buf[];
   CryptEncode(CRYPT_BASE64, hashbuf, not_use, base64buf);
   string base64 = CharArrayToString(base64buf);
   
   Print(base64); // hCtSmYh+iHYCEqBWrE7C7hYmtUk=



WebRequest al rescate

La función WebRequest() permite acceder a cualquier interfaz REST API a través de internet sin usar una DLL externa. El siguiente código permite acceder a Twitter API con la ayuda de WebRequest()


#define WEBREQUEST_TIMEOUT 5000
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
string SendRequest(string method, string url, string headers="", string params="", int timeout=WEBREQUEST_TIMEOUT)
  {
   char data[];
   char result[];
   string resp_headers;
   ResetLastError();
   StringToCharArray(params, data);
   ArrayResize(data, ArraySize(data)-1);
   int res = WebRequest(method, url, headers, timeout, data, result, resp_headers);
   if(res != -1)
     {
      string resp = CharArrayToString(result);
      if(verbose)
        {
         Print("***");
         Print("Data:");
         Print(CharArrayToString(data));
         Print("Resp Headers:");
         Print(resp_headers);
         Print("Resp:");
         Print("***");
         Print(resp);
        }
      return resp;
     }
   else
     {
      int err = GetLastError();
      PrintFormat("* WebRequest error: %d (%d)", res, err);
      if(verbose)
        {
         Print("***");
         Print("Data:");
         Print(CharArrayToString(data));
        }
      if (err == 4014)
      {
         string msg = "* PLEASE allow https://api.tweeter.com in WebRequest listed URL";
         Print(msg);
      }
     }
   return "";
  }

Le rogamos que se familiarice con la documentación de WebRequest().

Para usar la función WebRequest(), deberemos añadir las direcciones de los servidores a la lista de URL permitidas en la pestaña "Asesores" de la ventana "Ajustes". El puerto del servidor se selecciona automáticamente usando como base el protocolo: 80 para "http://" y 443 para "https://".

Funciones auxiliares para crear Twitter REST API

Estas funciones auxiliares nos ayudarán a crear la firma de Twitter API.

string getNonce()
  {
   const string alnum = "abcdef0123456789";
   char base[];
   StringToCharArray(alnum, base);
   int x, len = StringLen(alnum);
   char res[32];
   for(int i=0; i<32; i++)
     {
      x = MathRand() % len;
      res[i] = base[x];
     }
   return CharArrayToString(res);
  }

string getBase(string&params[][2], string url, string method="POST")
  {
   string s = method;
   StringAdd(s, "&");
   StringAdd(s, URLEncode(url));
   StringAdd(s, "&");
   bool first = true;
   int x=ArrayRange(params,0);
   for(int i=0; i<x; i++)
     {
      if(first)
         first = false;
      else
         StringAdd(s, "%26");  // URLEncode("&")
      StringAdd(s, URLEncode(params[i][0]));
      StringAdd(s, "%3D"); // URLEncode("=")
      StringAdd(s, URLEncode(params[i][1]));
     }
   return s;
  }

string getQuery(string&params[][2], string url = "")
  {
   string key;
   string s = url;
   string sep = "";
   if(StringLen(s) > 0)
     {
      if(StringFind(s, "?") < 0)
        {
         sep = "?";
        }
     }
   bool first = true;
   int x=ArrayRange(params,0);
   for(int i=0; i<x; i++)
     {
      key = params[i][0];
      if(StringFind(key, "oauth_")==0)
         continue;
      if(first)
        {
         first = false;
         StringAdd(s, sep);
        }
      else
         StringAdd(s, "&");
      StringAdd(s, params[i][0]);
      StringAdd(s, "=");
      StringAdd(s, params[i][1]);
     }
   return s;
  }

string getOauth(string&params[][2])
  {
   string key;
   string s = "OAuth ";
   bool first = true;
   int x=ArrayRange(params,0);
   for(int i=0; i<x; i++)
     {
      key = params[i][0];
      if(StringFind(key, "oauth_")!=0)
         continue;
      if(first)
         first = false;
      else
         StringAdd(s, ", ");
      StringAdd(s, URLEncode(key));
      StringAdd(s, "=\"");
      StringAdd(s, URLEncode(params[i][1]));
      StringAdd(s, "\"");
     }
   return s;
  }



Ejemplo de script

Ahora, podemos enviar la primera solicitud de Twitter API.

void verifyCredentials()
  {

   string _api_key = consumer_key;
   string _api_secret = consumer_secret;
   string _token = access_token;
   string _secret = access_secret;

   string url = "https://api.twitter.com/1.1/account/verify_credentials.json";
   
   string params[][2];
   addParam("oauth_consumer_key", _api_key, params);
   string oauth_nonce = getNonce();
   addParam("oauth_nonce", oauth_nonce, params);
   addParam("oauth_signature_method", "HMAC-SHA1", params);
   
   string oauth_timestamp = IntegerToString(TimeGMT());
   addParam("oauth_timestamp", oauth_timestamp, params);
   
   addParam("oauth_token", _token, params);
   addParam("oauth_version", "1.0", params);
   
   sortParam(params);
   
   string query = getQuery(params, url);
   string base = getBase(params, url, "GET");
   uchar buf[];
   string key = URLEncode(_api_secret);
   StringAdd(key, "&");
   StringAdd(key, URLEncode(_secret));
   
   uchar hashbuf[], base64buf[], nokey[];
   string hash = hmac_sha1(base, key, hashbuf);
   CryptEncode(CRYPT_BASE64, hashbuf, nokey, base64buf);
   string base64 = CharArrayToString(base64buf);

   addParam("oauth_signature", base64, params);
   sortParam(params);
   string o = getOauth(params);

   string headers = "Host:api.twitter.com\r\nContent-Encoding: identity\r\nConnection: close\r\n";
   StringAdd(headers, "Authorization: ");
   StringAdd(headers, o);
   StringAdd(headers, "\r\n\r\n");
   
   string resp = SendRequest("GET", query, headers);
   Print(resp);

   // si todo está en orden, obtenemos la respuesta JSON
   // {"id":122,"id_str":"122","name":"xxx","screen_name":"xxx123","location":"","description":"", ... 
  }



Ejemplo de cliente de Twitter en un gráfico de MetaTrader 5


En la imagen, podemos ver un cliente de Twitter que muestra los tweets de un canal de noticias de Indonesia. En estos momentos, estamos preparando la publicación del siguiente artículo, con una implementación adicional de Twitter API.



Cliente de Twitter en un gráfico MT5

Fig 1. Representación de tweets en el gráfico


La siguiente captura de pantalla muestra la publicación de tweets desde el terminal MetaTrader 5.



Tweets desde el terminal MetaTrader 5
Fig.2. Tweet publicado desde el terminal MetaTrader 5 


Futuras mejoras


El método descrito más arriba funciona bien, pero no abarca, ni mucho menos, todas las funciones de Twitter API. Se trata de una buena base para los que quieren analizar Tweeter API con más detralle. En el próximo artículo, veremos cómo realizar publicaciones en Twitter, incluyendo capturas de pantalla de los gráficos.

Podremos crear la clase TwitterAPI, o incluso la clase general de cliente OAuth.

Notas sobre la autorización en tres pasos y la autorización basada en código PIN


Asimismo, para trabajar con Twitter, podremos utilizar la llamada autorización en tres pasos y la autorización por código PIN.

No obstante, el tema excede el marco del presente artículo. Si tiene usted alguna duda o necesita ayuda, le rogamos que nos escriba.

Conclusión

Twitter es una popular plataforma que permite a cualquiera publicar prácticamente todo lo que desee. Esperamos que los artículos y el código contenido en ellos resulten útiles para la comunidad MQL, y le ayuden a comprender OAuth al trabajar con Twitter API.

Esperamos sus comentarios y sugerencias. Puede usted utilizar cualquier código de este artículo en sus proyectos gratuitos y/o de pago.

Quisiéramos dar las gracias al usuario  Grzegorz Korycki por la biblioteca (SHA256, SHA384 y SHA512 + HMAC - la biblioteca MetaTrader 4), que inspiró al autor la creación de HMAC-SHA1.

¡Esperamos que los tweets le traigan alegría y beneficios!