In: , , , ,
On: 2010 / 05 / 07
Shorter URL for this post: http://ozh.in/rv

A few days ago, Twitter published a new tool that lets you embed a tweet on your site, simplifying the old school way: take a screenshot, crop the picture, upload it, embed it.

The problem is: this Twitter tool is way too lame. Basically you need to cut and paste a lengthy code snippet full of HTML and CSS. How ugly. Compared to the way you can embed a Youtube video in WordPress (just pasting a video URL, on its own line) this is seriously cumbersome.

Hey, wouldn't it be cool if you can embed a tweet just as easily? You would just paste its URL and let WordPress transforms it into a nicely presented tweet with links and everything? OK, let's do a plugin for this. And guess what, it's going to be fairly easy, using WordPress' oEmbed API implementation.


What is oEmbed

When you paste a Youtube video URL, raw (not hyperlinked) and on its own line, WordPress transforms it into an embedded video. Why? Basically, there's something in WordPress that says "every time you see a URL like http://www.youtube.com/watch?v=*, ask http://www.youtube.com/oembed what to do with it".

In short, oEmbed is a simple API that lets you define a URL pattern (used by a consumer) and the URL of a script (the provider) that will generate a clean HTLM out of it.

Twitter does not support (yet) oEmbed, so we will have to handle this on our own: transform a WordPress blog into a oEmbed provider.

Register your blog as an oEmbed provider

First, let's register our blog as an oEmbed provider, to replace all URLs like http://twitter.com/ozh/status/13156561 into a cool piece of HTML. If you can breathe and walk at the same time, you're qualified to do this: all we need is a single function call. The function to be used here is wp_oembed_add_provider( $pattern, $url_of_service).

  1. // Return the internal URL that will handle the oEmbed requests (ie yourblog.com/blog/?ozh_tweet=1)
  2. function ozh_tweetembed_internal_oembed_url() {
  3.     return trailingslashit( home_url() ) . '?ozh_tweet=1';
  4. }
  5.  
  6. // Register this internal URL as an oEmbed provider for twitter URLs
  7. function ozh_tweetembed_add_oembed_provider() {
  8.     // http://twitter.com/ozh/statuses/1234567
  9.     wp_oembed_add_provider( 'http://twitter.com/*/status/*', ozh_tweetembed_internal_oembed_url() );
  10. }
  11. add_action( 'init', 'ozh_tweetembed_add_oembed_provider' );

OK, so far, we're telling WP that everytime something looks like http://twitter.com/*/status/*, it will have to ask yourblog.com/blog/?ozh_tweet=1 what to do with it.

Catch URLs that will contain ?ozh_tweet=1

Since we're now expecting WP to do something particular when called with ?ozh_tweet=1, we have to hijack this URL:

  1. // Register 'ozh_tweet' as a query var so we can check if ?ozh_tweet=1
  2. function ozh_tweetembed_add_tweet_query_var( $query_vars ) {
  3.     $query_vars[] = 'ozh_tweet';
  4.     return $query_vars;
  5. }
  6. add_filter('query_vars', 'ozh_tweetembed_add_tweet_query_var');
  7.  
  8. // As early as possible, check for query var 'ozh_tweet' and maybe shortcut everything
  9. function ozh_tweetembed_get_tweet_query_var() {
  10.     $tweet = get_query_var( 'ozh_tweet' );
  11.     if( $tweet ) {
  12.         // Make something with the oEmbed request
  13.         ozh_tweetembed_handle_oembed_request();
  14.         // Now die, we don't want a blog page
  15.         die();
  16.     }
  17. }
  18. add_action( 'template_redirect', 'ozh_tweetembed_get_tweet_query_var' );

So, at this point, every time WP sees a line matching http://twitter.com/*/status/*, it will ask yourblog.com/blog/?ozh_tweet=1&url=the_tweet_url&some_other_var=... what to do with it. So, what to do with it?

Handling the oEmbed request itself

Now for the fun part: the function that handles the oEmbed request and return the expected formatted output.

