Nativer Twitter-Client für MT4 und MT5 ohne DLL

Soewono Effendi | 9 September, 2020

Einführung


Tweeter bietet eine kostenlose Plattform für jeden, der etwas auf seiner Website veröffentlichen möchte. Das kann so wertvoll sein wie finanzielle Tipps oder so wertlos, wie viele Gedanken Prominenter. Da sich dieser Artikel in erster Linie auf die Medien und nicht auf den Inhalt konzentriert, lassen Sie uns anfangen.

Bitte melden Sie sich bei Tweeter an, um sich mit einem Bündel von Token zu beglücken, die für den Zugriff auf die Tweeter-API erforderlich sind.

Diese Token können anfangs recht verwirrend sein, einfach weil viele von ihnen ähnlichen Namen haben. Im Grunde genommen benötigen Sie nur folgende Token, um die Twitter-API nutzen zu können:

Anmerkung:

Wenn Sie mit öffentlichen und privaten Schlüsseln zum digitalen Signieren vertraut sind, sind Sie bereits auf der richtigen Seite.

customer_token und customer_token_secret sind öffentliche und private Schlüssel zur Identifizierung Ihrer "Twitter-App". Eine Twitter-Anwendung ist, einfach gesagt, eine Identifizierung Ihres Dienstes und/oder Zugangs über die Twitter-API. 

access_token und access_token_secret sind öffentliche und private Schlüssel, um Sie als "Twitter-Nutzer" zu identifizieren, im Twitter-Begriff heißt es user auth oder user context. Basierend auf diesem access_token kann die Twitter-API identifizieren, wer darauf zugreift.

Es gibt noch weitere so genannte bearer_token, die einen anonymen Zugang über die Twitter-API erlauben. Diese Zugriffsmethode wird als app auth oder app context bezeichnet. Ohne user context sind einige Twitter-APIs nicht zugänglich, die unter Twitter-API-Referenz gut dokumentiert sind.

Für diejenigen, die in anderen Programmiersprachen programmieren können, sind diese Twitter Libraries als Referenz nützlich. Sie sind großartige Ressourcen, die einen guten Einblick in Implementierungsdetails bieten, die manchmal nicht offensichtlich sind, wenn man nur die API-Dokumentation liest.


Tweeter-API und Autorisierung

Wir konzentrieren uns auf die Verwendung der oben genannten Tokens:

Auf YouTube gibt es zahlreiche Anleitungen und/oder Tutorials, wie man diese Token erhält.

OAuth ist ein gut akzeptierter und weit verbreiteter Standard für die Authentifizierung und Autorisierung der webbasierten API, die auch die Twitter-API verwendet.
Einfach gesagt, ist die OAuth eine digitale Signatur, eine Methode, digitale Inhalte so zu signieren, dass jeder Versuch, den Inhalt zu manipulieren, diesen ungültig macht.

Um den Inhalt und seine Signatur korrekt zu verifizieren, müssen bestimmte Methoden und Prozesse zur Erstellung dieses Inhalts genau befolgt werden.

Auch hier hat die Twitter-API-Dokumentation bei der Dokumentation dieses gesamten Prozesses hervorragende Arbeit geleistet.

Um diesen Prozess einfach zu halten, verlangt die Twitter-API eine bestimmte Methode der Kodierung einer Anforderung über HTTP. Diese Methode wird im nächsten Kapitel beschrieben.

URL-Kodierung und Sortierung der Parameter

Um die absolute Richtigkeit digital signierter Inhalte zu gewährleisten, muss der Inhalt selbst klar definiert sein. Zu diesem Zweck erfordert die Twitter-API (genauer gesagt die OAuth-Methode) HTTP POST und/oder HTTP GET-Parameter, um wohldefinierte Schritte durchlaufen zu können, bevor sie digital signiert werden.

Es ist zwingend erforderlich, die Parameter für HTTP POST / HTTP GET wie folgt zu kodieren:

Bitte beachten Sie auch in der Referenzdokumentation und dieser Dokumentation bezüglich der "%"- und "+"-Zeichen, die ebenfalls korrekt kodiert werden müssen.

Bitte beachten Sie auch die Sortierung der hier zitierten Parameter aus der Referenzdokumentation:

Eine einfache Umsetzung dieser Anforderung ist wie folgt:

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


Ein Beispiel für die Verwendung der obigen Funktionen ist wie folgt:

   string params[][2];

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

   sortParam(params);

Bitte beachten Sie:
Zur Vereinfachung ist die Sortierung der Parameter unvollständig, sie berücksichtigt nicht mehrere gleiche Schlüsselparameter. Vielleicht möchten Sie sie verbessern, falls Sie Parameter mit gleichen Schlüsseln verwenden, d.h. Optionsfelder, Kontrollkästchen auf Ihrem html-Formular.


