Update with media IDs: 400 bad request error (validation of media IDs failed)

android
java
api

#1

Hi all,

I am trying to extend the Twitter API client to be able to post updates with media IDs (using the statuses/update endpoint) to attach images to a tweet (note: I am aware of the Tweet Composer, but for several reasons it’s not suitable for me, so I would prefer to be able to extend the Statuses Service update method to include media IDs, as we know that it’s possible with the REST API). However, when I try to post with my method, I get 400 bad request error, even when I post without any media ID (so basically the same way as in the built-in update method, I actually copied the Retrofit fields from that). I don’t know what’s going wrong, as my request seems to be correctly formed.

Here are some code snippets:

Custom API client:

public interface StatusWithMediaService {
    @FormUrlEncoded
    @POST(value="/1.1/statuses/update.json")
    void update(@Field(value="status")
                java.lang.String status,
                @Field(value="in_reply_to_status_id")
                java.lang.Long inReplyToStatusId,
                @Field(value="possibly_sensitive")
                java.lang.Boolean possiblySensitive,
                @Field(value="lat")
                java.lang.Double latitude,
                @Field(value="long")
                java.lang.Double longitude,
                @Field(value="place_id")
                java.lang.String placeId,
                @Field(value="display_cooridnates")
                java.lang.Boolean displayCoordinates,
                @Field(value="trim_user")
                java.lang.Boolean trimUser,
                @Field(value ="media_ids")
                String mediaIds,
                com.twitter.sdk.android.core.Callback<Tweet> cb);
}

public class MyTwitterApiClient extends TwitterApiClient {

    public MyTwitterApiClient(TwitterSession session)
    {
        super(session);
    }

    public StatusWithMediaService getStatusWithMediaService() {
        return  getService(StatusWithMediaService.class);
    }

}

