aeden-contacts 0.2.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +51 -0
  3. data/Rakefile +71 -0
  4. data/VERSION.yml +4 -0
  5. data/lib/config/contacts.yml +10 -0
  6. data/lib/contacts/flickr.rb +133 -0
  7. data/lib/contacts/google.rb +387 -0
  8. data/lib/contacts/google_oauth.rb +91 -0
  9. data/lib/contacts/version.rb +9 -0
  10. data/lib/contacts/windows_live.rb +164 -0
  11. data/lib/contacts/yahoo.rb +236 -0
  12. data/lib/contacts.rb +55 -0
  13. data/spec/contact_spec.rb +61 -0
  14. data/spec/feeds/contacts.yml +10 -0
  15. data/spec/feeds/flickr/auth.getFrob.xml +4 -0
  16. data/spec/feeds/flickr/auth.getToken.xml +5 -0
  17. data/spec/feeds/google-many.xml +48 -0
  18. data/spec/feeds/google-single.xml +46 -0
  19. data/spec/feeds/wl_contacts.xml +29 -0
  20. data/spec/feeds/yh_contacts.txt +119 -0
  21. data/spec/feeds/yh_credential.xml +28 -0
  22. data/spec/flickr/auth_spec.rb +80 -0
  23. data/spec/gmail/auth_spec.rb +70 -0
  24. data/spec/gmail/fetching_spec.rb +198 -0
  25. data/spec/rcov.opts +2 -0
  26. data/spec/spec.opts +2 -0
  27. data/spec/spec_helper.rb +84 -0
  28. data/spec/windows_live/windows_live_spec.rb +34 -0
  29. data/spec/yahoo/yahoo_spec.rb +83 -0
  30. data/vendor/fakeweb/CHANGELOG +80 -0
  31. data/vendor/fakeweb/LICENSE.txt +281 -0
  32. data/vendor/fakeweb/README.rdoc +160 -0
  33. data/vendor/fakeweb/Rakefile +57 -0
  34. data/vendor/fakeweb/fakeweb.gemspec +13 -0
  35. data/vendor/fakeweb/lib/fake_web/ext/net_http.rb +58 -0
  36. data/vendor/fakeweb/lib/fake_web/registry.rb +78 -0
  37. data/vendor/fakeweb/lib/fake_web/responder.rb +88 -0
  38. data/vendor/fakeweb/lib/fake_web/response.rb +10 -0
  39. data/vendor/fakeweb/lib/fake_web/socket_delegator.rb +24 -0
  40. data/vendor/fakeweb/lib/fake_web.rb +152 -0
  41. data/vendor/fakeweb/test/fixtures/test_example.txt +1 -0
  42. data/vendor/fakeweb/test/fixtures/test_request +21 -0
  43. data/vendor/fakeweb/test/test_allow_net_connect.rb +41 -0
  44. data/vendor/fakeweb/test/test_fake_web.rb +453 -0
  45. data/vendor/fakeweb/test/test_fake_web_open_uri.rb +62 -0
  46. data/vendor/fakeweb/test/test_helper.rb +52 -0
  47. data/vendor/fakeweb/test/test_query_string.rb +37 -0
  48. data/vendor/windowslivelogin.rb +1151 -0
  49. metadata +108 -0
