Como escrever um cliente nativo Twitter para MetaTrader 4 e MetaTrader 5 sem usar DLL

Soewono Effendi | 14 setembro, 2020

Introdução

O Twitter é uma plataforma gratuita para troca pública de mensagens. O usuário pode escrever qualquer coisa, desde conselhos financeiros valiosos até opiniões pessoais de pessoas famosas e não tão famosas. Porém, neste artigo, não falaremos sobre o conteúdo, mas, sim, sobre a forma de trabalhar com tweets.

Em primeiro lugar, precisamos nos registrar para obter acesso ao conjunto de tokens necessários para trabalhar com a Tweeter API. No início, podemos nos confundir com esses tokens: há muitos e ainda por cima têm nomes semelhantes.

Em geral, precisamos dos seguintes tokens para usar a Twitter API:

Nota: será necessário saber como trabalhar com as chaves públicas e privadas usadas para assinatura digital.

Os primeiros dois tokens, customer_token e customer_token_secret, são apenas as chaves pública e privada para identificar nosso aplicativo Twitter. Em termos leigos, um aplicativo do Twitter é a identificação do nosso serviço e/ou acesso usando Twitter API. 

Os tokens access_token e access_token_secret as chaves pública e privada para nos identificar como usuários do Twitter. Na linguagem do Twitter, isso é chamado de user auth ou user context. Com base nisso, a API access_token pode determinar quem a está acessando.

Há também outro token, bearer_token, com o qual podemos obter o chamado acesso anônimo usando a Twitter API. Este método de acesso é chamado app auth ou app context. Não é possível acessar algumas Twitter API sem o user context. Saiba mais sobre isso no guia online da Twitter API

Se sabe programar em outras linguagens, você pode usar as Bibliotecas do Twitter. Elas dão uma boa ideia sobre os detalhes de implementação que poderão não ser tão óbvios ao trabalhar apenas com a documentação da API.


Tweeter API e autorização

Usaremos os tokens mostrados acima:

No Youtube há muitas instruções e materiais de treinamento sobre como obter esses tokens.

OAuth é um padrão geralmente aceito e amplamente usado para autenticação e autorização de APIs da web, que também é usado pela Twitter API. Em termos simples, OAuth é uma assinatura digital, um método de assinatura de conteúdo digital que ao ser usado qualquer tentativa de manipular o conteúdo o invalida.

Para validar e assinar adequadamente o conteúdo, é necessário usar certos métodos e seguir o processo exato de criação desse conteúdo.

Todo o processo está bem documentado na documentação da Twitter API.

Twitter API exige um método de codificação específico de solicitações HTTP. Este método será descrito no próximo capítulo.

Codificação de URL e classificação de parâmetros

Para garantir que o conteúdo assinado digitalmente seja preciso, o próprio conteúdo deve ser claramente definido. Para isso, ao usar uma assinatura digital, a Twitter API (mais precisamente, o método OAuth) requer os parâmetros HTTP POST e/ou HTTP GET para concluir as etapas necessárias.

É imperativo codificar os parâmetros HTTP POST / HTTP GET assim:

Para obter informações sobre como codificar corretamente os caracteres "%" e "+", leia o guia de referência e a documentação adicional.

Ademais, preste atenção à classificação de parâmetros especificados na documentação de referência:

Implementação simples desse requisito:

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;
     }
  }


Exemplo de uso das funções discutidas acima:

   string params[][2];

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

   sortParam(params);

Atenção: para simplificar o código, a classificação aqui é incompleta, não leva em consideração os parâmetros de chaves idênticas. O código pode ser melhorado se você usar parâmetros com chaves idênticas. Por exemplo, é possível adicionar botões de opção ou caixas de seleção num formulário html.


Sobre o HMAC-SHA1 da maneira mais simples possível

Outro inconveniente ao criar uma assinatura OAuth é a falta de suporte integrado para HMAC-SHA1 em MQL. A função MQL CryptEncode() suporta apenas o algoritmo SHA1-HASH. A partir daqui obtemos o sinalizador: CRYPT_HASH_SHA1.

Bem, codifiquemos nosso HMAC-SHA1 com ajuda de CryptEncode()


string hmac_sha1(string smsg, string skey, uchar &dstbuf[])
  {
// Описание HMAC:
// https://ru.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 verificar se o resultado é certo, comparemos com o hash gerado com base na documentação da 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=


Solução via WebRequest

A função WebRequest() permite acessar qualquer interface REST API via Internet sem usar uma DLL externa. O código a seguir permite acessar a Twitter API com ajuda 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 "";
  }

Certifique-se de verificar a documentação sobre WebRequest().

Para usar a função WebRequest(), é necessário adicionar os endereços dos servidores à lista de URL permitidos na guia "Experts" da janela "Configurações". A porta do servidor é selecionada automaticamente com base no protocolo especificado - 80 para "http://" e 443 para "https://".

Funções auxiliares para criar uma Twitter REST API

Essas funções auxiliares serão úteis ao criar uma assinatura da 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;
  }


Script de amostra

Agora é possível enviar a primeira solicitação 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);

   // если все в порядке, мы получим JSON-ответ
   // {"id":122,"id_str":"122","name":"xxx","screen_name":"xxx123","location":"","description":"", ... 
  }


Exemplo de cliente Twitter num gráfico do MetaTrader 5


A imagem abaixo mostra um cliente Twitter exibindo tweets a partir de um canal de notícias da Indonésia. No momento, estou preparando a publicação do seguinte artigo com una implementação adicional da Twitter API.



Cliente Twitter no gráfico MT5

Figura 1. Exibindo tweets num gráfico


A seguinte imagem mostra a postagem de tweets a partir do terminal MetaTrader 5.



Tweet a partir do terminal MetaTrader 5
Fig. 2. Tweet postado a partir do terminal MetaTrader 5 


Melhorias futuras


O método acima funciona bem, mas não cobre todas as funções da Twitter API. Esta é uma boa base para quem deseja aprender mais sobre a Tweeter API. No próximo artigo, veremos como postar no Twitter, incluindo capturas de tela de gráficos.

Será possível criar uma classe TwitterAPI ou até mesmo uma classe de cliente OAuth genérica.

Nota sobre autorização em três passos e baseada em PIN


Além disso, para trabalhar com o Twitter, podemos usar autorização em três passos e autorização de código PIN.

No entanto, isso está além do escopo deste artigo. Se você tiver alguma dúvida ou precisar de ajuda, entre em contato comigo.

Fim do artigo

O Twitter é uma plataforma popular que permite a qualquer usuário postar quase de tudo. Espero que os artigos e o código que eles contêm sejam úteis para a comunidade MQL e ajudem você a entender o OAuth ao trabalhar com a Twitter API.

Aguardo seus comentários e sugestões. Você pode usar qualquer código deste artigo em seus projetos gratuitos e/ou pagos.

Quero agradecer ao usuário  Grzegorz Korycki pela biblioteca (SHA256, SHA384 и SHA512 + HMAC - biblioteca para MetaTrader 4) que me inspirou a criar a função HMAC-SHA1.

Desejamos que os tweets tragam prazer e lucro!