Twitter 提供了一个免费平台,任何人都可以在其网站上发布任何内容。 它可以如同财务提示一样有价值,也可以像任何知名人士表白自己见解一样无聊。 由于本文主要关注媒介而不是内容,所以我们就开始吧。
请注册 Twitter,访问 Twitter API 所需的一堆令牌会令您难以自拔。
起初,这些令牌可能会给您造成很大的困扰,仅仅是因为其中有许多类似的名称。 基本上,您需要以下令牌才能使用 Twitter API:注意:
如果您熟悉数字签名的公钥和私钥,那么您已立于正确的方向。
customer_token 和 customer_token_secret 是标识 “Twitter app” 的公钥和私钥。 简而言之,Twitter app 就是用 Twitter API 对您的服务和/或访问进行甄别。
access_token 和 access_token_secret 是将您标识为 “Twitter user” 的公钥和私钥,在 Twitter 的术语中称为 “用户身份验证”或“用户上下文”。 基于该 access_token,Twitter API 可以甄别正在访问它的是谁。
还有另一个所谓的 bearer_token ,它允许用 Twitter API 进行“匿名”访问。 这种访问方法称为 “app 身份验证”或 “app 上下文”。 如果没有“用户上下文”,则无法访问某些 Twitter API,这些 API 在Twitter API 参考中有很好的归档。
对于那些会用其他编程语言进行编码的人,可能会发现这些Twitter 函数库对于参考非常有用。 它们是非常有用的资源,可为实现细节提供深刻洞察,而有时仅通过阅读 API 文档无法了解这些细节。
我们专注于上述令牌:
YouTube 上有很多有关如何获取这些令牌的指南和/或教程。
OAuth 是基于 Web 的身份验证和授权的公认标准 API,被广泛使用,它也是 Twitter API 正在使用的。
简而言之,OAuth 是数字签名,一种针对数字内容进行签名的方法,故此任何操纵内容的尝试都将导致其无效。
为了正确验证内容及其签名,创建方法和过程都是特制的,所有内容均应精确遵循该过程。
再次指出,Twitter API 文档在整个归档工作中极为出色。
为了确保数字签名内容的绝对正确性,必须针对内容本身进行明确定义。 为此目的,Twitter API(准确地说是 OAuth 方法)需要 HTTP POST 和/或 HTTP GET 的参数在进行数字签名之前,要经过明确定义的步骤。
按字母顺序排序的参数
必须对 HTTP POST / HTTP GET 参数如下进行编码:
请注意,在参考文档和本文档中有关 “%” 和 “+” 字符的信息,它们也应正确编码。
另请注意此处引述来自参考文档里的有关参数排序的信息:
此需求的简单实现如下:
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; } }
使用上述函数的示例如下:
string params[][2]; addParam("oauth_callback", "oob", params); addParam("oauth_consumer_key", consumer_key, params); sortParam(params);
请注意:
为了简化,参数排序是不完整的,它不考虑多个相同的关键字参数。 如果您期望使用相同关键字的参数,即 html 表单上的单选按钮、复选框,则可能需要改进它。
在创建 OAuth 签名之前,另一个障碍是 MQL 中缺乏 HMAC-SHA1 原生支持。 事实证明,MQL CryptEncode() 在此只能用到一点点,因为它仅支持构建 SHA1-HASH,因此标记为:CRYPT_HASH_SHA1。
因此,我们借助 CryptEncode() 编写自己的 HMAC-SHA1
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; }
为了验证其正确性,我们拿其与在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(),现在可以轻松地经由 Web 访问任何 REST API,而无需使用任何外部 DLL。
以下代码将简化 WebRequest() 访问 Twitter API 的过程
#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 ""; }
为了使用 WebRequest() 函数,请在“选项”窗口的“智能系统”选卡”允许 URL“ 列表中添加所需服务器的地址。 服务器端口会根据指定的协议自动选择 - “http://” 为 80,“https://” 为 443。
下面是一些有助于构建 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¶ms[][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¶ms[][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¶ms[][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; }
现在,我们准备发送第一个 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); // 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":"", ... }
下图显示了一个 Twitter 客户端,其内显示的是印度尼西亚新闻频道的推文。 我正在准备有关更多 Twitter API 实现的后续文章,希望能尽快发布。
图例 1. 在图表上显示推文
另一个截图显示的是从 MT5 终端发布推文。
图例 2. 从 MT5 终端发布的推文
上述方法已能够很出色地工作,尽管距完整覆盖所有 Twitter API 还很遥远。 对于那些想深入了解 Twitter API 的人来说,这是一个很好的练习。 下一篇后续文章里将介绍有关发布媒介的更多详细信息,包括图表屏幕截图。
您也许想要构建一个 Twitter API 类,甚至是一个通用的 OAuth 客户端类。
您也许想更深入地研究所谓的三足式授权和基于 PIN 的授权。
深入讲述它们超出了本文的范围,若您需要进一步的指导,请随时与我联系。
Twitter 是一个广为接受的平台,任何人均可在其上发布几乎所有内容。
借助本文及里面讲述的代码,我希望以自己对访问 Twitter API 的 OAuth 的微薄了解,为 MQL 社区做出贡献。
期待鼓励和改进反馈。 您可随意在自己的免费和/或商业项目中使用本文提供的所有代码。
我要鸣谢 Grzegorz Korycki 所提供的这个函数库代码 (SHA256, SHA384 和 SHA512 + HMAC - 用于 MetaTrader 4 的函数库) 因它而启发我创建了 HMAC-SHA1 函数。
我们发推文来获取乐趣和收益吧!
尽享快乐。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...