Tuesday, January 19, 2010

Extending Contacts Gem to fetch LinkedIn contacts to invite

Deprecated owing to captcha fixation!

Prepare:

Getting contacts gem to play as plugin make it easier to manipulate accordingly.
Do whatever way or find a simple cut & paste like solution here...
Extending Ruby on Rails Contacts Gem to invite Facebook users
Put linkedin.rb into "#{RAILS_ROOT}/vendor/plugins/contacts/lib/contacts"[Plugin] OR "#{GEM_PATH}/contacts-x.x.x/lib/contacts" containing...
class Contacts
  class Linkedin < Base
    URL = "http://linkedin.com"
    LOGIN_URL = "https://www.linkedin.com/secure/login?trk=hb_signin"
    LOGIN_POST_URL = "https://www.linkedin.com/secure/login"
    HOME_URL = "http://www.linkedin.com/home"
    CONTACT_URL = "http://www.linkedin.com/dwr/exec/ConnectionsBrowserService.getMyConnections.dwr"
    REFERER_URL = "http://www.linkedin.com/connections?trk=hb_side_cnts"
    PROTOCOL_ERROR = "LinkedIn has changed its protocols, please upgrade this library."
    
    def real_connect
      data, resp, cookies, forward = get(LOGIN_URL)
      
      if resp.code_type != Net::HTTPOK
        raise ConnectionError, PROTOCOL_ERROR
      end
      
      postdata = "csrfToken=guest_token&session_key=#{@login}&session_password=#{@password}&session_login=Sign In&session_login=&session_rikey="
      
      data, resp, cookies, forward = post(LOGIN_POST_URL, postdata, cookies)
      
      if data.index("The email address or password you provided does not match our records.")
        raise AuthenticationError, "Username and password do not match!"
      elsif data.index('Please enter your password') || data.index('Please enter your email address.')
       raise AuthenticationError, "Email or Password can not be blank!"
      elsif cookies == ""
        raise ConnectionError, PROTOCOL_ERROR
      end
      
      data, resp, cookies, forward = get(HOME_URL, cookies, URL)
      
      if resp.code_type != Net::HTTPOK
        raise ConnectionError, PROTOCOL_ERROR
      end
      
      @ajax_session = get_element_string(data,'name="csrfToken" value="','"')
      @logout_url = "https://www.linkedin.com/secure/login?session_full_logout=&trk=hb_signout"
      @cookies = cookies
    end
    
    def contacts      
   raise ConnectionError, PROTOCOL_ERROR unless @ajax_session
   
   postdata = "callCount=1"
   postdata+= "&JSESSIONID=#{@ajax_session}"
   postdata+= "&c0-scriptName=ConnectionsBrowserService"
   postdata+= "&c0-methodName=getMyConnections"
   postdata+= "&c0-param0=string:0"
   postdata+= "&c0-param1=number:-1"
   postdata+= "&c0-param2=string:DONT_CARE"
   postdata+= "&c0-param3=number:500"
   postdata+= "&c0-param4=boolean:false"
   postdata+= "&c0-param5=boolean:true"
   postdata+= "&xml=true"
   
   data, resp, cookies, forward = ajaxpost(CONTACT_URL, postdata, @cookies, REFERER_URL)
   raise ConnectionError, PROTOCOL_ERROR if resp.code_type != Net::HTTPOK
   cr = /detailsLink=s\d+;(.*?)\.firstName=/
   fr = /emailAddress=s\d+;var s\d+=\"(.*?)\";s\d+/
   er =  /var s\d+=\"(.*?)\";s\d+.emailAddress/
   
   contacts = []
   results = data.scan(cr)
   results.each do |result|
    result = result.to_s
    first_name = result.scan(fr).to_s
    email = result.scan(er).to_s
    contacts << [first_name, email]
   end
   
   logout
   return contacts
    end
    
    def logout
     return false unless @ajax_session
     if @logout_url && @cookies
      url = URI.parse(@logout_url)
       http = open_http(url)
       http.get("#{url.path}?#{url.query}",
        "Cookie" => @cookies
        )
        return true
     end
    end
    
    private
    
    def ajaxpost(url, postdata, cookies="", referer="")
   url = URI.parse(url)
   http = open_http(url)
   resp, data = http.post(url.path, postdata,
     "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
     "Accept-Encoding" => "gzip",
     "Cookie" => cookies,
     "Referer" => referer,
     "Content-Type" => 'text/plain;charset=UTF-8'
   )
   data = uncompress(resp, data)
   cookies = parse_cookies(resp.response['set-cookie'], cookies)
   forward = resp.response['Location']
   forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
   if(not forward.nil?) && URI.parse(forward).host.nil?
    forward = url.scheme.to_s + "://" + url.host.to_s + forward
   end
   return data, resp, cookies, forward
    end
    
  end
  TYPES[:linkedin] = Linkedin
