1 module twitter.twitterbase; 2 3 import twitter.api; 4 5 import vibe.http.common; 6 7 /// took some inspiration from: https://github.com/alphaKAI/twitter4d 8 abstract class TwitterBase 9 { 10 private static immutable API_URL = "https://api.twitter.com"; 11 12 private TwitterCredentials credentials; 13 14 /// 15 this(TwitterCredentials credentials) 16 { 17 this.credentials = credentials; 18 } 19 20 /// 21 protected auto request(T)(string path, HTTPMethod method, string[string] params) 22 { 23 import std.algorithm : filter, map; 24 import std.string : join; 25 import std.conv : to; 26 import vibe.d : requestHTTP, HTTPClientRequest, deserializeJson, 27 formEncode, readAllUTF8; 28 import vibe.core.log : logError, logInfo, logDebug; 29 30 immutable url = API_URL ~ path; 31 32 string[string] paramsObj = buildParams(params); 33 paramsObj["oauth_signature"] = signature(method, url, paramsObj); 34 35 auto authorizeKeys = paramsObj.keys.filter!q{a.startsWith("oauth_")}; 36 auto authorize = "OAuth " ~ authorizeKeys.map!(k => k ~ "=" ~ paramsObj[k]).join(","); 37 38 string getPath = paramsObj.keys.map!(k => k ~ "=" ~ paramsObj[k]).join("&"); 39 40 auto res = requestHTTP(url ~ '?' ~ getPath, (scope HTTPClientRequest req) { 41 42 req.method = method; 43 // if (method == HTTPMethod.POST) 44 // { 45 // req.headers["Content-Type"] = "application/x-www-form-urlencoded"; 46 // req.headers["Content-Length"] = (getPath.length).to!string; 47 // } 48 req.headers["Authorization"] = authorize; 49 50 //req.bodyWriter.write(getPath); 51 }); 52 scope (exit) 53 { 54 res.dropBody(); 55 } 56 57 if (res.statusCode == 200) 58 { 59 auto json = res.readJson(); 60 61 scope (failure) 62 { 63 logError("Response deserialize failed: %s", json); 64 } 65 66 return deserializeJson!T(json); 67 } 68 else 69 { 70 logDebug("API Error: %s", res.bodyReader.readAllUTF8()); 71 logError("API Error Code: %s", res.statusCode); 72 throw new Exception("API Error"); 73 } 74 } 75 76 private string signature(HTTPMethod method, string url, string[string] params) 77 { 78 import std.digest.sha : SHA1; 79 import std.digest.hmac : hmac; 80 import std.string : representation; 81 import std.algorithm : map, sort; 82 import std.string : join; 83 import std.base64 : Base64; 84 85 immutable methodString = method == HTTPMethod.GET ? "GET" : "POST"; 86 87 auto query = sort(params.keys).map!(k => k ~ "=" ~ params[k]).join("&"); 88 auto key = [this.credentials.consumerSecret, this.credentials.accessTokenSecret].map!( 89 x => encodeComponent(x)).join("&"); 90 auto base = [methodString, url, query].map!(x => encodeComponent(x)).join("&"); 91 string oauthSignature = encodeComponent( 92 Base64.encode(base.representation.hmac!SHA1(key.representation))); 93 94 return oauthSignature; 95 } 96 97 private string[string] buildParams(string[string] additionalParam = null) 98 { 99 import std.uuid : randomUUID; 100 import std.conv : to; 101 import std.datetime.systime : Clock; 102 103 immutable now = Clock.currTime.toUnixTime.to!string; 104 105 string[string] params = [ 106 "oauth_consumer_key" : this.credentials.consumerKey, // 107 "oauth_nonce" : randomUUID().to!string, // 108 "oauth_signature_method" : "HMAC-SHA1", // 109 "oauth_timestamp" : now, // 110 "oauth_token" : this.credentials.accessToken, // 111 "oauth_version" : "1.0" 112 ]; 113 114 if (additionalParam !is null) 115 { 116 foreach (key, value; additionalParam) 117 { 118 params[key] = value; 119 } 120 } 121 122 foreach (key, value; params) 123 { 124 params[key] = encodeComponent(value); 125 } 126 127 return params; 128 } 129 130 private string encodeComponent(string s) 131 { 132 import std.regex : ctRegex, replaceAll; 133 import std.uri : encodeComponentUnsafe = encodeComponent; 134 135 char hexChar(ubyte c) 136 { 137 assert(c >= 0 && c <= 15); 138 if (c < 10) 139 return cast(char)('0' + c); 140 else 141 return cast(char)('A' + c - 10); 142 } 143 144 enum InvalidChar = ctRegex!`[!\*'\(\)]`; 145 146 return s.encodeComponentUnsafe.replaceAll!((s) { 147 char c = s.hit[0]; 148 char[3] encoded; 149 encoded[0] = '%'; 150 encoded[1] = hexChar((c >> 4) & 0xF); 151 encoded[2] = hexChar(c & 0xF); 152 return encoded[].idup; 153 })(InvalidChar); 154 } 155 }