sundawg_contacts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +18 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +30 -0
- data/README.rdoc +47 -0
- data/Rakefile +68 -0
- data/lib/config/contacts.yml +10 -0
- data/lib/contacts.rb +46 -0
- data/lib/contacts/flickr.rb +133 -0
- data/lib/contacts/google.rb +353 -0
- data/lib/contacts/version.rb +9 -0
- data/lib/contacts/windows_live.rb +164 -0
- data/lib/contacts/yahoo.rb +242 -0
- data/spec/contact_spec.rb +41 -0
- data/spec/feeds/contacts.yml +10 -0
- data/spec/feeds/flickr/auth.getFrob.xml +4 -0
- data/spec/feeds/flickr/auth.getToken.xml +5 -0
- data/spec/feeds/google-many.xml +48 -0
- data/spec/feeds/google-single.xml +46 -0
- data/spec/feeds/wl_contacts.xml +29 -0
- data/spec/feeds/yh_contacts.txt +119 -0
- data/spec/feeds/yh_credential.xml +28 -0
- data/spec/flickr/auth_spec.rb +80 -0
- data/spec/gmail/auth_spec.rb +70 -0
- data/spec/gmail/fetching_spec.rb +196 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +84 -0
- data/spec/windows_live/windows_live_spec.rb +34 -0
- data/spec/yahoo/yahoo_spec.rb +83 -0
- data/sundawg_contacts.gemspec +33 -0
- data/vendor/windows_live_login.rb +1156 -0
- metadata +119 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'contacts'
|
2
|
+
require File.join(File.dirname(__FILE__), %w{.. .. vendor windows_live_login})
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'hpricot'
|
6
|
+
require 'uri'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module Contacts
|
10
|
+
# = How I can fetch Windows Live Contacts?
|
11
|
+
# To gain access to a Windows Live user's data in the Live Contacts service,
|
12
|
+
# a third-party developer first must ask the owner for permission. You must
|
13
|
+
# do that through Windows Live Delegated Authentication.
|
14
|
+
#
|
15
|
+
# This library give you access to Windows Live Delegated Authentication System
|
16
|
+
# and Windows Live Contacts API. Just follow the steps below and be happy!
|
17
|
+
#
|
18
|
+
# === Registering your app
|
19
|
+
# First of all, follow the steps in this
|
20
|
+
# page[http://msdn.microsoft.com/en-us/library/cc287659.aspx] to register your
|
21
|
+
# app.
|
22
|
+
#
|
23
|
+
# === Configuring your Windows Live YAML
|
24
|
+
# After registering your app, you will have an *appid*, a <b>secret key</b> and
|
25
|
+
# a <b>return URL</b>. Use their values to fill in the config/contacts.yml file.
|
26
|
+
# The policy URL field inside the YAML config file must contain the URL
|
27
|
+
# of the privacy policy of your Web site for Delegated Authentication.
|
28
|
+
#
|
29
|
+
# === Authenticating your user and fetching his contacts
|
30
|
+
#
|
31
|
+
# wl = Contacts::WindowsLive.new
|
32
|
+
# auth_url = wl.get_authentication_url
|
33
|
+
#
|
34
|
+
# Use that *auth_url* to redirect your user to Windows Live. He will authenticate
|
35
|
+
# there and Windows Live will POST to your return URL. You have to get the
|
36
|
+
# body of that POST, let's call it post_body. (if you're using Rails, you can
|
37
|
+
# get the POST body through request.raw_post, in the context of an action inside
|
38
|
+
# ActionController)
|
39
|
+
#
|
40
|
+
# Now, to fetch his contacts, just do this:
|
41
|
+
#
|
42
|
+
# contacts = wl.contacts(post_body)
|
43
|
+
# #-> [ ['Fitzgerald', 'fubar@gmail.com', 'fubar@example.com'],
|
44
|
+
# ['William Paginate', 'will.paginate@gmail.com'], ...
|
45
|
+
# ]
|
46
|
+
#--
|
47
|
+
# This class has two responsibilities:
|
48
|
+
# 1. Access the Windows Live Contacts API through Delegated Authentication
|
49
|
+
# 2. Import contacts from Windows Live and deliver it inside an Array
|
50
|
+
#
|
51
|
+
class WindowsLive
|
52
|
+
CONFIG_FILE = File.dirname(__FILE__) + '/../config/contacts.yml'
|
53
|
+
|
54
|
+
# Initialize a new WindowsLive object.
|
55
|
+
#
|
56
|
+
# ==== Paramaters
|
57
|
+
# * config_file <String>:: The contacts YAML config file name
|
58
|
+
#--
|
59
|
+
# You can check an example of a config file inside config/ directory
|
60
|
+
#
|
61
|
+
def initialize(config_file=CONFIG_FILE)
|
62
|
+
confs = YAML.load_file(config_file)['windows_live']
|
63
|
+
@wll = WindowsLiveLogin.new(confs['appid'], confs['secret'], confs['security_algorithm'],
|
64
|
+
nil, confs['policy_url'], confs['return_url'])
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Windows Live Contacts API need to authenticate the user that is giving you
|
69
|
+
# access to his contacts. To do that, you must give him a URL. That method
|
70
|
+
# generates that URL. The user must access that URL, and after he has done
|
71
|
+
# authentication, hi will be redirected to your application.
|
72
|
+
#
|
73
|
+
def get_authentication_url
|
74
|
+
@wll.getConsentUrl("Contacts.Invite")
|
75
|
+
end
|
76
|
+
|
77
|
+
# After the user has been authenticaded, Windows Live Delegated Authencation
|
78
|
+
# Service redirects to your application, through a POST HTTP method. Along
|
79
|
+
# with the POST, Windows Live send to you a Consent that you must process
|
80
|
+
# to access the user's contacts. This method process the Consent
|
81
|
+
# to you.
|
82
|
+
#
|
83
|
+
# ==== Paramaters
|
84
|
+
# * consent <String>:: A string containing the Consent given to you inside
|
85
|
+
# the redirection POST from Windows Live
|
86
|
+
#
|
87
|
+
def process_consent(consent)
|
88
|
+
consent.strip!
|
89
|
+
consent = URI.unescape(consent)
|
90
|
+
@consent_token = @wll.processConsent(consent)
|
91
|
+
end
|
92
|
+
|
93
|
+
# This method return the user's contacts inside an Array in the following
|
94
|
+
# format:
|
95
|
+
#
|
96
|
+
# [
|
97
|
+
# ['Brad Fitzgerald', 'fubar@gmail.com'],
|
98
|
+
# [nil, 'nagios@hotmail.com'],
|
99
|
+
# ['William Paginate', 'will.paginate@yahoo.com'] ...
|
100
|
+
# ]
|
101
|
+
#
|
102
|
+
# ==== Paramaters
|
103
|
+
# * consent <String>:: A string containing the Consent given to you inside
|
104
|
+
# the redirection POST from Windows Live
|
105
|
+
#
|
106
|
+
def contacts(consent)
|
107
|
+
process_consent(consent)
|
108
|
+
contacts_xml = access_live_contacts_api()
|
109
|
+
contacts_list = WindowsLive.parse_xml(contacts_xml)
|
110
|
+
end
|
111
|
+
|
112
|
+
# This method access the Windows Live Contacts API Web Service to get
|
113
|
+
# the XML contacts document
|
114
|
+
#
|
115
|
+
def access_live_contacts_api
|
116
|
+
http = http = Net::HTTP.new('livecontacts.services.live.com', 443)
|
117
|
+
http.use_ssl = true
|
118
|
+
|
119
|
+
response = nil
|
120
|
+
http.start do |http|
|
121
|
+
request = Net::HTTP::Get.new("/users/@L@#{@consent_token.locationid}/rest/invitationsbyemail", {"Authorization" => "DelegatedToken dt=\"#{@consent_token.delegationtoken}\""})
|
122
|
+
response = http.request(request)
|
123
|
+
end
|
124
|
+
|
125
|
+
return response.body
|
126
|
+
end
|
127
|
+
|
128
|
+
# This method parses the XML Contacts document and returns the contacts
|
129
|
+
# inside an Array
|
130
|
+
#
|
131
|
+
# ==== Paramaters
|
132
|
+
# * xml <String>:: A string containing the XML contacts document
|
133
|
+
#
|
134
|
+
def self.parse_xml(xml)
|
135
|
+
doc = Hpricot::XML(xml)
|
136
|
+
|
137
|
+
contacts = []
|
138
|
+
doc.search('/livecontacts/contacts/contact').each do |contact|
|
139
|
+
email = contact.at('/preferredemail').inner_text
|
140
|
+
email.strip!
|
141
|
+
|
142
|
+
first_name = last_name = nil
|
143
|
+
if first_name = contact.at('/profiles/personal/firstname')
|
144
|
+
first_name = first_name.inner_text.strip
|
145
|
+
end
|
146
|
+
|
147
|
+
if last_name = contact.at('/profiles/personal/lastname')
|
148
|
+
last_name = last_name.inner_text.strip
|
149
|
+
end
|
150
|
+
|
151
|
+
name = nil
|
152
|
+
if !first_name.nil? || !last_name.nil?
|
153
|
+
name = "#{first_name} #{last_name}"
|
154
|
+
name.strip!
|
155
|
+
end
|
156
|
+
new_contact = Contact.new(email, name)
|
157
|
+
contacts << new_contact
|
158
|
+
end
|
159
|
+
|
160
|
+
return contacts
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'contacts'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hpricot'
|
5
|
+
require 'md5'
|
6
|
+
require 'net/https'
|
7
|
+
require 'uri'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
module Contacts
|
11
|
+
# = How I can fetch Yahoo Contacts?
|
12
|
+
# To gain access to a Yahoo user's data in the Yahoo Address Book Service,
|
13
|
+
# a third-party developer first must ask the owner for permission. You must
|
14
|
+
# do that through Yahoo Browser Based Authentication (BBAuth).
|
15
|
+
#
|
16
|
+
# This library give you access to Yahoo BBAuth and Yahoo Address Book API.
|
17
|
+
# Just follow the steps below and be happy!
|
18
|
+
#
|
19
|
+
# === Registering your app
|
20
|
+
# First of all, follow the steps in this
|
21
|
+
# page[http://developer.yahoo.com/wsregapp/] to register your app. If you need
|
22
|
+
# some help with that form, you can get it
|
23
|
+
# here[http://developer.yahoo.com/auth/appreg.html]. Just two tips: inside
|
24
|
+
# <b>Required access scopes</b> in that registration form, choose
|
25
|
+
# <b>Yahoo! Address Book with Read Only access</b>. Inside
|
26
|
+
# <b>Authentication method</b> choose <b>Browser Based Authentication</b>.
|
27
|
+
#
|
28
|
+
# === Configuring your Yahoo YAML
|
29
|
+
# After registering your app, you will have an <b>application id</b> and a
|
30
|
+
# <b>shared secret</b>. Use their values to fill in the config/contacts.yml
|
31
|
+
# file.
|
32
|
+
#
|
33
|
+
# === Authenticating your user and fetching his contacts
|
34
|
+
#
|
35
|
+
# yahoo = Contacts::Yahoo.new
|
36
|
+
# auth_url = yahoo.get_authentication_url
|
37
|
+
#
|
38
|
+
# Use that *auth_url* to redirect your user to Yahoo BBAuth. He will authenticate
|
39
|
+
# there and Yahoo will redirect to your application entrypoint URL (that you provided
|
40
|
+
# while registering your app with Yahoo). You have to get the path of that
|
41
|
+
# redirect, let's call it path (if you're using Rails, you can get it through
|
42
|
+
# request.request_uri, in the context of an action inside ActionController)
|
43
|
+
#
|
44
|
+
# Now, to fetch his contacts, just do this:
|
45
|
+
#
|
46
|
+
# contacts = wl.contacts(path)
|
47
|
+
# #-> [ ['Fitzgerald', 'fubar@gmail.com', 'fubar@example.com'],
|
48
|
+
# ['William Paginate', 'will.paginate@gmail.com'], ...
|
49
|
+
# ]
|
50
|
+
#--
|
51
|
+
# This class has two responsibilities:
|
52
|
+
# 1. Access the Yahoo Address Book API through Delegated Authentication
|
53
|
+
# 2. Import contacts from Yahoo Mail and deliver it inside an Array
|
54
|
+
#
|
55
|
+
class Yahoo
|
56
|
+
AUTH_DOMAIN = "https://api.login.yahoo.com"
|
57
|
+
AUTH_PATH = "/WSLogin/V1/wslogin?appid=#appid&ts=#ts"
|
58
|
+
CREDENTIAL_PATH = "/WSLogin/V1/wspwtoken_login?appid=#appid&ts=#ts&token=#token"
|
59
|
+
ADDRESS_BOOK_DOMAIN = "address.yahooapis.com"
|
60
|
+
ADDRESS_BOOK_PATH = "/v1/searchContacts?format=json&fields=name,email&appid=#appid&WSSID=#wssid"
|
61
|
+
CONFIG_FILE = File.dirname(__FILE__) + '/../config/contacts.yml'
|
62
|
+
|
63
|
+
attr_reader :appid, :secret, :token, :wssid, :cookie
|
64
|
+
|
65
|
+
# Initialize a new Yahoo object.
|
66
|
+
#
|
67
|
+
# ==== Paramaters
|
68
|
+
# * config_file <String>:: The contacts YAML config file name
|
69
|
+
#--
|
70
|
+
# You can check an example of a config file inside config/ directory
|
71
|
+
#
|
72
|
+
def initialize(config_file=CONFIG_FILE)
|
73
|
+
confs = YAML.load_file(config_file)['yahoo']
|
74
|
+
@appid = confs['appid']
|
75
|
+
@secret = confs['secret']
|
76
|
+
end
|
77
|
+
|
78
|
+
# Yahoo Address Book API need to authenticate the user that is giving you
|
79
|
+
# access to his contacts. To do that, you must give him a URL. This method
|
80
|
+
# generates that URL. The user must access that URL, and after he has done
|
81
|
+
# authentication, hi will be redirected to your application.
|
82
|
+
#
|
83
|
+
def get_authentication_url
|
84
|
+
path = AUTH_PATH.clone
|
85
|
+
path.sub!(/#appid/, @appid)
|
86
|
+
|
87
|
+
timestamp = Time.now.utc.to_i
|
88
|
+
path.sub!(/#ts/, timestamp.to_s)
|
89
|
+
|
90
|
+
signature = MD5.hexdigest(path + @secret)
|
91
|
+
return AUTH_DOMAIN + "#{path}&sig=#{signature}"
|
92
|
+
end
|
93
|
+
|
94
|
+
# This method return the user's contacts inside an Array in the following
|
95
|
+
# format:
|
96
|
+
#
|
97
|
+
# [
|
98
|
+
# ['Brad Fitzgerald', 'fubar@gmail.com'],
|
99
|
+
# [nil, 'nagios@hotmail.com'],
|
100
|
+
# ['William Paginate', 'will.paginate@yahoo.com'] ...
|
101
|
+
# ]
|
102
|
+
#
|
103
|
+
# ==== Paramaters
|
104
|
+
# * path <String>:: The path of the redirect request that Yahoo sent to you
|
105
|
+
# after authenticating the user
|
106
|
+
#
|
107
|
+
def contacts(path, force_json_gem=false)
|
108
|
+
validate_signature(path)
|
109
|
+
credentials = access_user_credentials()
|
110
|
+
parse_credentials(credentials)
|
111
|
+
contacts_json = access_address_book_api()
|
112
|
+
Yahoo.parse_contacts(contacts_json, force_json_gem)
|
113
|
+
end
|
114
|
+
|
115
|
+
# This method processes and validates the redirect request that Yahoo send to
|
116
|
+
# you. Validation is done to verify that the request was really made by
|
117
|
+
# Yahoo. Processing is done to get the token.
|
118
|
+
#
|
119
|
+
# ==== Paramaters
|
120
|
+
# * path <String>:: The path of the redirect request that Yahoo sent to you
|
121
|
+
# after authenticating the user
|
122
|
+
#
|
123
|
+
def validate_signature(path)
|
124
|
+
path.match(/^(.+)&sig=(\w{32})$/)
|
125
|
+
path_without_sig = $1
|
126
|
+
sig = $2
|
127
|
+
|
128
|
+
if sig == MD5.hexdigest(path_without_sig + @secret)
|
129
|
+
path.match(/token=(.+?)&/)
|
130
|
+
@token = $1
|
131
|
+
return true
|
132
|
+
else
|
133
|
+
raise 'Signature not valid. This request may not have been sent from Yahoo.'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# This method accesses Yahoo to retrieve the user's credentials.
|
138
|
+
#
|
139
|
+
def access_user_credentials
|
140
|
+
url = get_credential_url()
|
141
|
+
uri = URI.parse(url)
|
142
|
+
|
143
|
+
http = http = Net::HTTP.new(uri.host, uri.port)
|
144
|
+
http.use_ssl = true
|
145
|
+
|
146
|
+
response = nil
|
147
|
+
http.start do |http|
|
148
|
+
request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
|
149
|
+
response = http.request(request)
|
150
|
+
end
|
151
|
+
|
152
|
+
return response.body
|
153
|
+
end
|
154
|
+
|
155
|
+
# This method generates the URL that you must access to get user's
|
156
|
+
# credentials.
|
157
|
+
#
|
158
|
+
def get_credential_url
|
159
|
+
path = CREDENTIAL_PATH.clone
|
160
|
+
path.sub!(/#appid/, @appid)
|
161
|
+
|
162
|
+
path.sub!(/#token/, @token)
|
163
|
+
|
164
|
+
timestamp = Time.now.utc.to_i
|
165
|
+
path.sub!(/#ts/, timestamp.to_s)
|
166
|
+
|
167
|
+
signature = MD5.hexdigest(path + @secret)
|
168
|
+
return AUTH_DOMAIN + "#{path}&sig=#{signature}"
|
169
|
+
end
|
170
|
+
|
171
|
+
# This method parses the user's credentials to generate the WSSID and
|
172
|
+
# Coookie that are needed to give you access to user's address book.
|
173
|
+
#
|
174
|
+
# ==== Paramaters
|
175
|
+
# * xml <String>:: A String containing the user's credentials
|
176
|
+
#
|
177
|
+
|
178
|
+
# temporary hack to find out cause of xml values being nil
|
179
|
+
class XMLParseError < StandardError; end
|
180
|
+
|
181
|
+
def parse_credentials(xml)
|
182
|
+
doc = Hpricot::XML(xml)
|
183
|
+
node = doc.at('/BBAuthTokenLoginResponse/Success/WSSID')
|
184
|
+
raise XMLParseError, xml.to_s if !node
|
185
|
+
@wssid = doc.at('/BBAuthTokenLoginResponse/Success/WSSID').inner_text.strip
|
186
|
+
@cookie = doc.at('/BBAuthTokenLoginResponse/Success/Cookie').inner_text.strip
|
187
|
+
end
|
188
|
+
|
189
|
+
# This method accesses the Yahoo Address Book API and retrieves the user's
|
190
|
+
# contacts in JSON.
|
191
|
+
#
|
192
|
+
def access_address_book_api
|
193
|
+
http = http = Net::HTTP.new(ADDRESS_BOOK_DOMAIN, 80)
|
194
|
+
|
195
|
+
response = nil
|
196
|
+
http.start do |http|
|
197
|
+
path = ADDRESS_BOOK_PATH.clone
|
198
|
+
path.sub!(/#appid/, @appid)
|
199
|
+
path.sub!(/#wssid/, @wssid)
|
200
|
+
|
201
|
+
request = Net::HTTP::Get.new(path, {'Cookie' => @cookie})
|
202
|
+
response = http.request(request)
|
203
|
+
end
|
204
|
+
|
205
|
+
return response.body
|
206
|
+
end
|
207
|
+
|
208
|
+
# This method parses the JSON contacts document and returns an array
|
209
|
+
# contaning all the user's contacts.
|
210
|
+
#
|
211
|
+
# ==== Parameters
|
212
|
+
# * json <String>:: A String of user's contacts in JSON format
|
213
|
+
#
|
214
|
+
def self.parse_contacts(json, force_json_gem=false)
|
215
|
+
contacts = []
|
216
|
+
people = if !force_json_gem && defined? ActiveSupport::JSON
|
217
|
+
ActiveSupport::JSON.decode(json)
|
218
|
+
else
|
219
|
+
require 'json'
|
220
|
+
JSON.parse(json)
|
221
|
+
end
|
222
|
+
|
223
|
+
people['contacts'].each do |contact|
|
224
|
+
name = nil
|
225
|
+
email = nil
|
226
|
+
contact['fields'].each do |field|
|
227
|
+
case field['type']
|
228
|
+
when 'email'
|
229
|
+
email = field['data']
|
230
|
+
email.strip!
|
231
|
+
when 'name'
|
232
|
+
name = "#{field['first']} #{field['last']}"
|
233
|
+
name.strip!
|
234
|
+
end
|
235
|
+
end
|
236
|
+
contacts.push Contact.new(email, name)
|
237
|
+
end
|
238
|
+
return contacts
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'contacts'
|
3
|
+
|
4
|
+
describe Contacts::Contact do
|
5
|
+
describe 'instance' do
|
6
|
+
before do
|
7
|
+
@contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have email" do
|
11
|
+
@contact.email.should == 'max@example.com'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have name" do
|
15
|
+
@contact.name.should == 'Max Power'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should support multiple emails" do
|
19
|
+
@contact.emails << 'maxpower@example.com'
|
20
|
+
@contact.email.should == 'max@example.com'
|
21
|
+
@contact.emails.should == ['max@example.com', 'maxpower@example.com']
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have username" do
|
25
|
+
@contact.username.should == 'maxpower'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#inspect' do
|
30
|
+
it "should be nice" do
|
31
|
+
@contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
|
32
|
+
@contact.inspect.should == '#<Contacts::Contact "Max Power" (max@example.com)>'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be nice without email" do
|
36
|
+
@contact = Contacts::Contact.new(nil, 'Max Power', 'maxpower')
|
37
|
+
@contact.inspect.should == '#<Contacts::Contact "Max Power">'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|