Instagr.am Picture Previews with Ruby

In my previous post I discusses how to get image previews from TwitPic, yfrog and plixi. Getting preview pictures from instagr.am is slightly harder. It is worth the extra effort however as instagr.am is popular at the moment and the pictures tend to be better than the other services as people take more care with what they post.

Getting the picture preview url

Currently there is no official/supported API for this so I referred to mislav’s unofficial API documentation and ruby library. In order to get the picture preview URL you need an instagr.am page link:
http://instagr.am/p/BT7lU/
. You then make a GET request to the instagr.am API using the page link as a parameter and specifying an additional maxwidth parameter: http://instagr.am/api/v1/oembed/?url=http://instagr.am/p/BT7lU/&maxwidth=150.

This returns the following JSON:

{
	"provider_url": "http://instagr.am/", 
	"title": "Cupcake!", 
	"url": "http://distillery.s3.amazonaws.com/media/2011/01/31/ad710c1abd964cbd8f89e09cc7c76eab_5.jpg",
 	"author_name": "andrewnez", 
	"height": 150, 
	"width": 150, 
	"version": "1.0", 
	"author_url": "http://instagr.am/", 
	"provider_name": "Instagram", 
	"type": "photo"
}

The url field of this JSON document contains a direct link to the image preview:
instagr.am picture preview

You can get a link to a larger image by passing a larger value to the maxwidth parameter such as http://instagr.am/api/v1/oembed/?url=http://instagr.am/p/BT7lU/&maxwidth=650.

Here is the ruby code to do this:

def instagram_image_url(url)
    json = get_url(URI.parse("http://instagr.am/api/v1/oembed/?url=#{url}&maxwidth=200"))
    doc = JSON.parse(json)
    doc["url"]
end

# Plundered from https://github.com/mislav/instagram/blob/master/lib/instagram.rb
def get_url(url)
	response = Net::HTTP.start(url.host, url.port) { |http|
	  http.get url.request_uri, 'User-agent' => 'http://www.whoeveryourare.com'
	}

	if Net::HTTPSuccess === response
	  response.body
	else
	  response.error!
	end
end

Performance Issues when Querying the API

Querying the instagr.am API as you are processing a twitter stream can mean a lot of (probably slow) requests to external servers. If you want your webpage to respond quickly this isn’t ideal. A simple workaround is to use a redirection URL on your site. As you process the twitter stream you extract a unique ID for each picture. The redirection URL then takes that ID, makes the JSON request and redirects the browser to the appropriate picture.

This redirect code is pretty straight-forward, here is a sinatra based example

get "/" do
	instagram_preview_html("http://instagr.am/p/BT7lU/")
end

get "/instagram/:id" do
  redirect instagram_image_url(params[:id]), 301
end

def instagram_preview_html(url)
	id = url.split("/").last
	"<a href=\"#{url}\"><img src=\"/instagram/#{id}\" title=\"#{text}\"/></a>"
end

def instagram_image_url(id)
	url = "http://instagr.am/p/#{id}"
	json = get_url(URI.parse("http://instagr.am/api/v1/oembed/?url=#{url}&maxwidth=200"))
	doc = JSON.parse(json)
	doc["url"]
end

The techniques for other services with Restful APIs, such as Flickr are similar. It would be great if Instagr.am followed the lead of other picture services and made their pictures directly addressable and hackable based on their URLs without having to use the JSON API. Hopefully as their web presence grow they will do so.

Twitter Picture Previews with Ruby

As part of a pet project (coming soon!) I have been investigating extracting previews (thumbnails) of pictures from services such as TwitPic, yfrog and plixi in Twitter feeds. Last week I added this feature to the Forward technology website. It is a neat feature as it adds some visual spice to the page. It also gives some insight into what we get up to at work (mostly being geeky and drinking too much).

The hard(ish) bit

What makes this process a little bit awkward is the sheer number of different twitter picture services that are out there. I decided to handle the most popular amongst the people I follow: TwitPic, yfrog and plixi. None of these services share a common interface but after a bit of Googling (this article was especially helpful) and digging around the API documentation (TwitPic API page, yfrog API page and plixi API page) I came up with the following translation:

Service Picture page URL Picture preview URL Example page URL Picture preview
TwitPic http://twitpic.com/{id} http://twitpic.com/show/thumb/{id}.jpg http://twitpic.com/ibhev
http://twitpic.com/show/thumb/ibhev.jpg
yfrog http://yfrog.com/{id} http://yfrog.com/{id}:small http://yfrog.com/bggccwj
http://yfrog.com/bggccwj:small
Plixi http://plixi.com/p/24330722 http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=The picture page url http://plixi.com/p/24330722
http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=http://plixi.com/p/24330722

Generally the link to the picture page will contain a unique ID that identifies the picture. This ID is easy to extract and use to build the URL to the image preview.

The easy bit

After figuring out the how the URL the code is pretty straightforward. I use the Twitter gem to search for tweets with links from the user on my timeline and then build a set of image previews.

require 'twitter'
require "yajl"

class TwitterPictures

  class TwitpicAdapter
    def self.match?(url)
      !(url =~ /twitpic\.com/).nil?
    end

    def self.html(url, text)
      uri = URI.parse(url)
      id = uri.path
      "<a href="\&quot;#{url}\&quot;"><img title="\&quot;#{text}\&quot;/" src="\&quot;http://twitpic.com/show/thumb/#{id}.jpg\&quot;" alt="" /></a>"
    end
  end

  class YfrogAdapter
    def self.match?(url)
      !(url =~ /yfrog\.com/).nil?
    end

    def self.html(url, text)
      uri = URI.parse(url)
      id = uri.path
      "<a href="\&quot;#{url}\&quot;"><img title="\&quot;#{text}\&quot;/" src="\&quot;http://yfrog.com#{id}:small\&quot;" alt="" /></a>"
    end
  end

  class PlixiAdapter
    def self.match?(url)
      !(url =~ /plixi\.com/).nil?
    end

    def self.html(url, text)
      "<a href="\&quot;#{url}\&quot;"><img title="\&quot;#{text}\&quot;" src="\&quot;http://api.plixi.com/api/tpapi.svc/imagefromurl?size=small&url=#{url}\&quot;" alt="" /></a>"
    end
  end

  def self.for_users(users)
    tweets = fetch_tweets(users)
    build_pictures_html(tweets)
  end

  private

    def self.extract_urls(text, urls = [])
      url_regex =  Regexp.new('\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))')
      url_regex.match(text) do |match|
        urls << match[0]
        extract_urls(match.post_match, urls)
      end
      urls
    end

    def self.build_pictures_html(tweets)
      pictures = {}
      tweets.each do |t|
        pictures_for_text = build_picture_html("from @#{t.from_user}: #{t.text}")
        pictures.merge!(pictures_for_text)
      end
      pictures.values
    end

    def self.fetch_tweets(users)
      @client = Twitter::Search.new
      users_query = users.join(' OR ')
      query = "http (#{users_query})"
      @client.containing(query).per_page(200)
    end

    def self.build_picture_html(text)
      adapters = [TwitpicAdapter, PlixiAdapter, YfrogAdapter]
      pictures = {}
      urls = extract_urls(text)
      urls.each do |url|
        adapters.each do |adapter|
          pictures[url] = adapter.html(url, text) if adapter.match? url
        end
      end
      pictures
    end
end

An example is on github if you want to read more.

More hard bits

Extracting instagr.am preview pictures is a little bit trickier. I will talk about how to do that in an upcoming blog post.