How to post an image on twitter in Phonegap using javascript

advertisements

I'm currently implementing a web smartphone application with Phonegap. On this application, users can post images they take with the phone camera on Facebook. This feature has been succesfully implemented only using javascript, by sending a base 64 encoded image. Now, I want to implement the same feature using Twitter.

I found some very interesting blog posts about this and I'm already be able to update the user status only using javascript... but I can't post images too using the update_with_media Twitter web service.

According too this post, someone says it's impossible to implement this operation without using a server side code (like a php script for example).

So my question is : is it possible to use the update_with_media Twitter web service only with javascript ?

I send you my code to have an overview of the current solution. I've taken this article as working base : http://oodlestechnologies.com/blogs/Twitter-integration-on-PhoneGap-using-ChildBrowser-and-OAuth-for-iOS-and-Android-Platforms

Here is my HTML code.

<!DOCTYPE html>
<html>
    <head>
        <title>Test</title>
        <script type="text/javascript" src="../js/jquery/jquery.min.js"></script>
        <script type="text/javascript" src="../cordova-2.5.0.js"></script>
        <script type="text/javascript" src="../js/childBrowser/childbrowser.js"></script>
        <script type="text/javascript" src="../js/helpers/jsOAuth-1.3.6.js"></script>
        <script type="text/javascript" src="../js/helpers/twitter.js"></script>
    </head>
    <body>
        <h4>Oodles Twitter App</h4>
        <table border="1">
            <tr>
                <th>Login using Twitter</th>
                <th>
                    <button id="loginBtn" onclick="Twitter.init();">Login</button>
                    <button id="logoutBtn" onclick="logOut();">Logout</button>
                </th>
            </tr>
            <tr id="tweetText">
                <td colspan="2"><textarea id="tweet"></textarea></td>
            </tr>
            <tr id="tweetBtn">
                <td colspan="2" align="right">
                    <button id="tweeter" onclick="Twitter.tweet();">Tweet</button>
                </td>
            </tr>
            <tr><td colspan="2"><div id="welcome">Please Login to use this app</div></td></tr>
        </table>
        <br/>
        <br/>
        <button onclick="javascript:location.reload();">Recharger la page</button>
    </body>
</html>

Here is my twitter.js code : (The point is in the post method)

$(document).ready(function() {
    document.addEventListener("deviceready", onDeviceReady, false);
});

function onDeviceReady() {
    var root = this;
    cb = window.plugins.childBrowser;
    if (!localStorage.getItem(twitterKey)) {
        $("#loginBtn").show();
        $("#logoutBtn").hide();
        $("tweetBtn").hide();
        $("tweetText").hide();
    }
    else {
        $("#loginBtn").hide();
        $("#logoutBtn").show();
        $("tweetBtn").show();
        $("tweetText").show();
    }

    if (cb != null) {
        cb.onLocationChange = function(loc) {
            root.locChanged(loc);
        };
        cb.onClose = function() {
            root.onCloseBrowser()
        };
        cb.onOpenExternal = function() {
            root.onOpenExternal();
        };
    }
}

function onCloseBrowser() {
    console.log("onCloseBrowser!");
}

function locChanged(loc) {
    console.log("locChanged!");
}

function onOpenExternal() {
    console.log("onOpenExternal!");
}

// Consumer key : ...
// Consumer secret : ...