end
Require linkedin.rb into lib/contacts.rb
require 'linkedin'

Is it working?

Console>> Contacts::Linkedin.new('linkedin-id','password').contacts
=> [["User1", 'Email1'], ["User2", 'Email2'], ["User3", 'Email3']]
Yes! :)

Twitter API And Extending Rails Contacts Gem To Invite Friends

Introduction:

Yet to install contacts gem?
OR, how to get it to play as plugin?
Follow this link...
Extending Ruby on Rails Contacts Gem to invite Facebook users
Got through?
Put twitter.rb into "#{RAILS_ROOT}/vendor/plugins/contacts/lib/contacts" containing...
class Contacts
 class Twitter < Base
  
  URL = "http://twitter.com"
  LOGIN_URL = "http://twitter.com/account/verify_credentials."
  ONTACT_URL = "http://twitter.com/statuses/friends."
  DMESSAGE_URL = "http://twitter.com/direct_messages/new."
  LOGOUT_URL = "http://twitter.com/account/end_session."
  PROTOCOL_ERROR = "Twitter has changed its protocols, please upgrade this library."
    
  def real_connect
   format = 'xml'
   api_url = LOGIN_URL + format
   url = URI.parse(api_url)
   req = Net::HTTP::Get.new(url.path)
   req.basic_auth(@login, @password)
   resp = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
     
   if resp.code_type == Net::HTTPUnauthorized
    raise AuthenticationError, "Username and password do not match!"
   elsif resp.code_type != Net::HTTPOK
    raise ConnectionError, PROTOCOL_ERROR
   end
     
   return true
  end
   
  def contacts
   format = 'json'
   api_url = CONTACT_URL + format
   url = URI.parse(api_url)
   req = Net::HTTP::Get.new(url.path)
   req.basic_auth(@login, @password)
   resp = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
     
   if resp.code_type != Net::HTTPOK
    raise ConnectionError, PROTOCOL_ERROR
   end
      
   data = resp.body
   data = Contacts.parse_json(data) if data
   return [] if data.empty?
   contacts = []
   data.each do |d|
    contacts << [d['name'],d['id']]
   end
   return contacts
      
  end
   
  def send_message(contact, msg, format = 'json')
   return "Direct Message must been less than 140 characters." if msg && msg.length > 160
   return "Direct Message must have something in it..." if msg.nil? || msg.length < 1
     
   api_url = DMESSAGE_URL + format
   url = URI.parse(api_url)
   req = Net::HTTP::Post.new(url.path)
   req.basic_auth(@login, @password)
   req.set_form_data({'user' => contact.last, 'text'=> msg }, '&')
   resp = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) }
   return true if resp.code_type == Net::HTTPOK
   return false
  end
   
  def logout
   #...
  end
   
 end
 TYPES[:twitter] = Twitter
end
We also need to pick appropriate JSON, if we are getting contacts in json format.
# Use ActiveSupport's version of JSON if available
if !Object.const_defined?('ActiveSupport')
  require 'json/add/rails'
end

class Contacts
  def self.parse_json(string)
    if Object.const_defined?('ActiveSupport') && ActiveSupport.const_defined?('JSON')
      ActiveSupport::JSON.decode(string)
    elsif Object.const_defined?('JSON')
      JSON.parse(string)
    else
      raise 'Contacts requires JSON or Rails (with ActiveSupport::JSON)'
    end
  end
end
Don't forget to add this into lib/contacts.rb
require 'twitter'
Done!

Usage

C:\Me\Workspace\contacts>ruby script/console
Loading development environment (Rails 2.3.4)
>> twitter=Contacts::Twitter.new('twitter-id','password')
...
...
>> contacts = twitter.contacts
=> [["Jeremy Piven", 20221159], ["The Next Web", 10876852], ["bob saget", 38536306], ["Joel Stein", 24608680], ["Moin Haidar", 38841828]]
>> contacts.each{|c|twitter.send_message(c, 'Your Message')}
All should be well :)