Basically, we're going to:

  1. Guess the tweet id
  2. Poll the Twitter API for information about this tweet, get a JSON response
  3. Parse the JSON response, make something pretty with this it
  4. Return that pretty thing into a JSON response

Vamos, muchachos:

  1. // Handle the oEmbed requests
  2. // The oEmbed request is something like:
  3. // yourblog.com/blog/?ozh_tweet=1&url=http://twitter.com/ozh/statuses/123456&...
  4. //
  5. function ozh_tweetembed_handle_oembed_request() {
  6.     $url = esc_url( $_GET['url'] );
  7.     if( !$url )
  8.         die('URL missing');
  9.    
  10.     preg_match( '!http://twitter.com/([^/]+)/status/(\d+)!', $url, $matches );
  11.     $author  = $matches[1];
  12.     $tweetid = $matches[2];
  13.     unset( $matches );
  14.    
  15.     // From this point, we fetch content from Twitter and print a JSON string.
  16.     // If something goes wrong (like, Twitter unreachable) then we'll simply print anything but JSON
  17.     // and let WP handle the response.
  18.    
  19.     // fetch http://api.twitter.com/1/statuses/show/$tweet.json
  20.     $apiurl = 'http://api.twitter.com/1/statuses/show/'.$tweetid.'.json';
  21.     if ( !$result = wp_remote_retrieve_body( wp_remote_get( $apiurl, array('timeout' => 15) ) ) )
  22.         die( "could not fetch $apiurl" );
  23.  
  24.     // Check that JSON is well formed
  25.     $result = trim( $result );
  26.     if ( !$data = json_decode($result) )
  27.         die( "Data was not JSON" );
  28.    
  29.     // Now extract a few variables from the $data object
  30.     $text = $data->text;
  31.     $created_at = date('d M Y g:i a', strtotime( $data->created_at ) );
  32.     if( !empty($data->source) )
  33.         $source = 'via '.$data->source;
  34.     if( !empty($data->in_reply_to_screen_name) ) {
  35.         $in_reply_to_screen_name = $data->in_reply_to_screen_name;
  36.         $in_reply_to_status_id   = $data->in_reply_to_status_id;
  37.         $inreply = "in reply to <a href='http://twitter.com/$in_reply_to_screen_name/status/$in_reply_to_status_id'>@$in_reply_to_screen_name</a>";
  38.     }
  39.     $name = $data->user->name;
  40.     $screen_name = $data->user->screen_name;
  41.     $profile_image = $data->user->profile_image_url;
  42.    
  43.     // Make up some HTML with the tweet info
  44.     $html = <<<HTML
  45. <div class="tweet">
  46.     <div class="text">$text</div>
  47.     <div class="meta">$created_at $source $inreply</div>
  48.     <hr/>
  49.     <div class="pic"><a href="http://twitter.com/$screen_name"><img src="$profile_image">$name</a></div>
  50. </div>
  51. HTML;
  52.  
  53.     // Put everything into a regular array
  54.     $json = array(
  55.         'version' => '1.0',
  56.         'type'    => 'rich',
  57.         'width'   => 1337, // this parameter is mandatory according to the oEmbed specs, but we're not using it
  58.         'height'  => 1337, // same.
  59.         'html'    => trim( $html ),
  60.     );
  61.    
  62.     // Now output a JSON response  
  63.     header('Cache-Control: no-cache, must-revalidate');
  64.     header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
  65.     header('Content-type: application/json');
  66.     header('Content-Disposition: attachment; filename="oembed.json"');
  67.     echo json_encode( $json );
  68. }

And… That's it. Now just paste a tweet URL and let WordPress do the job. For your convenience, get the whole plugin we've just dissected in a single file

Download the plugin ozh-oembed-tweet.php.txt
(save as .php then put in your plugins folder)

Further improvements and remarks

This simple "proof of concept" plugin returns very basic HTML, you'd need to make it prettier (use the tweet background, CSS style it). Just modify the "$html =" starting at line 44 or, even cooler, add an interface to the plugin where you can define a template for the tweet output. If you want some inspiration, I do something like this in my plugin Better Feed where the user can customize feed item footer with a simple templating system.