@@ -0,0 +1,91 @@
1
+ require 'oauth'
2
+ require 'contacts/google'
3
+
4
+ # An extension to the standard OAuth library so we can nicely use Google APIs
5
+ module GoogleOAuth
6
+ class RequestToken < OAuth::RequestToken
7
+ def authorize_url(params={})
8
+ params.merge! :oauth_token => token
9
+ params = params.map { |k,v| "%s=%s" % [CGI.escape(k.to_s), CGI.escape(v)] }
10
+ consumer.authorize_url + "?" + params.join("&")
11
+ end
12
+ end
13
+
14
+ class Consumer < OAuth::Consumer
15
+ def initialize(consumer_key, consumer_secret)
16
+ super(consumer_key,
17
+ consumer_secret,
18
+ {:site => "https://www.google.com",
19
+ :request_token_path => "/accounts/OAuthGetRequestToken",
20
+ :access_token_path => "/accounts/OAuthGetAccessToken",
21
+ :authorize_path => "/accounts/OAuthAuthorizeToken"})
22
+ end
23
+
24
+ def marshal_load(data)
25
+ initialize(data[:key], data[:secret])
26
+ end
27
+
28
+ def marshal_dump
29
+ {:key => self.key, :secret => self.secret}
30
+ end
31
+
32
+ def get_request_token(params={})
33
+ params_str = params.map { |k,v| "%s=%s" % [CGI.escape(k.to_s), CGI.escape(v)] }.join("&")
34
+ uri = URI.parse(request_token_url? ? request_token_url : request_token_path)
35
+ if !uri.query || uri.query == ''
36
+ uri.query = params_str
37
+ else
38
+ uri.query = uri.query + "&" + params_str
39
+ end
40
+
41
+ response=token_request(http_method, uri.to_s, nil, {})
42
+ GoogleOAuth::RequestToken.new(self, response[:oauth_token], response[:oauth_token_secret])
43
+ end
44
+ end
45
+ end
46
+
47
+ module Contacts
48
+ class GoogleOAuth < Google
49
+ def initialize(consumer_key, consumer_secret, user_id = 'default')
50
+ @consumer = ::GoogleOAuth::Consumer.new(consumer_key, consumer_secret)
51
+ @request_token = @consumer.get_request_token :scope => "https://www.google.com/m8/feeds/"
52
+ @projection = 'thin'
53
+ @user = user_id.to_s
54
+ end
55
+
56
+ def marshal_load(data)
57
+ @consumer = data[:consumer]
58
+ @request_token = data[:request_token]
59
+ @projection = 'thin'
60
+ @user = data[:user]
61
+ end
62
+
63
+ def marshal_dump
64
+ {:consumer => @consumer,
65
+ :request_token => @request_token,
66
+ :user => @user}
67
+ end
68
+
69
+ # Available parameters:
70
+ # - hd: Google Apps domain that should be requested (default nil)
71
+ # - oauth_callback: The URL that the user should be redirected to when he successfully authorized us.
72
+ def authentication_url(params={})
73
+ @request_token.authorize_url params
74
+ end
75
+
76
+ def access_token
77
+ return @access_token if @access_token
78
+ begin
79
+ @access_token = @request_token.get_access_token
80
+ rescue Net::HTTPServerException
81
+ end
82
+ end
83
+
84
+ def get(params={})
85
+ path = FeedsPath + CGI.escape(@user)
86
+ google_params = translate_parameters(params)
87
+ query = self.class.query_string(google_params)
88
+ access_token.get("#{path}/#{@projection}?#{query}")
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,9 @@
1
+ module Contacts
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 6
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,164 @@
1
+ require 'contacts'
2
+ require File.join(File.dirname(__FILE__), %w{.. .. vendor windowslivelogin})
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,236 @@
1
+ require 'contacts'
2
+
3
+ require 'rubygems'
4
+ require 'hpricot'
5
+ require 'md5'
6
+ require 'net/https'
7
+ require 'uri'
8
+ require 'yaml'
9
+ require 'json'
10
+
11
+ module Contacts
12
+ # = How I can fetch Yahoo Contacts?
13
+ # To gain access to a Yahoo user's data in the Yahoo Address Book Service,
14
+ # a third-party developer first must ask the owner for permission. You must
15
+ # do that through Yahoo Browser Based Authentication (BBAuth).
16
+ #
17
+ # This library give you access to Yahoo BBAuth and Yahoo Address Book API.
18
+ # Just follow the steps below and be happy!
19
+ #
20
+ # === Registering your app
21
+ # First of all, follow the steps in this
22
+ # page[http://developer.yahoo.com/wsregapp/] to register your app. If you need
23
+ # some help with that form, you can get it
24
+ # here[http://developer.yahoo.com/auth/appreg.html]. Just two tips: inside
25
+ # <b>Required access scopes</b> in that registration form, choose
26
+ # <b>Yahoo! Address Book with Read Only access</b>. Inside
27
+ # <b>Authentication method</b> choose <b>Browser Based Authentication</b>.
28
+ #
29
+ # === Configuring your Yahoo YAML
30
+ # After registering your app, you will have an <b>application id</b> and a
31
+ # <b>shared secret</b>. Use their values to fill in the config/contacts.yml
32
+ # file.
33
+ #
34
+ # === Authenticating your user and fetching his contacts
35
+ #
36
+ # yahoo = Contacts::Yahoo.new
37
+ # auth_url = yahoo.get_authentication_url
38
+ #
39
+ # Use that *auth_url* to redirect your user to Yahoo BBAuth. He will authenticate
40
+ # there and Yahoo will redirect to your application entrypoint URL (that you provided
41
+ # while registering your app with Yahoo). You have to get the path of that
42
+ # redirect, let's call it path (if you're using Rails, you can get it through
43
+ # request.request_uri, in the context of an action inside ActionController)
44
+ #
45
+ # Now, to fetch his contacts, just do this:
46
+ #
47
+ # contacts = wl.contacts(path)
48
+ # #-> [ ['Fitzgerald', 'fubar@gmail.com', 'fubar@example.com'],
49
+ # ['William Paginate', 'will.paginate@gmail.com'], ...
50
+ # ]
51
+ #--
52
+ # This class has two responsibilities:
53
+ # 1. Access the Yahoo Address Book API through Delegated Authentication
54
+ # 2. Import contacts from Yahoo Mail and deliver it inside an Array
55
+ #
56
+ class Yahoo
57
+ AUTH_DOMAIN = "https://api.login.yahoo.com"
58
+ AUTH_PATH = "/WSLogin/V1/wslogin?appid=#appid&ts=#ts"
59
+ CREDENTIAL_PATH = "/WSLogin/V1/wspwtoken_login?appid=#appid&ts=#ts&token=#token"
60
+ ADDRESS_BOOK_DOMAIN = "address.yahooapis.com"
61
+ ADDRESS_BOOK_PATH = "/v1/searchContacts?format=json&fields=name,email&appid=#appid&WSSID=#wssid"
62
+ CONFIG_FILE = File.dirname(__FILE__) + '/../config/contacts.yml'
63
+
64
+ attr_reader :appid, :secret, :token, :wssid, :cookie
65
+
66
+ # Initialize a new Yahoo object.
67
+ #
68
+ # ==== Paramaters
69
+ # * config_file <String>:: The contacts YAML config file name
70
+ #--
71
+ # You can check an example of a config file inside config/ directory
72
+ #
73
+ def initialize(config_file=CONFIG_FILE)
74
+ confs = YAML.load_file(config_file)['yahoo']
75
+ @appid = confs['appid']
76
+ @secret = confs['secret']
77
+ end
78
+
79
+ # Yahoo Address Book API need to authenticate the user that is giving you
80
+ # access to his contacts. To do that, you must give him a URL. This method
81
+ # generates that URL. The user must access that URL, and after he has done
82
+ # authentication, hi will be redirected to your application.
83
+ #
84
+ def get_authentication_url
85
+ path = AUTH_PATH.clone
86
+ path.sub!(/#appid/, @appid)
87
+
88
+ timestamp = Time.now.utc.to_i
89
+ path.sub!(/#ts/, timestamp.to_s)
90
+
91
+ signature = MD5.hexdigest(path + @secret)
92
+ return AUTH_DOMAIN + "#{path}&sig=#{signature}"
93
+ end
94
+
95
+ # This method return the user's contacts inside an Array in the following
96
+ # format:
97
+ #
98
+ # [
99
+ # ['Brad Fitzgerald', 'fubar@gmail.com'],
100
+ # [nil, 'nagios@hotmail.com'],
101
+ # ['William Paginate', 'will.paginate@yahoo.com'] ...
102
+ # ]
103
+ #
104
+ # ==== Paramaters
105
+ # * path <String>:: The path of the redirect request that Yahoo sent to you
106
+ # after authenticating the user
107
+ #
108
+ def contacts(path)
109
+ begin
110
+ validate_signature(path)
111
+ credentials = access_user_credentials()
112
+ parse_credentials(credentials)
113
+ contacts_json = access_address_book_api()
114
+ Yahoo.parse_contacts(contacts_json)
115
+ rescue Exception => e
116
+ "Error #{e.class}: #{e.message}."
117
+ end
118
+ end
119
+
120
+ # This method processes and validates the redirect request that Yahoo send to
121
+ # you. Validation is done to verify that the request was really made by
122
+ # Yahoo. Processing is done to get the token.
123
+ #
124
+ # ==== Paramaters
125
+ # * path <String>:: The path of the redirect request that Yahoo sent to you
126
+ # after authenticating the user
127
+ #
128
+ def validate_signature(path)
129
+ path.match(/^(.+)&sig=(\w{32})$/)
130
+ path_without_sig = $1
131
+ sig = $2
132
+
133
+ if sig == MD5.hexdigest(path_without_sig + @secret)
134
+ path.match(/token=(.+?)&/)
135
+ @token = $1
136
+ return true
137
+ else
138
+ raise 'Signature not valid. This request may not have been sent from Yahoo.'
139
+ end
140
+ end
141
+
142
+ # This method accesses Yahoo to retrieve the user's credentials.
143
+ #
144
+ def access_user_credentials
145
+ url = get_credential_url()
146
+ uri = URI.parse(url)
147
+
148
+ http = http = Net::HTTP.new(uri.host, uri.port)
149
+ http.use_ssl = true
150
+
151
+ response = nil
152
+ http.start do |http|
153
+ request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
154
+ response = http.request(request)
155
+ end
156
+
157
+ return response.body
158
+ end
159
+
160
+ # This method generates the URL that you must access to get user's
161
+ # credentials.
162
+ #
163
+ def get_credential_url
164
+ path = CREDENTIAL_PATH.clone
165
+ path.sub!(/#appid/, @appid)
166
+
167
+ path.sub!(/#token/, @token)
168
+
169
+ timestamp = Time.now.utc.to_i
170
+ path.sub!(/#ts/, timestamp.to_s)
171
+
172
+ signature = MD5.hexdigest(path + @secret)
173
+ return AUTH_DOMAIN + "#{path}&sig=#{signature}"
174
+ end
175
+
176
+ # This method parses the user's credentials to generate the WSSID and
177
+ # Coookie that are needed to give you access to user's address book.
178
+ #
179
+ # ==== Paramaters
180
+ # * xml <String>:: A String containing the user's credentials
181
+ #
182
+ def parse_credentials(xml)
183
+ doc = Hpricot::XML(xml)
184
+ @wssid = doc.at('/BBAuthTokenLoginResponse/Success/WSSID').inner_text.strip
185
+ @cookie = doc.at('/BBAuthTokenLoginResponse/Success/Cookie').inner_text.strip
186
+ end
187
+
188
+ # This method accesses the Yahoo Address Book API and retrieves the user's
189
+ # contacts in JSON.
190
+ #
191
+ def access_address_book_api
192
+ http = http = Net::HTTP.new(ADDRESS_BOOK_DOMAIN, 80)
193
+
194
+ response = nil
195
+ http.start do |http|
196
+ path = ADDRESS_BOOK_PATH.clone
197
+ path.sub!(/#appid/, @appid)
198
+ path.sub!(/#wssid/, @wssid)
199
+
200
+ request = Net::HTTP::Get.new(path, {'Cookie' => @cookie})
201
+ response = http.request(request)
202
+ end
203
+
204
+ return response.body
205
+ end
206
+
207
+ # This method parses the JSON contacts document and returns an array
208
+ # contaning all the user's contacts.
209
+ #
210
+ # ==== Parameters
211
+ # * json <String>:: A String of user's contacts in JSON format
212
+ #
213
+ def self.parse_contacts(json)
214
+ contacts = []
215
+ people = JSON.parse(json)
216
+
217
+ people['contacts'].each do |contact|
218
+ name = nil
219
+ email = nil
220
+ contact['fields'].each do |field|
221
+ case field['type']
222
+ when 'email'
223
+ email = field['data']
224
+ email.strip!
225
+ when 'name'
226
+ name = "#{field['first']} #{field['last']}"
227
+ name.strip!
228
+ end
229
+ end
230
+ contacts.push Contact.new(email, name)
231
+ end
232
+ return contacts
233
+ end
234
+
235
+ end
236
+ end
data/lib/contacts.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'contacts/version'
2
+
3
+ module Contacts
4
+
5
+ Identifier = 'Ruby Contacts v' + VERSION::STRING
6
+
7
+ # An object that represents a single contact
8
+ class Contact
9
+ attr_reader :emails, :ims, :phones, :addresses, :organizations
10
+ attr_accessor :name, :username, :service_id, :note
11
+
12
+ def initialize(email, name = nil, username = nil)
13
+ @emails = []
14
+ @emails << email if email
15
+ @ims = []
16
+ @phones = []
17
+ @addresses = []
18
+ @organizations = []
19
+ @name = name
20
+ @username = username
21
+ end
22
+
23
+ def email
24
+ @emails.first
25
+ end
26
+
27
+ def inspect
28
+ %!#<Contacts::Contact "#{name}"#{email ? " (#{email})" : ''}>!
29
+ end
30
+ end
31
+
32
+ def self.verbose=(verbose)
33
+ @verbose = verbose
34
+ end
35
+
36
+ def self.verbose?
37
+ @verbose || 'irb' == $0
38
+ end
39
+
40
+ class Error < StandardError
41
+ end
42
+
43
+ class TooManyRedirects < Error
44
+ attr_reader :response, :location
45
+
46
+ MAX_REDIRECTS = 2
47
+
48
+ def initialize(response)
49
+ @response = response
50
+ @location = @response['Location']
51
+ super "exceeded maximum of #{MAX_REDIRECTS} redirects (Location: #{location})"
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,61 @@
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 a service id" do
19
+ @contact.service_id = 'service identifier'
20
+ @contact.service_id.should == 'service identifier'
21
+ end
22
+
23
+ it "should support multiple emails" do
24
+ @contact.emails << 'maxpower@example.com'
25
+ @contact.email.should == 'max@example.com'
26
+ @contact.emails.should == ['max@example.com', 'maxpower@example.com']
27
+ end
28
+
29
+ it "should support multiple ims" do
30
+ @contact.ims << {'value' => 'max', 'type' => 'skype'}
31
+ @contact.ims.should == [{'value' => 'max', 'type' => 'skype'}]
32
+ end
33
+
34
+ it "should support multiple phones" do
35
+ @contact.phones << {'value' => '111 111 1111', 'type' => 'home'}
36
+ @contact.phones.should == [{'value' => '111 111 1111', 'type' => 'home'}]
37
+ end
38
+
39
+ it "should support multiple addresses" do
40
+ @contact.addresses << {'formatted' => '111 SW 1st Street, New York, NY'}
41
+ @contact.addresses.should == [{'formatted' => '111 SW 1st Street, New York, NY'}]
42
+ end
43
+
44
+ it "should have username" do
45
+ @contact.username.should == 'maxpower'
46
+ end
47
+ end
48
+
49
+ describe '#inspect' do
50
+ it "should be nice" do
51
+ @contact = Contacts::Contact.new('max@example.com', 'Max Power', 'maxpower')
52
+ @contact.inspect.should == '#<Contacts::Contact "Max Power" (max@example.com)>'
53
+ end
54
+
55
+ it "should be nice without email" do
56
+ @contact = Contacts::Contact.new(nil, 'Max Power', 'maxpower')
57
+ @contact.inspect.should == '#<Contacts::Contact "Max Power">'
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,10 @@
1
+ windows_live:
2
+ appid: your_app_id
3
+ secret: your_app_secret_key
4
+ security_algorithm: wsignin1.0
5
+ return_url: http://yourserver.com/your_return_url
6
+ policy_url: http://yourserver.com/you_policy_url
7
+
8
+ yahoo:
9
+ appid: i%3DB%26p%3DUw70JGIdHWVRbpqYItcMw--
10
+ secret: a34f389cbd135de4618eed5e23409d34450
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8" ?>
2
+ <rsp stat="ok">
3
+ <frob>934-746563215463214621</frob>
4
+ </rsp>
@@ -0,0 +1,5 @@
1
+ <auth>
2
+ <token>45-76598454353455</token>
3
+ <perms>read</perms>
4
+ <user nsid="12037949754@N01" username="Bees" fullname="Cal H" />
5
+ </auth>
@@ -0,0 +1,48 @@
1
+ <feed>
2
+ <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>
3
+ <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>
4
+ <title>Elizabeth Bennet</title>
5
+ <content>My good friend, Liz. A little quick to judge sometimes, but nice girl.</content>
6
+ <gd:email rel='http://schemas.google.com/g/2005#work' primary='true' address='liz@gmail.com'/>
7
+ <gd:email rel='http://schemas.google.com/g/2005#home' address='liz@example.org'/>
8
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#work' primary='true'>
9
+ (206)555-1212
10
+ </gd:phoneNumber>
11
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#home'>
12
+ (206)555-1213
13
+ </gd:phoneNumber>
14
+ <gd:phoneNumber rel='http://schemas.google.com/g/2005#mobile'>
15
+ (206) 555-1212
16
+ </gd:phoneNumber>
17
+ <gd:im rel='http://schemas.google.com/g/2005#home'
18
+ protocol='http://schemas.google.com/g/2005#GOOGLE_TALK'
19
+ address='liz@gmail.com'/>
20
+ <gd:postalAddress rel='http://schemas.google.com/g/2005#work' primary='true'>
21
+ 1600 Amphitheatre Pkwy
22
+ Mountain View, CA 94043
23
+ </gd:postalAddress>
24
+ <gd:postalAddress rel='http://schemas.google.com/g/2005#home'>
25
+ 800 Main Street
26
+ Mountain View, CA 94041
27
+ </gd:postalAddress>
28
+ <gd:organization>
29
+ <gd:orgName>Google, Inc.</gd:orgName>
30
+ <gd:orgTitle>Tech Writer</gd:orgTitle>
31
+ </gd:organization>
32
+ </entry>
33
+
34
+ <entry>
35
+ <title>Poor Jack</title>
36
+ <content>Poor Jack doesn't have an e-mail address</content>
37
+ </entry>
38
+
39
+ <entry>
40
+ <title>William Paginate</title>
41
+ <gd:email address='will_paginate@googlegroups.com' />
42
+ </entry>
43
+
44
+ <entry>
45
+ <content>This guy doesn't have a name</content>
46
+ <gd:email address='anonymous@example.com' />
47
+ </entry>
48
+ </feed>