How to Handle Facebook App Uninstalls with RFacebook

Posted on February 20, 2008

I posted the following here—but thought I’d solicit other feedback via blog as well.

As of this writing, the rfacebook plugin does not seem to provide a way to handle uninstalls (easily).

Is there a method I’m not aware of? Or does one handle it pretty much how I’ve done below?

In this example, to handle uninstalls in your rails app we will use the post-remove URL of ‘foo.yourapp.com/uninstalled’.

In your application.rb, you probably have something like:

  before_filter :require_facebook_install
  before_filter :require_facebook_login

Change your application.rb to something like:

  before_filter :require_facebook_install, :except => [:uninstalled]
  before_filter :require_facebook_login, :except => [:uninstalled]

  # Before Filter on *only* the 'uninstalled' method
  before_filter :verify_uninstall_signature, :only => [:uninstalled]

  # Note: it's important this method is *above* the 'protected' definition, since it needs to be called directly
  def uninstalled
    @fb_uid = params[:fb_sig_user]
    # From here on it will be app specific -- given the facebook uid, destroy the user, like...
    @user = User.find_by_fb_uid(@fb_uid)
    @user.destroy if @user
    render :nothing => true; return
  end

  protected
   ...

Next, in your ‘protected’ section, add the following method which roughly corresponds to the PHP / pseudocode:

  def verify_uninstall_signature
    signature = ''
    keys = params.keys.sort
    keys.each do |key|
      next if key == 'fb_sig'
      next unless key.include?('fb_sig')
      key_name = key.gsub('fb_sig_', '')
      signature += key_name
      signature += '='
      signature += params[key]
    end
    signature += FACEBOOK['secret']
    calculated_sig = Digest::MD5.hexdigest(signature)
    #logger.info "\nUNINSTALL :: Signature (fb_sig param from facebook) :: #{params[:fb_sig]}" 
    #logger.info "\nUNINSTALL :: Signature String (pre-hash) :: #{signature}" 
    #logger.info "\nUNINSTALL :: MD5 Hashed Sig :: #{calculated_sig}" 
    if calculated_sig != params[:fb_sig]
      #logger.warn "\n\nUNINSTALL :: WARNING :: expected signatures did not match\n\n" 
      return false
    else
      #logger.warn "\n\nUNINSTALL :: SUCCESS!! Signatures matched.\n" 
    end
    return true
  end

This might be handy to add to ‘rfacebook’, but it sounds like the state of that project is in flux.

Also, you might have to add this entry to your config/routes.rb file:

  map.connect 'uninstalled', :controller => 'application', :action => 'uninstalled'

Happy uninstalling! :)

Tinkering with Git + Quick Facebook App Template

Posted on February 17, 2008

I just got my new GitHub account – yay!

Time to tinker. One thing I’ve done with rails is create a “QuickStart Rails” template that I have checked into svn. It’s got several useful plugins that I use in just about all my rails apps, plus a working authentication system including all the boring, trivial details that come with that like ‘Forgot Password?’ functionality, etc.

Not all apps get the QuickStart rails treatment, but if they involve user authentication, I usually bootstrap the app using QS.

Recently I’ve been working on a Facebook app; following its development, I’ve made a Quick FB rails application template.

Again, this does all of the mundane things that all Facebook apps will require, like an Invitation page:

Facebook | Maven (Development)


If you’re interested, you can browse the git repository online.

To bootstrap a new Quick FB app (“fb_foo”) using git, do a:

git clone git://github.com/sbraford/quick-fb.git fb_foo

This will give you a complete Rails 2.0 application structure, plus:

  • rfacebook, will_paginate plugins already added
  • application.rb configured for you
  • User, Friendship models already created—as users add the application, they are automatically associated as Friends within your app
  • Invitation widget (like shown above) already included
See config/environment.rb for the list of TODOs once you have cloned the Quick FB git repo:

TODO after cloning the quick-fb git repo:

  1. Create your app's database.  Modify 'config/database.yml'.

  2. Open 'config/facebook.yml' and modify the following:

      key: YOUR_API_KEY_HERE
      secret: YOUR_API_SECRET_HERE
      canvas_path: /yourAppName/
      callback_path: /relative_root/
      sever_host: foo.com
      app_name: Foo App

  3. Modify the relative URL root below.

  4. Open up 'app/helpers/application_helper.rb' and modify the invitation text.

  5. Generate a new secret app key with:

        rake secret

  Copy & paste this into the session config section above. (this might not be necessary for fb apps, but just in case)

Note: to port this over to SVN, I believe you can simply do a:

  rm -rf .git/
  rm .gitignore  # and any other .gitignore files

... and you should be good to go. (copy to an existing svn repo and svn add/import as usual)

FSX Trader Broken due to Facebook Login Changes

Posted on January 01, 2008

Facebook obviously does not want people writing bots that log into (facebook) accounts. Clearly, automation in this area could have potentially nefarious goals (i.e. see MySpace bulletin/comment spamming, etc).

This is ironic though (or hypocritical), since Facebook built much of its user acquisition on this exact same technique via its GMail, Hotmail and Yahoo contact import scripts.

Even though my FSX Trader library is for personal use only, it has since become broken due to changes in the Facebook login mechanism.