HMAC-SHA1, so einfach wie möglich

Eine weitere Hürde vor dem Erstellen einer OAuth-Signatur ist das Fehlen einer nativen HMAC-SHA1-Unterstützung in MQL. Es stellt sich heraus, dass CryptEncode() von MQL hier kaum von Nutzen ist, da es nur den Aufbau von SHA1-HASH unterstützt, daher das Flag: CRYPT_HASH_SHA1.

Codieren wir also unser eigenes HMAC-SHA1 mit Hilfe von CryptEncode()


string hmac_sha1(string smsg, string skey, uchar &dstbuf[])
  {
// HMAC as described on:
// https://en.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;
  }


Um die Richtigkeit zu überprüfen, können wir es mit dem Hash vergleichen, der auf Twitter API documentation erstellt wurde:

   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 als Rettung


Dank WebRequest() ist es jetzt einfach, auf jede REST-API über das Web zuzugreifen, ohne eine externe DLL verwenden zu müssen.
Der folgende Code vereinfacht den Zugriff auf die Twitter-API mit 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 "";
  }

LESEN SIE BITTE DIE DOKUMENTATION VON WEBREQUEST().


ZITAT:

Um die Funktion WebRequest() zu verwenden, fügen Sie die Adressen der benötigten Server in die Liste der zulässigen URLs im Register "Expert Advisors" des Fensters "Optionen" ein. Der Server-Port wird automatisch auf der Grundlage des angegebenen Protokolls ausgewählt - 80 für "http://" und 443 für "https://".


Hilfsfunktionen zum Aufbau der Twitter REST API

Nachfolgend einige Hilfsfunktionen, die beim Aufbau einer Twitter-API-Signatur nützlich sind.

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


Beispielskript

Jetzt sind wir bereit, unsere erste Twitter-API-Anfrage zu senden.

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

   // if everything works well, we shall receive JSON-response similar to the following
   // NOTE: Identity has been altered to protect the innocent.
   // {"id":122,"id_str":"122","name":"xxx","screen_name":"xxx123","location":"","description":"", ... 
  }


Ein Beispiel für einen Twitter-Client auf einem MT5-Chart


Das folgende Bild zeigt einen Twitter-Client, der Tweets eines indonesischen Nachrichtenkanals anzeigt. Ich bereite einen Folgeartikel mit weiterer Twitter-API-Implementierung vor, den ich hoffentlich so bald wie möglich veröffentlichen werde.



Twitter-Client auf MT5-Chart

Abb. 1 Tweets dargestellt auf dem Chart


Ein anderer Screenshot zeigt Tweets, die von einem anderen MT5-Terminal gesendet wurden.



Tweet eines anderen MT5-Terminals
Abb. 2 Tweet eines anderen MT5-Terminals 


Zukünftige Erweiterungen


Die oben beschriebene Methode funktioniert so, wie sie ist, auch wenn sie bei weitem nicht vollständig ist, um alle Twitter-APIs abzudecken. Sie ist eine gute Übung für diejenigen, die tiefer in die Tweeter-API eintauchen möchten. Einige weitere Details zum Posten von Medien, einschließlich Diagramm-Screenshots, werden im nächsten Artikel beschrieben.

Vielleicht möchten Sie eine TwitterAPI-Klasse oder sogar eine allgemeine OAuth-Client-Klasse erstellen.

Ein Hinweis zu 3-Wege Autorisierung und PIN-basierter Autorisierung


Vielleicht möchten Sie tiefer in die so genannte 3-Wege Autorisierung und auch in die PIN-basierte Autorisierung eintauchen.

Es liegt jenseits dieses Artikels, sie zu beschreiben, bitte zögern Sie nicht, mich zu kontaktieren, falls Sie weitere Hilfe benötigen.

Schlussfolgerung

Twitter ist eine gut akzeptierte Plattform, die es jedem ermöglicht, fast alles zu veröffentlichen.
Mit diesem Artikel und seinem oben beschriebenen Code hoffe ich, mit dem Ergebnis meines kleinen Abenteuers, OAuth beim Zugriff auf die Twitter-API zu verstehen, zur MQL-Gemeinschaft beizutragen.

Ich freue mich auf ermutigende und verbessernde Rückmeldungen. Sie können jederzeit den gesamten zur Verfügung gestellten Code in Ihren freien und/oder kommerziellen Projekten verwenden.

Ich möchte mich bei Grzegorz Korycki für die Bereitstellung dieses Bibliothekscodes (SHA256, SHA384 und SHA512 + HMAC - Bibliothek für MetaTrader 4) bedanken, wodurch ich inspiriert wurde, die HMAC-SHA1-Funktion zu erstellen.

Let's tweet, mit Gewinn und Genuss!

Viel Spaß.