Add FriendFeed comments and likes to WordPress.com posts using Ruby

The problem
FriendFeed aggregates your blog posts from WordPress.com. Naturally, people prefer to comment on your post at FriendFeed – it’s quicker, easier and more fun. However, you would like to see an indication of this activity back at the original blog post.

The solutions
You could self-host your blog using software from WordPress.org. This allows you to install plugins such as FriendFeed comments. But you’re at WordPress.com because you don’t want to self-host, right? So you just have to live with the absence of useful plugins. My advice: don’t try discussing issues like this one in the WordPress.com forums unless you’re the kind of person who enjoys comment threads at YouTube.

For intelligent, mature and constructive discussion go to FriendFeed of course, where Lars writes:

How hard would it be to make a web service that reads the RSS feed from you blog, accesses FriendFeed via the API, identifies comments on FriendFeed related to your blog posts, and reposts them on your blog? If you want to, you could also keep track of “likes”…

Lets find out – using Ruby!

Disclaimer
I am a Ruby novice. This is rough and ready code, without tests or adequate error checking. Constructive and polite suggestions for improvement are welcome.

Also, this is not a complete, working solution :-)

For the impatient
To see the results, take a look at the comment by “FriendFeed Summary” on this blog post. For usage, scroll down this page.

Here’s what we want to achieve:

  1. Find the FriendFeed entries that correspond to our blog post
  2. Extract the FriendFeed comments and likes
  3. Post them back to WordPress.com as a comment

1. Find the FriendFeed entries that correspond to our blog post

require 'rubygems'
require 'mechanize'
require 'json'
require 'net/http'

def fetch_ff_entries(url)
  ffurl  = "http://friendfeed.com/api/feed/url?url=#{url}"
  resp   = Net::HTTP.get_response(URI.parse(ffurl))
  data   = resp.body
  result = JSON.parse(data)

   if result.has_key? 'errorCode'
      raise "Web service error"
   end

  return result
end

We define a method, fetch_ff_entries, which takes a URL (of the blog post) as an argument. It builds a second URL (ffurl) and calls the FriendFeed API to find all FriendFeed entries related to the blog post. These are returned in JSON format. JSON.parse() converts the output to a Ruby data structure, which is returned.

To get an idea of what this JSON looks like, try:

curl "http://friendfeed.com/api/feed/url?url=https://nsaunders.wordpress.com/2009/01/28/big-data-shoot-first-ask-questions-later/" > outfile

It looks complex – it is – but it’s basically a set of keys: id, title, link, comments etc. and values: user => name, user => profileURL and so on. See the FriendFeed API documentation for the details. On conversion to a Ruby data structure we get hash keys with values that may themselves be hashes, arrays, arrays of hashes, strings and so on.

On to the next step.

2. Extract the FriendFeed comments and likes

def ff_reactions_to_wp(ff,url)
  c = ""
  l = Array.new

  if url =~ /^(http:\/\/.*?)\//
    service = $1
    else
      raise "Does not look like a WP blog URL?"
    end

  ff['entries'].each {|entry|
    if entry['service']['profileUrl'] == service
# likes
      if entry['likes'].length > 0
        entry['likes'].each {|like|
          l << "<a href=\"#{like&#91;'user'&#93;&#91;'profileUrl'&#93;}\">#{like['user']['name']}</a>"
        }
      end
      c += "Liked by #{l.length} people: " + l[0..l.length-2].join(", ") +
           " and #{l[l.length-1]}" + "\n\n"
# comments
      if entry['comments'].length > 0
        entry['comments'].each {|comment|
          c += "<a href=\"#{comment&#91;'user'&#93;&#91;'profileUrl'&#93;}\">" +
               "#{comment['user']['name']}</a> said:\n" +
               "#{comment['body']}\n\n"
        }
      end
# ID and timestamp
      c += "\n<em>submitted using ff2wp: #{entry['id']} #{entry['updated']}</em>\n"
    end
  }
  return c
end

This method, ff_reactions_to_wp, takes two parameters: the parsed JSON output from the previous method and the blog post URL. First, it uses a regex to try and find the URL of the WordPress blog. It assumes that the root URL is contained between “http://&#8221; and the first “/”. This assumption is probably a very poor idea: it works for my blog, “nsaunders.wordpress.com” but would fail for e.g. “nsaunders.wordpress.com/blog/myposts/are/down/here”.

Second, the method loops through each entry in the JSON output and looks for the “service” key. If this matches the blog URL, we have found the aggregated blog post at FriendFeed. Note that there may be other FriendFeed items related to the blog post which are not from the blog – for example, if another user saves the post to del.icio.us.

Third, the method loops through the “likes” and the “comments”. For “likes”, an HTML-formatted string is generated by pushing user names and their FriendFeed URLs onto an array, then formatting the array elements. “Comments” are processed in a similar fashion (but an intermediate array is not required). The final comment contains the user name (linked to their profile) and the text of their comment.

Finally, a unique identifier is created using the “id” and “updated” keys from the JSON output. This can be used later on to check that we haven’t already posted this set of comments and likes.
update – the updated key doesn’t work as a timestamp; however, individual comments are dated

Put all that together and return the string (c).

3. Post them back to WordPress.com as a comment
To use these methods, you can try something like this:

usage = "Usage: ff2wp.rb WP.com_blog_post_URL"
url   = ARGV[0]

if url != nil
  if url =~ /^http:\/\/.*?/
    ff   = fetch_ff_entries(url)
    post = ff_reactions_to_wp(ff,url)
    puts post
    else
    puts usage
  end
  else
  puts usage
end

Save the whole lot as “ff2wp.rb” and run “ff2wp.rb WP_blog_post_URL” – obviously, replacing the argument with the URL of your blog post. The code above contains a cursory and inadequate check for an argument resembling a URL. If it works, you’ll see the text of the comments, all ready for posting to a WordPress blog post.

The next step would be automated posting of the comment – and this is where I stop.

Why? I have written the code for my own use but in its current state, it allows comments to be posted to any WordPress.com blog – not just your own. In other words, it’s an excellent tool for comment spam! I’m sure that comment spammers know all the automated posting tricks, but I don’t want to be responsible for contributing another one, in public. If you’d like to see the full working code, you’re a responsible individual and I know you, send me a message.

Next steps
The ideal solution to the comment spam issue would be to write a web interface around this code, forcing the user to log in and post comments to only their own WordPress blog. Might make a rather nice Rails project.

2 thoughts on “Add FriendFeed comments and likes to WordPress.com posts using Ruby

  1. FriendFeed Summary

    Liked by 2 people: Shirley Wu and Adam Kraut

    Shirley Wu said:
    Cool – I’m interested. But question: does this only produce a static snapshot of that post’s FF activity? Would you just run it periodically to update it, and delete the earlier ones?

    Neil Saunders said:
    Yes, if the time stamp at FriendFeed changes, it would appear as a new comment with original comments/likes + the new ones. So not ideal.

    submitted using ff2wp: db6363f2-7bbb-f5d1-316c-af8626a1b8e7 2009-02-06T05:26:35Z

  2. Mr. Gunn

    Just dropping you a note to let you know that if it ever gets to production quality, I’m interested in it. Maybe someone else can pick up making it a WP plug-in?

Comments are closed.