Skip to content

Commit

Permalink
move fetching Twitter/Facebook data into ServiceFetcher
Browse files Browse the repository at this point in the history
Handles being unauthorized by removing stored OAuth tokens.

Also improves fetching/refreshing Twitter picture.
  • Loading branch information
mislav committed Feb 4, 2012
1 parent 1c76bad commit 903090d
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 54 deletions.
75 changes: 75 additions & 0 deletions app/models/service_fetcher.rb
@@ -0,0 +1,75 @@
require 'net/http'
require 'json'
require 'oauth'
require 'oauth2'
require 'hashie/mash'

module ServiceFetcher

extend self

def twitter_config
Movies::Application.config.twitter
end

def twitter_client(token_values)
token, secret, = token_values
oauth = OAuth::Consumer.new twitter_config.consumer_key, twitter_config.secret,
site: 'https://api.twitter.com'
OAuth::AccessToken.new(oauth, token, secret)
end

def get_twitter_friends(token_values)
client = twitter_client(token_values)
response = client.get('/1/friends/ids.json')
process_json_response(response.body, response.code.to_i)
rescue => error
error
end

def facebook_config
Movies::Application.config.facebook
end

def facebook_client(token_values)
token, = token_values
oauth = OAuth2::Client.new facebook_config.app_id, facebook_config.secret,
site: 'https://graph.facebook.com',
raise_errors: false

OAuth2::AccessToken.new(oauth, token, mode: :query, param_name: 'access_token')
end

def get_facebook_info(token_values, params)
client = facebook_client(token_values)
response = client.get('/me', params: params)
process_json_response(response.body, response.status)
rescue => error
error
end

def get_twitter_profile_image(screen_name)
get_redirect_location "http://api.twitter.com/1/users/profile_image/#{screen_name}?size=bigger"
end

private

def process_json_response(body, status)
data = JSON.parse body rescue $!
data = Hashie::Mash.new data if data.is_a? Hash

data.singleton_class.class_eval "def status() #{status} end"
data
end

def get_redirect_location(url)
return nil if Movies.offline?
url = URI.parse url unless url.respond_to? :request_uri
response = Net::HTTP.start(url.host, open_timeout: 2) {|http| http.get url.request_uri }
response['location'] if response.is_a? Net::HTTPRedirection
rescue Timeout::Error
NeverForget.log($!, url: url)
return nil
end

end
101 changes: 47 additions & 54 deletions app/models/user/social.rb
@@ -1,8 +1,3 @@
require 'net/http'
require 'json'
require 'oauth'
require 'oauth2'

module User::Social
extend ActiveSupport::Concern

Expand Down Expand Up @@ -47,39 +42,45 @@ def refresh_social_connections

def fetch_twitter_friends
if self['twitter_token']
client = ::User::Social.twitter_client(self['twitter_token'])
response = client.get('/1/friends/ids.json')
ids_data = JSON.parse response.body
self.twitter_friends = ids_data['ids']
data = ServiceFetcher.get_twitter_friends(self['twitter_token'])
if data.respond_to? :status
case data.status
when 200
self.twitter_friends = data.ids
when 401
# the token seems no longer valid
self['twitter_token'] = nil
else
raise "unhandled status: #{data.status.inspect}"
end
elsif data.is_a? Exception
raise data
end
end
end

def self.twitter_client(token_values)
config = Movies::Application.config.twitter
token, secret, = token_values
oauth = OAuth::Consumer.new config.consumer_key, config.secret,
site: 'https://api.twitter.com'
OAuth::AccessToken.new(oauth, token, secret)
rescue StandardError
NeverForget.log($!, user_id: self.id)
end

def fetch_facebook_friends
if self['facebook_token']
client = ::User::Social.facebook_client(self['facebook_token'])
response = client.get('/me', params: {fields: 'friends'}) # 'movies,friends'
user_info = JSON.parse response.body
self.facebook_friends = user_info['friends']['data'].map { |f| f['id'] }
# watched.import_from_facebook user_info['movies']['data']
data = ServiceFetcher.get_facebook_info(self['facebook_token'], fields: 'friends')
if data.respond_to? :status
case data.status
when 200
self.facebook_friends = data.friends.data.map(&:id)
# watched.import_from_facebook data.movies.data
when 401
# the token seems no longer valid
self['facebook_token'] = nil
else
raise "unhandled status: #{data.status.inspect}"
end
elsif data.is_a? Exception
raise data
end
end
end

def self.facebook_client(token_values)
config = Movies::Application.config.facebook
token, = token_values
oauth = OAuth2::Client.new config.app_id, config.secret,
site: 'https://graph.facebook.com',
token_url: '/oauth/access_token'

OAuth2::AccessToken.new(oauth, token, mode: :query, param_name: 'access_token')
rescue StandardError
NeverForget.log($!, user_id: self.id)
end

