0
votes

We are trying to implement Twitter new DM API from Salesforce. We are sending our the JSON request in the body as mentioned in documentation but the legacy method for Oauth authorization is not working. Any help is greatly appreciated.

To add, I am sending a DM from salesforce to twitter, So 1) I am setting the request body in JSON. 2) I am doing a POST. 3) I am hitting the endpoint at 'https://api.twitter.com/1.1/direct_messages/events/new.json' 4) Oauth2, getting the access token(successfully) 5) Setting header as ('Content-Type', 'application/json'). 6) Creating Authorization header as twitter mentions using consumer key, Nonce, Signature, Signature method, Timestamp, Version. Building the same as in "Guide" section of developer.twitter.com/en/docs/basics/authentication/guides/ 7) On running the error code "{"errors":[{"code":32,"message":"Could not authenticate you."}]}".

Another important information that I had been using twitter old API to send DM that works perfect, only difference is it sends the request body in URL parameters instead of JSOn body but the authorization method remains same. As some new Functionality can only be achieved via Twitter New API and according to documentation the body needs to be sent via JSON format. Therefore the request part is changed but authorization is same.

Sample code:-

String accTok = 'redacted';
String conKey = 'redacted';
String conSec = 'redacted';
String accTokSec = 'redacted';

String theTweet = 'Hello world!';
String screenName ='some_test_username';
String jsonString = TwitterJsonReqGenerator.generateJSON(theTweet, screenName);
system.debug('JSON string ='+jsonString);

httpRequest newReq = new httpRequest();
newReq.setBody(jsonString);
newReq.setMethod('POST');
newReq.setEndpoint('https://api.twitter.com/1.1/direct_messages/events/new.json');

//Generate Nonce
string oAuth_nonce = EncodingUtil.base64Encode(blob.valueOf(string.valueOf(Crypto.getRandomInteger()+system.now().getTime())+string.valueOf(Crypto.getRandomInteger()))).replaceAll('[^a-z^A-Z^0-9]','');

map<String, String> heads = new map<String, String>{
    'oauth_token'=>accTok,
    'oauth_version'=>'1.0',
    'oauth_nonce'=>oAuth_nonce,
    'oauth_consumer_key'=>conKey,
    'oauth_signature_method'=>'HMAC-SHA1',
    'oauth_timestamp'=>string.valueOf(system.now().getTime()/1000)
};

//Alphabetize 
string[] paramHeads = new string[]{};
paramHeads.addAll(heads.keySet());
paramHeads.sort();
string params = '';
for(String encodedKey : paramHeads){
    params+=encodedKey+'%3D'+heads.get(encodedKey)+'%26';
}

//params+='status'+percentEncode('='+percentEncode(theTweet));
params+=percentEncode(theTweet);

//Build the base string
string sigBaseString = newReq.getMethod().toUpperCase()+'&'+EncodingUtil.urlEncode(newReq.getEndpoint(),'UTF-8')+'&'+params;
system.debug('signatureBaseString == '+sigBaseString);

//calculate signature
string sigKey = EncodingUtil.urlEncode(conSec,'UTF-8')+'&'+EncodingUtil.urlEncode(accTokSec,'UTF-8');
blob mac = crypto.generateMac('hmacSHA1', blob.valueOf(sigBaseString), blob.valueOf(sigKey));
string oauth_signature = EncodingUtil.base64Encode(mac);
heads.put(EncodingUtil.urlEncode('oauth_signature','UTF-8'), EncodingUtil.urlEncode(oauth_signature,'UTF-8'));

//build the authorization header
paramHeads.clear();
paramHeads.addAll(heads.keySet());
paramHeads.sort();
string oAuth_Body = 'OAuth ';
for(String key : paramHeads){
    oAuth_Body += key+'="'+heads.get(key)+'", ';
}
oAuth_Body = oAuth_Body.subString(0, (oAuth_Body.length() - 2));
newReq.setHeader('Authorization', oAuth_Body);
system.debug('Authroization Header == '+oAuth_Body);
newReq.setHeader('Content-Type', 'application/json');

httpResponse httpRes = new http().send(newReq);
String response = httpRes.getBody();

system.debug(response);

Thanks Prateek

2
Could you post your code through edit this question?Dima Kozhevin
Hi Dima, Thanks for replying.Prateek Jain
You should consider adding information about your problem, like what's not working. Also, you should consider adding info about the project like what technology are you using. Code sample are also really appreciated in those caseNicolas
Hi Dima, Thanks for replying. I can summarize you how we are doing the authorization as twitter mentions: consumer key, Nonce, Signature, Signature method, Timestamp, Version. Building the same as in "Guide" section of developer.twitter.com/en/docs/basics/authentication/guides/….Prateek Jain

2 Answers

0
votes