The FSX Trader lib currently uses ruby’s WWW::Mechanize library which might not have as good of JavaScript support.

One alternative would be to rewrite the library to use FireWatir or Watir which actually drive the browser.

To my knowledge, without using a CAPTCHA, there is no way to defeat an automated login system that actually drives the browser as FireWatir and Watir do.

HOW TO: Automate Facebook Interaction using Ruby and WWW::Mechanize

Posted on September 05, 2007

I told those fudgepackers that I liked Michael Bolton’s music. – Michael Bolton, Office Space

Overview

By the end of this tutorial, you will have learned how to use Ruby and WWW::Mechanize to login to your Facebook account and post a random Office Space quote on your friend’s wall!

Origins of this Tutorial

I first got interested in automating Facebook actions due to limitations in the the Fantasy Stock Exchange app on Facebook—I wanted a way to automate stop loss orders like a real brokerage firm might offer.

See this post on FSX Trader for more on that project, which implements the same techniques in this tutorial and more. You can download its source code here on rubyforge. Now onto the tutorial:

Autobots: Mechanize!

First you’ll need the mechanize ruby gem:

sudo gem install mechanize

Next create a new file called mechanize_ruby.rb. First we’ll need to make available the mechanize library:

require 'rubygems'
require 'mechanize'

For this brief example, let’s create a MechanizeFacebook class that will login to Facebook. It will take two paramaters, an auth Hash and boolean telling it whether or not to be verbose in its output:

class MechanizeFacebook
  def initialize(auth, verbose = false)
    @auth = auth
    @verbose = verbose
    @agent = WWW::Mechanize.new
    @agent.user_agent_alias = 'Mac FireFox'
    @agent.redirect_ok = true
  end

  def login
  end
end

auth = {'email' => 'my-fb-email@gmail.com', 'pass' => 'my-fb-password'}
mf = MechanizeFacebook.new(auth,  true)
mf.login

Above you can see we’ve instantiated a new WWW::Mechanize object, set its user agent alias and redirect_ok to true. For more on user agent aliases available to Mechanize (it can masquerade as IE on Windows, Mac Safari, etc) see its documentation here on rubyforge.

Change the ‘auth’ hash credentials to your own if you’d like to tinker with this on your machine.

Now let’s add the login functionality.

To get a page via WWW::Mechanize and print its HTML/form/link/etc data, we can now do add this to our ‘login’ stub above:

page = @agent.get('http://www.facebook.com/')
pp page if @verbose

If @verbose is true, this will output something like:

#<WWW::Mechanize::Page
 {url #<URI::HTTP:0xa629c6 URL:http://www.facebook.com/>}
 {meta}
 {title "Facebook | Welcome to Facebook!"}
    ..
 {links
  #<WWW::Mechanize::Link " " "http://www.facebook.com">
    #<WWW::Mechanize::Link
   "Forgot Password?" 
   "http://www.facebook.com/reset.php">
  #<WWW::Mechanize::Link "Login" "http://www.facebook.com/login.php">
    ...

Next let’s grab the login form. In this particular case, it happens to be the first form:

login_form = page.forms.first

If it were the second form on the page, we could do:

"page.forms[1]"

Time to set some form variables:

login_form.email = @auth['email']
login_form.pass = @auth['pass']
pp login_form if @verbose
If everything is peachy at this point, the output here should be:
#<WWW::Mechanize::Form
 {name "loginform"}
 {method "POST"}
 {action "https://login.facebook.com/login.php"}
 {fields
    ...
    #<WWW::Mechanize::Field:0x1433590
   @name="email",
   @value="your-fb-email@gmail.com">
  #<WWW::Mechanize::Field:0x1432dd4 @name="noerror", @value="1">
  #<WWW::Mechanize::Field:0x1432618 @name="pass", @value="your-fb-pass">}
Now let’s submit this bad boy:
page = @agent.submit(login_form)
pp page if @verbose

The output from this should yield your main personal Facebook homepage (activity feed links, personal app links, etc).

Getting Sneaky

What we’ve done so far has been an interesting learning tool but relatively straightforward.

Let’s combine a few steps and post a random quote from Office Space to one of your friend’s walls (or your own wall, but that’s not nearly as fun, eh?). Example of the resulting wallpost:

First, see this article on how to parse Office Space quotes off of WikiQuote.org. The “randomquote.rb” file from that project is included in randposter.zip file. Let’s break down what our new post method in mechanize_facebook.rb will do:

friends_profile_url = FRIEND_PROFILE_URL
page = @agent.get(friends_profile_url)
pp page if @verbose

Now let’s grab some quotes and instantiate a quote factory :

qg = QuotationGrabber.new('Office_Space', 10)
rq = RandomQuote.new('Office_Space')

The following assumes your friend’s Wall form is the last form on their profile page. This is normally true unless they’ve heavily customized their profile :

# Get the post to Wall form
wall_form = page.forms.last
# Set the wall message post value to the following:
wall_form.text = "Random Office Space quote of the day:\n\n#{rq.random_quote}"

Next we inspect the output of the form to make sure it’s doing what we expect, then submit the form!

pp wall_form if @verbose
# Submit the form!
@agent.submit(wall_form)

Now if you log into Facebook and look at your buddy’s wall—your random Office Space quote should be listed at the top.