def twitter_url
Expand All @@ -88,15 +89,20 @@ def twitter_url

# 73x73 px
def twitter_picture
self['twitter_picture'] || begin
name = self['twitter']['screen_name']
img = get_redirect_target "http://api.twitter.com/1/users/profile_image/#{name}?size=bigger"
if img and img !~ /default_profile_/
self['twitter_picture'] = img
self.save
img
end
end
update_twitter_picture(:autosave) if twitter_picture_stale?
self['twitter_picture']
end

def update_twitter_picture(autosave = false)
img = ServiceFetcher.get_twitter_profile_image self['twitter']['screen_name']
self['twitter_picture'] = img =~ /default_profile_/ ? nil : img
self['twitter_picture_updated_at'] = Time.now
self.save if autosave
end

def twitter_picture_stale?
self['twitter_picture_updated_at'].nil? or
self['twitter_picture_updated_at'] < 1.day.ago
end

def facebook_url
Expand Down Expand Up @@ -152,17 +158,4 @@ def login_from_provider(auth, current_user = nil)
return user
end
end

private

def get_redirect_target(url)
return nil if Movies.offline?
# TODO: cache results for 1 day
url = URI.parse url unless url.respond_to? :request_uri
response = Net::HTTP.start(url.host, open_timeout: 2) {|http| http.get url.request_uri }
response['location'] if response.is_a? Net::HTTPRedirection
rescue Timeout::Error
NeverForget.log($!, url: url)
return nil
end
end
100 changes: 100 additions & 0 deletions spec/models/user_spec.rb
@@ -1,5 +1,7 @@
# encoding: utf-8
require 'spec_helper'
require 'ostruct'
require 'hashie/mash'

describe User do
before do
Expand Down Expand Up @@ -280,4 +282,102 @@ def join_collection_create(user, doc)
@user.should_not be_following_on_twitter(user2)
end
end

describe "fetching friends lists" do
let(:user) do
build.tap { |user|
user.twitter_friends = [234]
user.facebook_friends = [234]
user['twitter_token'] = ['TWTOKEN', 'TWSECRET']
user['facebook_token'] = ['FBTOKEN', nil]
}
end

it "successful from twitter" do
twitter_data = OpenStruct.new status: 200, ids: [1234, 4567]

ServiceFetcher.should_receive(:get_twitter_friends).
with(user['twitter_token']).
and_return(twitter_data)

user.fetch_twitter_friends
user.twitter_friends.to_a.should =~ [1234, 4567]
user['twitter_token'].should_not be_nil
end

it "failed from twitter" do
twitter_data = OpenStruct.new status: 401

ServiceFetcher.should_receive(:get_twitter_friends).
with(user['twitter_token']).
and_return(twitter_data)

user.fetch_twitter_friends
user.twitter_friends.to_a.should =~ [234]
user['twitter_token'].should be_nil
end

it "successful from facebook" do
facebook_data = Hashie::Mash.new status: 200,
friends: {
data: [ {id: 1234}, {id: 4567} ]
}

ServiceFetcher.should_receive(:get_facebook_info).
with(user['facebook_token'], fields: 'friends').
and_return(facebook_data)

user.fetch_facebook_friends
user.facebook_friends.to_a.should =~ ["1234", "4567"]
user['facebook_token'].should_not be_nil
end

it "failed from facebook" do
facebook_data = OpenStruct.new status: 401

ServiceFetcher.should_receive(:get_facebook_info).
with(user['facebook_token'], fields: 'friends').
and_return(facebook_data)

user.fetch_facebook_friends
user.facebook_friends.to_a.should =~ ["234"]
user['facebook_token'].should be_nil
end
end

describe "picture" do
let(:user) { build }

it "has no picture" do
user.picture_url.should be_nil
end

it "fetches picture from twitter" do
user['twitter'] = {'id' => 1, 'screen_name' => 'mislav'}

ServiceFetcher.should_receive(:get_twitter_profile_image).
once.with('mislav').
and_return('http:///mislav.png')

user.picture_url.should eq('http:///mislav.png')
user.reload.picture_url.should eq('http:///mislav.png')
end

it "refreshes stale twitter picture" do
user['twitter'] = {'id' => 1, 'screen_name' => 'mislav'}
user['twitter_picture'] = 'http:///mislav.png'
user['twitter_picture_updated_at'] = 2.days.ago.utc

ServiceFetcher.should_receive(:get_twitter_profile_image).
once.with('mislav').
and_return('http:///new_pic.png')

user.picture_url.should eq('http:///new_pic.png')
end

it "uses facebook picture" do
user['facebook'] = {'id' => '1'}
user.picture_url.should eq('http://graph.facebook.com/1/picture?type=normal')
end
end
end

0 comments on commit 903090d

Please sign in to comment.