I've written Twitter libraries and applications in the past, and the bst advice that I can give you is to use an existing implementation of OAuth instead of attempting to write your own. Re-implementing OAuth in new code is re-inventing the wheel, and it's a wheel that hates you. There are a number of robust and mature OAuth libraries that are free and/or open source.

0
votes

Just happened to stumble on your query. I am posting a code(c#) (though it is a bit late) which worked for me to send DM to twitter using the new API. Hope this helps. Thanks to Danny Tuppeny's blog

namespace TweetApp.Droid
{
class TweetDM
{
    const string TwitterApiBaseUrl = "https://api.twitter.com/1.1/";
    readonly string consumerKey, consumerKeySecret, accessToken, accessTokenSecret;
    readonly HMACSHA1 sigHasher;
    readonly DateTime epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    public TweetDM(string consumerKey, string consumerKeySecret, string accessToken, string accessTokenSecret)
    {
        this.consumerKey = consumerKey;
        this.consumerKeySecret = consumerKeySecret;
        this.accessToken = accessToken;
        this.accessTokenSecret = accessTokenSecret;

        sigHasher = new HMACSHA1(new ASCIIEncoding().GetBytes(string.Format("{0}&{1}", consumerKeySecret, accessTokenSecret)));
    }

    public Task<string> Tweet(string text, string recipientID)
    {
        JSONObject jasonobject = new JSONObject
        {
            @event = new TwitterEvent
            {
                type = "message_create",
                message_create = new msg_create
                {
                    target = new tgt
                    {
                        recipient_id = recipientID
                    },
                    message_data = new msg_data
                    {
                        text = text
                    }
                },
            }
        };

        var JsonString =JsonConvert.SerializeObject(jasonobject);


        var data4Auth = new Dictionary<string, string>   {
        };
        return PrepareAuth("direct_messages/events/new.json", data4Auth, JsonString);
      }

    Task<string> PrepareAuth(string url, Dictionary<string, string> data4Auth, string JsonString)
    {
        var fullUrl = TwitterApiBaseUrl + url;

        var timestamp = (int)((DateTime.UtcNow - epochUtc).TotalSeconds);

        data4Auth.Add("oauth_consumer_key", consumerKey);
        data4Auth.Add("oauth_signature_method", "HMAC-SHA1");
        data4Auth.Add("oauth_timestamp", timestamp.ToString());
        data4Auth.Add("oauth_nonce", "a"); // Required, but Twitter doesn't appear to use it, so "a" will do.
        data4Auth.Add("oauth_token", accessToken);
        data4Auth.Add("oauth_version", "1.0");

        // Generate the OAuth signature and add it to our payload.
        data4Auth.Add("oauth_signature", GenerateSignature(fullUrl, data4Auth));

        // Build the OAuth HTTP Header from the data.
        string oAuthHeader = GenerateOAuthHeader(data4Auth);

        // Setting Content details
        var JsonData = new StringContent(JsonString, Encoding.UTF8, "application/json");
        return SendRequest(fullUrl, oAuthHeader, JsonData);
    }

    string GenerateSignature(string url, Dictionary<string, string> data)
    {
        var sigString = string.Join(
            "&",
            data
                .Union(data)
                .Select(kvp => string.Format("{0}={1}", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
                .OrderBy(s => s)
        );

        var fullSigData = string.Format(
            "{0}&{1}&{2}",
            "POST",
            Uri.EscapeDataString(url),
            Uri.EscapeDataString(sigString.ToString())
        );

        return Convert.ToBase64String(sigHasher.ComputeHash(new ASCIIEncoding().GetBytes(fullSigData.ToString())));
    }

    string GenerateOAuthHeader(Dictionary<string, string> data)
    {
        return "OAuth " + string.Join(
            ", ",
            data
                .Where(kvp => kvp.Key.StartsWith("oauth_"))
                .Select(kvp => string.Format("{0}=\"{1}\"", Uri.EscapeDataString(kvp.Key), Uri.EscapeDataString(kvp.Value)))
                .OrderBy(s => s)
        );
    }

    async Task<string> SendRequest(string fullUrl, string oAuthHeader, StringContent jsondata)
    {
        using (var http = new HttpClient())
        {
            http.DefaultRequestHeaders.Add("Authorization", oAuthHeader);
            var httpResp = await http.PostAsync(fullUrl, jsondata);
            var respBody = await httpResp.Content.ReadAsStringAsync();
            return respBody;
        }
    }
}
// Classes for creating JSON body
public class JSONObject
{
    public TwitterEvent @event;
}
    public class TwitterEvent
{
    public string type;
    public msg_create message_create;
   }
public class msg_create
{
    public tgt target;
    public msg_data message_data;
}
public class tgt
{
    public string recipient_id;
}
public class msg_data
{
    public string text;
}
}

To call:

 var twitter = new TweetDM(consumerKey, consumerKeySecret, accessToken, accessTokenSecret);
 await twitter.Tweet(textBox1.Text, textBox2.Text);