Search code examples
iostwitterparse-platformtwitter-oauthnsurlrequest

How do I add media to a Tweet using Parse.com and PFTwitterUtils?


I am trying to hit the media/upload endpoint to upload an image to twitter. I am signing the request with Parse.com's PFTwitterUtils class. Posting a tweet to the statuses/update works perfectly, but the media/upload endpoint keeps returning an authentication error:

{
    "errors": [
        {
            "message": "Could not authenticate you.",
            "code": 32
        }
    ]
}

I have made sure that I have a valid oauth token for the user and that the data being sent in the POST body is Base64 encoded.

Here is my code:

NSURL *url = [NSURL URLWithString:@"https://upload.twitter.com/1.1/media/upload.json"];
NSData *imageData = UIImageJPEGRepresentation(imageToUpload, 1.0);
NSString *postString = [NSString stringWithFormat:@"media=%@", [[imageData base64EncodedStringWithOptions:kNilOptions] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPMethod:@"POST"];

[[PFTwitterUtils twitter] signRequest:request];

NSLog(@"Sending twitter request...");
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    NSLog(@"Got twitter response");
    NSLog(@"Response: %@", response);
    NSError *jsonSerializationError;
    NSDictionary *mediaDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonSerializationError];
}];

Solution

  • I found this old Parse.com forum post that explained how to upload an image with the now deprecated statuses/update_with_media endpoint: https://parse.com/questions/how-to-upload-image-to-twitter-using-pftwitterutils-signrequest

    I realized I was signing my request properly with parse, but the endpoint is expecting a multipart/form-data POST request. After formatting the request properly, I was able to make a valid request to the media/upload endpoint and get a media_id value back.

    Below is my final working code:

    NSURL *mediaURL = [NSURL URLWithString:@"https://upload.twitter.com/1.1/media/upload.json"];
    
    UIImage *imageToUpload = [UIImage ...]
    
    NSData *imageData = UIImageJPEGRepresentation(imageToUpload, 1.0);
    
    NSMutableURLRequest *mediaRequest = [NSMutableURLRequest requestWithURL:mediaURL];
    [mediaRequest setHTTPMethod:@"POST"];
    
    NSString *boundary = @"---------------------------14737809831466499882746641449";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [mediaRequest addValue:contentType forHTTPHeaderField:@"Content-Type"];
    
    // body
    NSMutableData *body = [NSMutableData data];
    [body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Disposition: form-data; name=\"media\"; filename=\"image.jpg\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:imageData];
    [body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    [mediaRequest setHTTPBody:body];
    
    [[PFTwitterUtils twitter] signRequest:mediaRequest];
    
    NSLog(@"Sending twitter request...");
    [NSURLConnection sendAsynchronousRequest:mediaRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        NSString *mediaIdString;
        if (data && !connectionError) {
            NSError *jsonSerializationError;
            NSDictionary *mediaDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonSerializationError];
            if (!jsonSerializationError) {
                mediaIdString = mediaDict[@"media_id_string"];
            } else {
                NSLog(@"JSON serialization error: %@", jsonSerializationError);
            }
        } else {
            NSLog(@"Error hitting media/upload endpoint: %@", connectionError);
        }
    
        // add that media_id to a tweet using the statuses/update endpoint
    }];
    

    Helpful Twitter API Documentation links:

    https://dev.twitter.com/rest/public/uploading-media

    https://dev.twitter.com/rest/reference/post/media/upload

    I hope this post is useful for other developers using the Parse.com platform.