Tagged: dabr

HOWTO: Twitpic and OAuth

I am no longer confused! Here is a quick tutorial in how to post images to Twitpic and Twitter when using OAuth. I'm indebted to Steve Corona of Twitpic, for his help with this.

You can see the full code on Dabr's Google Code page.

First of all, you'll need to have enabled OAuth for your Twitter client. I use Abraham's excellent OAuth libraries for PHP.

This tutorial assumes you already have OAuth working. I'll attempt to explain what I'm doing as I go along - but the code should be pretty readable.

Start by reading the Twitpic API documentation. You will also need to register for an API key - this only takes a few seconds.

We start by setting CURL's headers to those required by Twitpic

  1. //Has the user submitted an image and message?
  2. if ($_POST['message'])
  3. {
  4.  $twitpicURL = 'http://api.twitpic.com/2/upload.json';
  5.  
  6.  //Set the initial headers
  7.  $header = array(
  8.        'X-Auth-Service-Provider: https://api.twitter.com/1/account/verify_credentials.json',
  9.        'X-Verify-Credentials-Authorization: OAuth realm="http://api.twitter.com/"'
  10.       );

Next, we create the OAuth credentials that we need. Essentially, we're signing a URL request. We then pass that on to Twitpic and they verify it with Twitter. We never pass our OAUTH_CONSUMER_SECRET - so Twitpic can't impersonate us.

  1.  //Using Abraham's OAuth library
  2.  require_once('OAuth.php');
  3.  
  4.  // instantiating OAuth customer
  5.  $consumer = new OAuthConsumer(OAUTH_CONSUMER_KEY, OAUTH_CONSUMER_SECRET);
  6.  
  7.  // instantiating signer
  8.  $sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
  9.  
  10.  // user's token
  11.  list($oauth_token, $oauth_token_secret) = explode('|', $GLOBALS['user']['password']);
  12.  $token = new OAuthConsumer($oauth_token, $oauth_token_secret);
  13.  
  14.  // Generate all the OAuth parameters needed
  15.  $signingURL = 'https://api.twitter.com/1/account/verify_credentials.json';
  16.  
  17.  $request = OAuthRequest::from_consumer_and_token($consumer, $token, 'GET', $signingURL, array());
  18.  
  19.  $request->sign_request($sha1_method, $consumer, $token);

We add these generated credentials into the header.

  1.  $header[1] .= ", oauth_consumer_key=\"" . $request->get_parameter('oauth_consumer_key') ."\"";
  2.  $header[1] .= ", oauth_signature_method=\"" . $request->get_parameter('oauth_signature_method') ."\"";
  3.  $header[1] .= ", oauth_token=\"" . $request->get_parameter('oauth_token') ."\"";
  4.  $header[1] .= ", oauth_timestamp=\"" . $request->get_parameter('oauth_timestamp') ."\"";
  5.  $header[1] .= ", oauth_nonce=\"" . $request->get_parameter('oauth_nonce') ."\"";
  6.  $header[1] .= ", oauth_version=\"" . $request->get_parameter('oauth_version') ."\"";
  7.  $header[1] .= ", oauth_signature=\"" . urlencode($request->get_parameter('oauth_signature')) ."\"";

Add everything into CURL

  1.  //open connection
  2.  $ch = curl_init();
  3.  
  4.  //Set paramaters
  5.  curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
  6.  curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
  7.  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  8.  
  9.  //set the url, number of POST vars, POST data
  10.  curl_setopt($ch,CURLOPT_URL,$twitpicURL);

The data we send to Twitpic (message text, image and key) have to go via POST.

  1.  //TwitPic requires the data to be sent as POST
  2.  $media_data = array(
  3.        'media' => '@'.$_FILES['media']['tmp_name'],
  4.            'message' => ' ' . stripslashes($_POST['message']), //A space is needed because twitpic b0rks if first char is an @
  5.            'key'=>TWITPIC_API_KEY
  6.       );
  7.  
  8.  curl_setopt($ch, CURLOPT_POST, true);
  9.  curl_setopt($ch,CURLOPT_POSTFIELDS,$media_data);

All done, we now send the data to Twitpic.

  1.  //execute post
  2.  $result = curl_exec($ch);
  3.  $response_info=curl_getinfo($ch);
  4.  
  5.  //close connection
  6.  curl_close($ch);

If this has worked, Twitpic will pass back the URL of the image we posted. We then need to post the entire message to Twitter ourselves (Twitpic can't do it for us).

  1.  if ($response_info['http_code'] == 200) //Success
  2.  {
  3.   //Decode the response
  4.   $json = json_decode($result);
  5.   $id = $json->id;
  6.   $twitpicURL = $json->url;
  7.   $text = $json->text;
  8.   $message = trim($text) . " " . $twitpicURL;

This next part is specific to Dabr - your client may post things differently.

  1.   //Send the user's message to twitter
  2.   $request = API_URL.'statuses/update.json';
  3.  
  4.   $post_data = array('source' => 'dabr', 'status' => $message);
  5.   $status = twitter_process($request, $post_data);
  6.  
  7.   //Back to the timeline
  8.   twitter_refresh("twitpic/confirm/$id");
  9.  }

If it didn't work, Twitpic will tell us why.

  1.  else
  2.  {
  3.   $content = "<p>Twitpic upload failed. No idea why!</p>";
  4.   $json = json_decode($result);
  5.   $content .= "<br / /><b>message</b> " . urlencode($_POST['message']);
  6.   $content .= "<br / /><b>json</b> " . print_r($json);
  7.   $content .= "<br / /><b>Response</b> " . print_r($response_info);
  8.   $content .= "<br / /><b>header</b> " . print_r($header);
  9.   $content .= "<br / /><b>media_data</b> " . print_r($media_data);
  10.   $content .= "<br /><b>URL was</b> " . $twitpicURL;
  11.   $content .= "<br /><b>File uploaded was</b> " . $_FILES['media']['tmp_name'];
  12.  }
  13. }

Bugs in Twitter Text Libraries

The Twitter Engineering Team have a set of text processing classes which are meant to simplify and standardise the recognition of URLs, screen names, and hashtags. Dabr makes use of them to keep in conformance with Twitter's style.

One of the advantages of the text processing is that it will recognise that www.example.com is a URL and automatically create a hyperlink. Considering that dropping the "http://" represents 5% saving on Twitter's 140 character limit for messages, this is great.

So, I was mightily surprised to get this bug report from user "schmmuck"

Dabr rendering error
Dabr rendering error

How very odd... This is how it looks on m.twitter.com.

m.twitter rendering error
m.twitter rendering error

Twitter also use mobile.twitter.com for smartphones. Here's how that site renders the text.

mobile.twitter rendering error
mobile.twitter rendering error

Finally, let's take a look at the "canonical" rendering at Twitter.com

Twitter rendering error
Twitter rendering error

The Problem(s)

The first issue is inconsistency.  Twitter ought to be using the same regex for each of its sites.  It doesn't.  This means that different developers will get divergent experiences.  This leads to confusion, which leads to fear, which, as we all know, leads to anger.... and so forth.

Secondly, and more importantly, parsing is hard.  There are so many edge cases that errors inevitably creep in.  My post about hashtags explains the problems in defining what should be recognised.

So, based on what we've seen, should Twitter recognise any of the following as URLs?

news.bbc.co.uk - no www there.

invalid.name - a silly URL, but a valid one.

खोज.com - International domains contain more than just ASCII

All the above are valid - yet they're not recognised by Twitter.

A (Simple) Solution?

There is a canonical list of TLDs which is also available as a plain text list.

Any string containing a "." followed by a valid TLD, then followed by a space or "/" should be treated as a URL.

Your thoughts?

Dabr - Reply to all and Geotagging

People have been very excited to see some new functionality in Dabr - the mobile Twitter client I develop for. But what is it and how does it work?

@@ and geotag
@@ and geotag

Reply to All

The @@ symbol allows you to reply to all the people mentioned within the tweet.
It only shows up on tweets which mention other users - so you should only ever see it when it can be used.

Hitting @@ on the above tweet will pre-populate the text box with "@topgold @whatleydude @dabr".

It should remove any duplicates and should also remove your username.

Geotagging

Twitter recently allowed the geotagging of tweets. This allows you to set the longitude and latitude of where you are when you wrote your message.
Clicking on the green globe (it just looks like a dot at that size!) takes you to a mobile mapping service.

Google Maps Mobile
Google Maps Mobile

It only shows up on tweets which are geotagged - so you should only ever see it when it can be used.

How Does It Work?

You can see all the code in the SVN, but here's a quick rundown.

Geotagging is very simple.  Statuses now have a <geo> field.  If this is populated, it will contain a longitude and latitude.

We can take those values and pass them to Google Maps Mobile (or your favourite mobile mapping service).
if ($geo !== null)
{
   $latlong = $geo->coordinates;
   $lat = $latlong[0];
   $long = $latlong[1];
   $actions[] = theme('action_icon', "http://maps.google.co.uk/m?q={$lat},{$long}", 'images/map.png', 'MAP');
}

Reply to all is a little more involved.
Twitter provide an excellent set of Text Libraries. These provide officially sanctioned regular expressions to help your project find URLs, hashtags and usernames.

Using the Twitter Text Extractor class we can determine how many usernames are mentioned in a tweet. Any duplicates are removed - as is the user's username.

Any questions - just ask!

Hashtags and Implicit Knowledge

What is "Implicit Knowledge"? Essentially it's stuff that everyone knows, but no one has written down. Usually it's something that people have worked out through their own experiences.

This sort of knowledge is common in life - but is fatal in computing and design. Take the following tweet I received.

@edent @dabr you folks aware ampersands / &s don't seem to work as part of hashtag links?

The complaint was that #tfm&a should be rendered as #tfm&a not #tfm&a.

Everyone knows that's how hashtags work!

On Twitter's website, find the page which discusses hashtag syntax. Find where they explain how they should be styled.

You can't.

And thus implicit knowledge is born. Dabr only looks at letters and numbers in a hashtag. It assumes that any other character is the end of the tag.

Dabr's Hashtag
Dabr's Hashtag

Without official guidance - implicit knowledge develops.

Has Dabr Got It Wrong?

No. I don't think so. Take a look at how Twitter on the web renders hashtags...

Twitter's Web Site
Twitter's Web Site

...and on the mobile.

Twitter Mobile
Twitter Mobile

So Where Does Render The Full Tag?

Several applications don't render tags in the same way as Twitter. Take a look at SocialScope

SocialScope Hashtags
SocialScope Hashtags
Tweetie2
Tweetie2

I'll upload more screenshots if I find examples of "badly behaved" hashtags.  Please let me know if you find any.

What Does Twitter Say?

Twitter has one page devoted to hashtags.  It is a support page for hashtags.  This explains to people what hashtags are.  There's no detail on valid characters, maximum length, or any of the things which might be useful for a developer or designer.

Edit 2010-02-25

David Dorward has pointed out that there is an official resource. On the Twitter Engineering blog - which isn't linked to from the developer site - there is a page discussing hashtags and how to validate them. You'll notice that they are rather circumspect on what should constitute a hashtag.

Conclusion

Standards and guidelines allow developers to create compatible applications.

Without explicit recommendations, developers will diverge as widely as possible.  Twitter - and everyone with an interest in compatibility and usability - needs to ensure that the knowledge they impart is explicit.

Letting people make it up as they go along leads to confusion.

Don't Let Users Do Things They Can't Do

There are many "rules" when it comes to User Interface / User Experience design.  One that I try to stick to is "Don't let users do things they can't do."

It's one of my gripes with Linux.  If you're editing a configuration file, you are relying on yourself to sanity check your input - often without knowing what the limits are.

Take these two different examples.

In a text file, we might have:

#Maximum Widgets to fidget
maxW_to-F = 0

Whereas a GUI would show

How many Widgets do you want to fidget?

Even if you don't know the rules behind Widget fidgetting (must be a prime number lower than 7), the GUI won't let you choose a value that you can't select. The GUI doesn't prevent you setting an innapropriate value - just an illegal one. Your config file, however, could be set to any crazy value that a user type - often resulting in "unpredictable" results.

#Maximum Widgets to fidget
maxW_to-F = seventeen

It's with this in mind that I've made the following change to Dabr - the mobile Twitter client.

To Auth or Not To Auth? That Is The Question

Twitter's API has bug / peculiarity (reported to their discussion board) which causes Dabr to log a user out. Let me explain the steps

  • User 1 (@private) has set her tweets to "protected".
  • This means no one can see @private's tweet unless she allows them.
  • @private has not allowed User 2 (@edent) to view her tweets.  She is protect from his view.
  • @edent clicks to view @private's profile.
  • @edent can see that @private has 42 friends, 17 followers and 3 favourites.

So far, this is the same behaviour on Twitter's website as it is through their API.  Here's the difference

Web

  • @edent tries to see @private's followers and can see their names, profile pictures etc.
  • @edent can also see @private's friends
  • @edent cannot see @private's favourites (or even how many favourites she has)

API

  • @edent tries to see @private's followers, friends or favourites
  • Because @edent isn't allowed to see @private's info, the API returns 401 Authorisation Required.

This is where things get tricky. Dabr sees the 401 and concludes that the user has invalid credentials.  It then, as a security measure, clears the user's cookie and logs them out.

This may be a little harsh, but HTTP 401 essentially says that the authorisation has failed.

Fixing It

There are 3 ways that this could be fixed

  1. Twitter could rationalise the API to allow access to the same content that a web user gets.
  2. Twitter could return a different status code.
  3. Dabr needs fixing.

So, how do we get Dabr not to log out when it receives a 401 only under these specific circumstances?

Looking at the code, we can see that Dabr simply sees the HTTP response code.  To fix it we'll need to pass extra parameters, check where the code was called from, investigate all the edge cases, add more logic to the system, futz around breaking things, etc... etc...

What a pain in the...

Or

Don't let users do things they can't do.

If a user can't see the information - why do we even let them try to see the information?  Why can't we just get rid of the link?

This is what a user currently sees:

Old Style
Old Style

We've established that they can't view followers, friends and favourites.  So we can get rid of those links (but not the information).

New Style
New Style

(Incidentally, I've changed the order of the links.  I've tried to group together similar items.  Followers, friends, favourites and lists go together. Then DM. Finally, follow, block, report spam.)

Now a user cannot click through to an unwanted error message.

Or

There is another way round this.  With "Direct Messages" we could do the same thing - simply remove the link if you're not able to send that user a DM.

Instead, we've taken the approach of displaying a suitable error message.

Direct Message Warning
Direct Message Warning

The advantage of this is that the user gets an explanation as to why they are unable to complete an action.

Your Thought?

Which do you prefer? Being unable to click on a link (with no explanation) or clicking on a link only to be given a warning message?