The part where I’m trying to tweet (note that I am using the ID of a recently manually uploaded picture as also the media uploading does not work for me for some reason, but let’s stick to one issue at a time :wink: )

    final TwitterSession session = Twitter.getSessionManager().getActiveSession();

    RestAdapter restAdapter = new RestAdapter.Builder()
                            .setEndpoint("https://api.twitter.com")
                            .setLogLevel(RestAdapter.LogLevel.FULL).setLog(new AndroidLog("testapp retrofit"))
                            .setRequestInterceptor(new RequestInterceptor() {
                        @Override
                        public void intercept(RequestFacade request) {

                            request.addHeader("Authorization", createSignature(session));

                        }
                    })
                    .build();

                    StatusWithMediaService mediaService = restAdapter.create(StatusWithMediaService.class);
                    mediaService.update("@tkl_testi " + eventtype + ": \n" + comment.getText().toString(), null, false, mypos.latitude, mypos.longitude, null, true, false, "628825606512316416", new Callback<Tweet>() {
                        @Override
                        public void success(Result<Tweet> tweetResult) {
                            Toast.makeText(getActivity(), "Raportin lähettäminen onnistui.", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void failure(TwitterException e) {
                            Log.e("testapp", e.getMessage());
                        }
                    });

Creating the header:

public static String sha1(String s, String keyString) throws
            UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {

        SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");

        mac.init(key);
        byte[] bytes = mac.doFinal(s.getBytes("UTF-8"));

        return Base64.encodeToString(bytes, Base64.URL_SAFE);
    }

    public String createSignature(TwitterSession session)
    {
        byte[] b = new byte[32];
        new Random().nextBytes(b);
        String randomBytes = null;

        try
        {
            randomBytes = URLEncoder.encode(String.valueOf(b), "UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
            Log.e("encoding error", e.getMessage());
        }

        long currentTimeInMillis = System.currentTimeMillis();
        TwitterAuthToken authToken = session.getAuthToken();
        String token = session.getAuthToken().token;
        String signature = String.format("oauth_consumer_key=%s&oauth_nonce=%s&oauth_signature_method=HMAC-SHA1&oauth_timestamp=%s&oauth_token=%s&oauth_version=1.0", MainActivity.TWITTER_KEY, randomBytes, currentTimeInMillis, token);
        String finalSignature = null;
        try
        {
            finalSignature = sha1(signature, MainActivity.TWITTER_SECRET + "&" +authToken.secret);
        }
        catch (UnsupportedEncodingException e)
        {
            Log.e("encoding error", e.getMessage());
        }
        catch (NoSuchAlgorithmException e)
        {
            Log.e("algorithm error", e.getMessage());
        }
        catch (InvalidKeyException e)
        {
            Log.e("key error", e.getMessage());
        }

        String header = String.format("OAuth oauth_consumer_key=\"%s\", oauth_nonce=\"%s\", oauth_signature=\"%s\", " +
                "oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"%s\", oauth_token=\"%s\", oauth_version=\"1.0\")", MainActivity.TWITTER_KEY, randomBytes, finalSignature, currentTimeInMillis, token);

        return header;
    }

When I run the app and log the request and response, I see this:

08-05 11:13:04.193  22497-23558/com.example.test.testapp D/testapp retrofit﹕ ---> HTTP POST https://api.twitter.com/1.1/statuses/update.json
08-05 11:13:04.193  22497-23558/com.example.test.testapp D/testapp retrofit﹕ Authorization: OAuth oauth_consumer_key="XXXXX", oauth_nonce="XXXXX", oauth_signature="XXXXX", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1438762384184", oauth_token="XXXXX", oauth_version="1.0")
08-05 11:13:04.193  22497-23558/com.example.test.testapp D/testapp retrofit﹕ Content-Type: application/x-www-form-urlencoded; charset=UTF-8
08-05 11:13:04.193  22497-23558/com.example.test.testapp D/testapp retrofit﹕ Content-Length: 135
08-05 11:13:04.194  22497-23558/com.example.test.testapp D/testapp retrofit﹕ status=%40tkl_testi+Onnettomuus%3A+%0A&possibly_sensitive=false&lat=61.4892668&long=23.7791515&display_cooridnates=true&trim_user=false
08-05 11:13:04.194  22497-23558/com.example.test.testapp D/testapp retrofit﹕ ---> END HTTP (135-byte body)
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ <--- HTTP 400 https://api.twitter.com/1.1/statuses/update.json (902ms)
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ : HTTP/1.0 400 Bad Request
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ content-length: 0
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ date: Wed, 05 Aug 2015 08:13:05 GMT
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ server: tsa_b
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ X-Android-Received-Millis: 1438762385096
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ X-Android-Response-Source: NETWORK 400
08-05 11:13:05.097  22497-23558/com.example.test.testapp D/testapp retrofit﹕ X-Android-Sent-Millis: 1438762384860
08-05 11:13:05.099  22497-23558/com.example.test.testapp D/testapp retrofit﹕ x-connection-hash: 9c60ff82dad92388344567a09da22e65
08-05 11:13:05.099  22497-23558/com.example.test.testapp D/testapp retrofit﹕ <--- END HTTP (0-byte body)

Does anyone have any idea what is wrong? I am completely lost right now :frowning:

UPDATE:

Instead of using the REST adapter, I tried instantiating my custom service through my custom client. So the update part now looks like this:

MyTwitterApiClient myclient = new MyTwitterApiClient(Twitter.getSessionManager().getActiveSession());

                    StatusWithMediaService mediaService = myclient.getStatusWithMediaService();
                    mediaService.update("@tkl_testi " + eventtype + ": \n" + comment.getText().toString(), null, false, mypos.latitude, mypos.longitude, null, true, false, null, new Callback<Tweet>() {
                        @Override
                        public void success(Result<Tweet> tweetResult) {
                            Toast.makeText(getActivity(), "Raportin lähettäminen onnistui.", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void failure(TwitterException e) {
                            TwitterApiException apiException = (TwitterApiException) e;
                            Log.e("testapp", apiException.getErrorMessage());
                            Log.e("testapp", apiException.getMessage());
                        }
                    });

With this now I can successfully post without media IDs. When I try to post with a media ID, however, I get the following error message:

The validation of media ids failed.

I can post the same status using twurl without any problem, so I kind of doubt the image size would be the problem (42 KB, 800x533).

By the way, I uploaded the image using twurl and that’s what I get back (just in case it helps you find anything problematic):

{"media_id":629232743633842180,"media_id_string":"629232743633842180","size":41860,"expires_after_secs":86400,"image":{"image_type":"image\/jpeg","w":800,"h":533}}

I always make a new upload before attempting to tweet, so duplicate ID should not be a problem.

Do you have any idea what the problem could be?


#2

OK, it turned out that it was my stupid mistake. I used a different account with twurl from the one I was using in the app, so the uploaded media came from a different user and did not have additional owners. After I switched accounts in twurl, everything worked fine.


#3

Thanks for following up!


#4