When WordPress makes an oEmbed request, it does it once and stores its result in the postmeta table. Twitter has that feature we all know and don't care about any longer: unreliability. Notice the timeout parameter I've added to the remote request (line 21), this is really needed. Still, if you blog cannot fetch the tweet from Twitter's API, it will store a dummy postmeta entry ({{unknown}}) and won't replace the tweet URL. A proper plugin should test for the postmeta value and re-poll the Twitter API if applicable.

Twitter does not support oEmbed, but another site does it for them: Ooh Embed. So, technically, a plugin to embed tweets using the oEmbed API would require just three lines, register oohembed.com as a producer. But that wouldn't be fun :) (and I don't know how reliable is oohembed.com)

Technically again, you could use more efficiently function wp_embed_register_handler() to deal with the oEmbed request without having to sending an extra HTTP request to your own site. But that would probably make a confusing example of the general mechanism of the API, with a consumer sending a request to a producer.

Any other comment or idea of something cool to do with Twitter and oEmbed? Use the comment form belllowwww

Shorter URL

Want to share or tweet this post? Please use this short URL: http://ozh.in/rv

Metastuff

This entry "How to Embed a Tweet in WordPress: a Complete oEmbed Tutorial" was posted on 07/05/2010 at 11:21 pm and is tagged with , , , ,
Watch this discussion : Comments RSS 2.0.

14 Blablas

  1. scribu says:

    Pretty neat, but wouldn't it be faster to use wp_embed_register_handler() ?

    This way, you avoid making an extra HTTP request to your own site.

  2. Ozh says:

    scribu » yep, totally from the spare-a-request point of view, but I thought that would not be a good illustration for how oEmbed works (ie having a producer URL, on your site or somewhere else). Anyway, since the extra request occurs only once when submitting the post, this is not a big deal.

  3. Ozh says:

    scribu » anyway I'm adding your comment at the end of the tutorial, that's still a valid point

  4. chris says:

    nice code, I did see wpvibe released a plugin to display embedded tweets

    http://wpvibe.com/tweeted-tweets-from-twitter-212/

  5. Xavier says:

    If you don't wanna go through all this code (or simply if you don't have a self-hosted wordpress), I made a simple Bookmarklet to get the HTML code of a tweet.
    See it there: http://bit.ly/aL4QVG

  6. Thanks for sharing! I created a similar WordPress plugin a while ago which mimics the original Twitter-design a bit more. You can find it here: http://embedtweet.com/

    ( Includes some extra features as well, such as replying, retweeting and I'll add a special layout for the RSS feeds in an update this week. )

    I didn't know about oEmbed, so I'm probably using some redundant code though. How's your sample code licensed? If it's okay I might like to re-use some parts. If not, I won't :).

  7. Ozh says:

    Marc Köhlbrugge » everything on this site is free

  8. That's an amazing tutorial! As you said, the new twitter embed is lame. Everything about it is lame, but this really makes embedding tweets a reality. Thanks!

  9. det says:

    hi ozh!

    i'm getting a Fatal error: Call to undefined function home_url() in /pruebas/wp-content/plugins/ozh-oembed-tweet.php on line 13

    any ideas?

    i'm really looking forward to see how this works…

    thanks!

  10. Ozh says:

    det » that function is introduced in WP 3.0. For earlier version you can replace with get_option('home')

  11. det says:

    ok, thanks!

  12. Yellowcab says:

    When I activated the plugin, the system crash. I had to remove the php-file using ftp.

  13. GerryMedia says:

    Hi Ozh,

    How will this impact WordPress performance, specially on installs that have a lot of plugins already?

    Thanks for the code!

    Gerry

  14. Ozh says:

    GerryMedia » no impact

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>
Gravatars: Curious about the little images next to each commenter's name ? Go to Gravatar and sign for a free account
Spam: Various spam plugins may be activated. I'll put pins in a Voodoo doll if you spam me.

Read more ?