// GLOBAL VARS
var oauth; // It Holds the oAuth data request
var requestParams; // Specific param related to request
var options = {consumerKey: '...', consumerSecret: '...', callbackUrl: "http://www.google.fr"};
var twitterKey = "twtrKey"; // This key is used for storing Information related
var Twitter = {
    init: function() {
        // Apps storedAccessData , Apps Data in Raw format
        var storedAccessData, rawData = localStorage.getItem(twitterKey);
        // here we are going to check whether the data about user is already with us.
        if (localStorage.getItem(twitterKey) !== null) {
            // when App already knows data
            storedAccessData = JSON.parse(rawData); //JSON parsing
            //options.accessTokenKey = storedAccessData.accessTokenKey; // data will be saved when user first time signin
            options.accessTokenSecret = storedAccessData.accessTokenSecret; // data will be saved when user first first signin

            // javascript OAuth take care of everything for app we need to provide just the options
            oauth = OAuth(options);
            oauth.get('https://api.twitter.com/1/account/verify_credentials.json?skip_status=true',
                    function(data) {
                        var entry = JSON.parse(data.text);
                        console.log("USERNAME: " + entry.screen_name);
                    }
            );
        }
        else {
            // we have no data for save user
            oauth = OAuth(options);
            oauth.get('https://api.twitter.com/oauth/request_token',
                    function(data) {
                        requestParams = data.text;
                        cb.showWebPage('https://api.twitter.com/oauth/authorize?' + data.text); // This opens the Twitter authorization / sign in page
                        cb.onLocationChange = function(loc) {
                            Twitter.success(loc);
                        }; // Here will will track the change in URL of ChildBrowser
                    },
                    function(data) {
                        console.log("ERROR: " + JSON.stringify(data));
                    }
            );
        }
    },
    /*
     When ChildBrowser's URL changes we will track it here.
     We will also be acknowledged was the request is a successful or unsuccessful
     */
    success: function(loc) {

        // Here the URL of supplied callback will Load

        /*
         Here Plugin will check whether the callback Url matches with the given Url
         */
        if (loc.indexOf("http://www.google.fr") >= 0) {

            // Parse the returned URL
            var index, verifier = '';
            var params = loc.substr(loc.indexOf('?') + 1);

            params = params.split('&');
            for (var i = 0; i < params.length; i++) {
                var y = params[i].split('=');
                if (y[0] === 'oauth_verifier') {
                    verifier = y[1];
                }
            }

            // Here we are going to change token for request with token for access

            /*
             Once user has authorised us then we have to change the token for request with token of access
             here we will give data to localStorage.
             */
            oauth.get('https://api.twitter.com/oauth/access_token?oauth_verifier=' + verifier + '&' + requestParams,
                    function(data) {
                        var accessParams = {};
                        var qvars_tmp = data.text.split('&');
                        for (var i = 0; i < qvars_tmp.length; i++) {
                            var y = qvars_tmp[i].split('=');
                            accessParams[y[0]] = decodeURIComponent(y[1]);
                        }

                        $('#oauthStatus').html('<span style="color:green;">Success!</span>');
                        $('#stage-auth').hide();
                        $('#stage-data').show();
                        oauth.setAccessToken([accessParams.oauth_token, accessParams.oauth_token_secret]);

                        // Saving token of access in Local_Storage
                        var accessData = {};
                        accessData.accessTokenKey = accessParams.oauth_token;
                        accessData.accessTokenSecret = accessParams.oauth_token_secret;

                        // Configuring Apps LOCAL_STORAGE
                        console.log("TWITTER: Storing token key/secret in localStorage");
                        localStorage.setItem(twitterKey, JSON.stringify(accessData));

                        oauth.get('https://api.twitter.com/1/account/verify_credentials.json?skip_status=true',
                                function(data) {
                                    var entry = JSON.parse(data.text);
                                    console.log("TWITTER USER: " + entry.screen_name);
                                    $("#welcome").show();
                                    document.getElementById("welcome").innerHTML = "welcome " + entry.screen_name;
                                    successfulLogin();
                                    // Just for eg.
                                    app.init();
                                },
                                function(data) {
                                    console.log("ERROR: " + data);
                                }
                        );

                        // Now we have to close the child browser because everthing goes on track.

                        window.plugins.childBrowser.close();
                    },
                    function(data) {
                        console.log(data);

                    }
            );
        }
        else {
            // Just Empty
        }
    },
    tweet: function() {
        var storedAccessData, rawData = localStorage.getItem(twitterKey);

        storedAccessData = JSON.parse(rawData); // Paring Json
        options.accessTokenKey = storedAccessData.accessTokenKey; // it will be saved on first signin
        options.accessTokenSecret = storedAccessData.accessTokenSecret; // it will be save on first login

        // javascript OAuth will care of else for app we need to send only the options
        oauth = OAuth(options);
        oauth.get('https://api.twitter.com/1/account/verify_credentials.json?skip_status=true',
                function(data) {
                    var entry = JSON.parse(data.text);
                    Twitter.post();
                }
        );
    },
    /*
     We now have the data to tweet
     */
    post: function() {
        alert('Post !');
        var theTweet = $("#tweet").val(); // You can change it with what else you likes.

        oauth.post('https://upload.twitter.com/1/statuses/update_with_media.json',
                {
                    'status': theTweet,
                    'media': //HERE IS THE PROBLEM, WHAT TO DO HERE ?
                }, "multipart/form-data",
                function(data)
                {
                    alert('Data 1 !');
                    console.log('------Data1 : ' + data);
                    var entry = JSON.parse(data.text);
                    console.log(entry);
                    done();
                },
                function(data) {
                    //var json_result = JSON.parse(data);
                    //alert(json_result.text.error);
                    var entry = JSON.stringify(data);
                    console.log('------Data2 : ' + entry);
                }
        );
    }

}

function done() {
    alert("OKKK !");
    $("#tweet").val('');
}

function successfulLogin() {
    $("#loginBtn").hide();
    $("#logoutBtn,#tweet,#tweeter,#tweetBtn,#tweetText").show();

}

function logOut() {
    //localStorage.clear();
    window.localStorage.removeItem(twitterKey);
    document.getElementById("welcome").innerHTML = "Please Login to use this app";
    $("#loginBtn").show();
    $("#logoutBtn,#tweet,#tweeter,#tweetText,#tweetBtn").hide();

}

After many tests (sending a base64 image, sending a blob, sending a binary file, ...) here is the return message from Twitter I have :

{\"errors\":[{\"message\":\"Internal error\",\"code\":131}]}","xml":"","requestHeaders":{"Content-Type":"multipart/form-data"},"responseHeaders":{"date":"Fri, 19 Apr 2013 15:45:28 GMT","content-encoding":"deflate","strict-transport-security":"max-age=631138519","status":"500 Internal Server Error","server":"tfe","content-type":"application/json; charset=utf-8","version":"HTTP/1.1"}}

A "solution" (by send a blob) have been posted on the Twitter dev forum but not working for me : dev.twitter.com/discussions/6969

Does anyone want to implement the same feature or have a solution ? Thank you !

------ EDITED :

I just want to use Javascript and I don't want to implement any server-side solution (no PHP, C#, Java...).


According to the docs, Twitter requires the multipart/form-data enctype, which means a base64 string isn't going to work.

Unlike POST statuses/update, this method expects raw multipart data. Your POST request's Content-Type should be set to multipart/form-data with the media[] parameter ~ https://dev.twitter.com/docs/api/1/post/statuses/update_with_media

However, you could host an endpoint that takes base64, converts it to a real file, and forwards the request to Twitter. For example (untested):

<?php

$base64 = $_POST['image'];
$data = base64_decode( $base64 );

// Make name unique to avoid conflicts.
$temp_file = uniqid() . $_POST['name'];

// Save the file to a temp location.
file_put_contents( $temp_file, $data );

$temp_info = pathinfo( $temp_file );
$temp_type = $temp_info['extension'];
$temp_name = basename( $temp_file, '.' . $temp_type );

// OAuth library recommended by Twitter: https://github.com/themattharris/tmhOAuth
// See original: https://github.com/themattharris/tmhOAuth-examples/blob/master/images.php

require 'tmhOAuth.php';
require 'tmhUtilities.php';

$tmhOAuth = new tmhOAuth( array(
    'consumer_key'    => $_POST['consumer_key'],
    'consumer_secret' => $_POST['consumer_secret'],
    'user_token'      => $_POST['user_token'],
    'user_secret'     => $_POST['user_secret'],
));

// note the type and filename are set here as well
// Edit: Not sure if the `type` and `filename` params are necessary.
$params = array( 'media[]' => "@{$temp_file};type={$temp_type};filename={$temp_name}" );

$code = $tmhOAuth->request( 'POST', $tmhOAuth->url( '1/status/update_with_media' ),
    $params,
    true, // use auth
    true  // multipart
);

// Remove temp file.
unlink( $temp_file );

if ( $code == 200 ) {
    tmhUtilities::pr( json_decode( $tmhOAuth->response['response'] ) );
}
tmhUtilities::pr( htmlentities( $tmhOAuth->response['response'] ) );

?>

And you might call it like:

    $.ajax({
        // You'll want to use https to protect the oauth info.
        url: "https://mysite.com/proxy.php",
        type: "POST",
        data: {
            image: "base64 data...",
            name: "foo.png",
            consumer_key: options.consumerKey,
            consumer_secret: options.consumerSecret,
            user_token: options.accessTokenKey,
            user_secret: options.accessTokenSecret
        },
        success: function( data ) {
            console.log( data );
        }
    });