linkedin-oauth2 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +9 -9
  2. data/.gitignore +24 -39
  3. data/.travis.yml +4 -4
  4. data/.yardopts +2 -0
  5. data/CHANGELOG.md +10 -0
  6. data/CONTRIBUTING.md +1 -0
  7. data/Gemfile +6 -5
  8. data/LICENSE +19 -17
  9. data/README.md +399 -0
  10. data/Rakefile +15 -22
  11. data/lib/linked_in/access_token.rb +24 -0
  12. data/lib/linked_in/api.rb +96 -3
  13. data/lib/linked_in/api_resource.rb +165 -0
  14. data/lib/linked_in/communications.rb +40 -0
  15. data/lib/linked_in/companies.rb +146 -0
  16. data/lib/linked_in/configuration.rb +41 -0
  17. data/lib/linked_in/connection.rb +31 -0
  18. data/lib/linked_in/errors.rb +33 -13
  19. data/lib/linked_in/groups.rb +116 -0
  20. data/lib/linked_in/jobs.rb +68 -0
  21. data/lib/linked_in/mash.rb +34 -34
  22. data/lib/linked_in/oauth2.rb +223 -0
  23. data/lib/linked_in/people.rb +141 -0
  24. data/lib/linked_in/search.rb +58 -43
  25. data/lib/linked_in/share_and_social_stream.rb +128 -0
  26. data/lib/linked_in/version.rb +1 -9
  27. data/lib/linkedin-oauth2.rb +43 -25
  28. data/linkedin-oauth2.gemspec +35 -21
  29. data/spec/linked_in/api/api_spec.rb +41 -0
  30. data/spec/linked_in/api/communications_spec.rb +13 -0
  31. data/spec/linked_in/api/companies_spec.rb +59 -0
  32. data/spec/linked_in/api/groups_spec.rb +55 -0
  33. data/spec/linked_in/api/jobs_spec.rb +33 -0
  34. data/spec/linked_in/api/people_spec.rb +181 -0
  35. data/spec/linked_in/api/search_spec.rb +71 -0
  36. data/spec/linked_in/api/share_and_social_stream_spec.rb +60 -0
  37. data/spec/linked_in/configuration_spec.rb +46 -0
  38. data/spec/linked_in/connection_spec.rb +10 -0
  39. data/spec/linked_in/module_loading_spec.rb +23 -0
  40. data/spec/linked_in/oauth/access_token_spec.rb +27 -0
  41. data/spec/linked_in/oauth/auth_code_spec.rb +86 -0
  42. data/spec/linked_in/oauth/credentials_spec.rb +96 -0
  43. data/spec/linked_in/oauth/get_access_token_spec.rb +108 -0
  44. data/spec/spec_helper.rb +15 -0
  45. data/spec/vcr_cassettes/access_token_success.yml +84 -0
  46. data/spec/vcr_cassettes/bad_code.yml +78 -0
  47. data/spec/vcr_cassettes/companies_data.yml +44 -0
  48. data/spec/vcr_cassettes/invalid_access_token.yml +60 -0
  49. data/spec/vcr_cassettes/not_found.yml +64 -0
  50. data/spec/vcr_cassettes/people_picture_urls.yml +54 -0
  51. data/spec/vcr_cassettes/people_profile_connections_fields.yml +73 -0
  52. data/spec/vcr_cassettes/people_profile_connections_other.yml +78 -0
  53. data/spec/vcr_cassettes/people_profile_connections_self.yml +78 -0
  54. data/spec/vcr_cassettes/people_profile_fields_complex.yml +70 -0
  55. data/spec/vcr_cassettes/people_profile_fields_simple.yml +57 -0
  56. data/spec/vcr_cassettes/people_profile_lang_spanish.yml +59 -0
  57. data/spec/vcr_cassettes/people_profile_multiple_fields.yml +68 -0
  58. data/spec/vcr_cassettes/people_profile_multiple_uids.yml +70 -0
  59. data/spec/vcr_cassettes/people_profile_multiple_uids_and_urls.yml +76 -0
  60. data/spec/vcr_cassettes/people_profile_multiple_urls.yml +70 -0
  61. data/spec/vcr_cassettes/people_profile_new_connections_fields.yml +69 -0
  62. data/spec/vcr_cassettes/people_profile_new_connections_other.yml +72 -0
  63. data/spec/vcr_cassettes/people_profile_new_connections_self.yml +72 -0
  64. data/spec/vcr_cassettes/people_profile_other_uid.yml +59 -0
  65. data/spec/vcr_cassettes/people_profile_other_url.yml +59 -0
  66. data/spec/vcr_cassettes/people_profile_own.yml +59 -0
  67. data/spec/vcr_cassettes/people_profile_own_secure.yml +59 -0
  68. data/spec/vcr_cassettes/unauthorized.yml +61 -0
  69. data/spec/vcr_cassettes/unavailable.yml +81 -0
  70. metadata +145 -88
  71. data/.autotest +0 -14
  72. data/.document +0 -5
  73. data/.gemtest +0 -0
  74. data/.rspec +0 -1
  75. data/.ruby-gemset +0 -1
  76. data/.ruby-version +0 -1
  77. data/README.markdown +0 -121
  78. data/changelog.markdown +0 -94
  79. data/examples/authenticate.rb +0 -16
  80. data/examples/network.rb +0 -12
  81. data/examples/profile.rb +0 -18
  82. data/examples/sinatra.rb +0 -69
  83. data/examples/status.rb +0 -6
  84. data/lib/linked_in/api/query_methods.rb +0 -123
  85. data/lib/linked_in/api/update_methods.rb +0 -76
  86. data/lib/linked_in/client.rb +0 -31
  87. data/lib/linked_in/helpers.rb +0 -6
  88. data/lib/linked_in/helpers/authorization.rb +0 -106
  89. data/lib/linked_in/helpers/request.rb +0 -93
  90. data/spec/cases/api_spec.rb +0 -192
  91. data/spec/cases/linkedin_spec.rb +0 -37
  92. data/spec/cases/mash_spec.rb +0 -85
  93. data/spec/cases/oauth_spec.rb +0 -130
  94. data/spec/cases/search_spec.rb +0 -190
  95. data/spec/helper.rb +0 -30
data/Rakefile CHANGED
@@ -1,26 +1,19 @@
1
- #!/usr/bin/env rake
2
-
3
- require 'bundler'
4
- Bundler::GemHelper.install_tasks
5
-
1
+ # Adds the following task:
2
+ # spec - Run RSpec tests & setup $LOAD_PATH properly
3
+ #
4
+ # We recommend you set the following RSpec options in your own ~/.rspec
5
+ # --color
6
+ # --format documentation
7
+ # --profile
8
+ # --order rand
6
9
  require 'rspec/core/rake_task'
7
10
  RSpec::Core::RakeTask.new(:spec)
8
11
 
9
- task :test => :spec
10
- task :default => :spec
11
-
12
- task :compile_and_test do
13
- puts "Compiling"
14
- puts `gem build linkedin-oauth2.gemspec && gem install ./linkedin-oauth2*.gem`
15
- puts "Testing"
16
- Rake::Task["test"].invoke
17
- end
12
+ # Adds the following tasks:
13
+ # build - Build gem in pkg/ directory
14
+ # install - Build and install gem
15
+ # release - Create tag, build gem, and push it to Rubygems
16
+ require 'bundler/gem_helper'
17
+ Bundler::GemHelper.install_tasks
18
18
 
19
- require 'rdoc/task'
20
- require File.expand_path('../lib/linked_in/version', __FILE__)
21
- RDoc::Task.new do |rdoc|
22
- rdoc.rdoc_dir = 'rdoc'
23
- rdoc.title = "linkedin #{LinkedIn::VERSION::STRING}"
24
- rdoc.rdoc_files.include('README*')
25
- rdoc.rdoc_files.include('lib/**/*.rb')
26
- end
19
+ task default: :spec
@@ -0,0 +1,24 @@
1
+ module LinkedIn
2
+ # A simple data object to contain the token string and expiration data.
3
+ class AccessToken
4
+ attr_accessor :token, :expires_in, :expires_at
5
+
6
+ # Creates a simple data wrapper for an access token.
7
+ #
8
+ # LinkedIn returns only an `expires_in` value. This calculates and
9
+ # sets and `expires_at` field for convenience.
10
+ #
11
+ # @param [String] token the access token
12
+ # @param [FixNum] expires_in number of seconds the token lasts for
13
+ # @param [Time] expires_at when the token will expire.
14
+ def initialize(token=nil, expires_in=nil, expires_at=nil)
15
+ self.token = token
16
+ self.expires_in = expires_in
17
+ if expires_at.nil? and not self.expires_in.nil?
18
+ self.expires_at = Time.now + expires_in
19
+ else
20
+ self.expires_at = expires_at
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,99 @@
1
1
  module LinkedIn
2
- module Api
3
- autoload :QueryMethods, "linked_in/api/query_methods"
4
- autoload :UpdateMethods, "linked_in/api/update_methods"
2
+ class API
3
+
4
+ attr_accessor :access_token
5
+
6
+ def initialize(access_token=nil)
7
+ access_token = parse_access_token(access_token)
8
+ verify_access_token!(access_token)
9
+ @access_token = access_token
10
+
11
+ @connection = LinkedIn::Connection.new params: default_params,
12
+ headers: default_headers
13
+
14
+ initialize_endpoints
15
+ end
16
+
17
+ extend Forwardable # Composition over inheritance
18
+ def_delegators :@jobs, :job,
19
+ :job_bookmarks,
20
+ :job_suggestions,
21
+ :add_job_bookmark
22
+
23
+ def_delegators :@people, :profile,
24
+ :connections,
25
+ :picture_urls,
26
+ :new_connections
27
+
28
+ def_delegators :@search, :search
29
+
30
+ def_delegators :@groups, :join_group,
31
+ :group_posts,
32
+ :group_profile,
33
+ :add_group_share,
34
+ :group_suggestions,
35
+ :group_memberships,
36
+ :post_group_discussion
37
+
38
+ def_delegators :@companies, :company,
39
+ :follow_company,
40
+ :company_updates,
41
+ :unfollow_company,
42
+ :add_company_share,
43
+ :company_statistics,
44
+ :company_updates_likes,
45
+ :company_updates_comments
46
+
47
+ def_delegators :@communications, :send_message
48
+
49
+ def_delegators :@share_and_social_stream, :shares,
50
+ :add_share,
51
+ :like_share,
52
+ :share_likes,
53
+ :unlike_share,
54
+ :share_comments,
55
+ :update_comment,
56
+ :network_updates
57
+
58
+ private ##############################################################
59
+
60
+ def initialize_endpoints
61
+ @jobs = LinkedIn::Jobs.new(@connection)
62
+ @people = LinkedIn::People.new(@connection)
63
+ @search = LinkedIn::Search.new(@connection)
64
+ @groups = LinkedIn::Groups.new(@connection)
65
+ @companies = LinkedIn::Companies.new(@connection)
66
+ @communications = LinkedIn::Communications.new(@connection)
67
+ @share_and_social_stream = LinkedIn::ShareAndSocialStream.new(@connection)
68
+ end
69
+
70
+ def default_params
71
+ # https//developer.linkedin.com/documents/authentication
72
+ return {oauth2_access_token: @access_token.token}
73
+ end
74
+
75
+ def default_headers
76
+ # https://developer.linkedin.com/documents/api-requests-json
77
+ return {"x-li-format" => "json"}
78
+ end
79
+
80
+ def verify_access_token!(access_token)
81
+ if not access_token.is_a? LinkedIn::AccessToken
82
+ raise no_access_token_error
83
+ end
84
+ end
85
+
86
+ def parse_access_token(access_token)
87
+ if access_token.is_a? LinkedIn::AccessToken
88
+ return access_token
89
+ elsif access_token.is_a? String
90
+ return LinkedIn::AccessToken.new(access_token)
91
+ end
92
+ end
93
+
94
+ def no_access_token_error
95
+ msg = LinkedIn::ErrorMessages.no_access_token
96
+ LinkedIn::InvalidRequest.new(msg)
97
+ end
5
98
  end
6
99
  end
@@ -0,0 +1,165 @@
1
+ module LinkedIn
2
+ # The abstract class all API endpoints inherit from. Providers common
3
+ # builder methods across all endpoints.
4
+ #
5
+ # @!macro profile_options
6
+ # @options opts [String] :id LinkedIn ID to fetch profile for
7
+ # @options opts [String] :url The profile url
8
+ # @options opts [String] :lang Requests the language of the profile.
9
+ # Options are: en, fr, de, it, pt, es
10
+ # @options opts [Array, Hash] :fields fields to fetch. The list of
11
+ # fields can be found at
12
+ # https://developer.linkedin.com/documents/profile-fields
13
+ # @options opts [String] :secure (true) specify if urls in the
14
+ # response should be https
15
+ # @options opts [String] :"secure-urls" (true) alias to secure option
16
+ #
17
+ # @!macro share_input_fields
18
+ # @param [Hash] share content of the share
19
+ # @option share [String] :comment
20
+ # @option share [String] :content
21
+ # @option share [String] :title
22
+ # @option share [String] :submitted-url
23
+ # @option share [String] :submitted-image-url
24
+ # @option share [String] :description
25
+ # @option share [String] :visibility
26
+ # @option share [String] :code
27
+ #
28
+ # @!macro company_path_options
29
+ # @param [Hash] options identifies the user profile you want
30
+ # @option options [String] :domain company email domain
31
+ # @option options [String] :id company ID
32
+ # @option options [String] :url
33
+ # @option options [String] :name company universal name
34
+ # @option options [String] :is_admin list all companies that the
35
+ # authenticated is an administrator of
36
+ class APIResource
37
+
38
+ def initialize(connection)
39
+ @connection = connection
40
+ end
41
+
42
+ protected ############################################################
43
+
44
+ def get(path, options={})
45
+ url, params, headers = prepare_connection_params(path, options)
46
+
47
+ response = @connection.get(url, params, headers)
48
+
49
+ return Mash.from_json(response.body)
50
+ end
51
+
52
+ def post(path=nil, body=nil, headers=nil, &block)
53
+ @connection.post(prepend_prefix(path), body, headers, &block)
54
+ end
55
+
56
+ def put(path=nil, body=nil, headers=nil, &block)
57
+ @connection.put(prepend_prefix(path), body, headers, &block)
58
+ end
59
+
60
+ def delete(path=nil, params=nil, headers=nil, &block)
61
+ @connection.delete(prepend_prefix(path), params, headers, &block)
62
+ end
63
+
64
+ def deprecated
65
+ LinkedIn::Deprecated.new(LinkedIn::ErrorMessages.deprecated)
66
+ end
67
+
68
+ private ##############################################################
69
+
70
+ def prepend_prefix(path)
71
+ return @connection.path_prefix + path
72
+ end
73
+
74
+ def prepare_connection_params(path, options)
75
+ path = prepend_prefix(path)
76
+ path += generate_field_selectors(options)
77
+
78
+ headers = options.delete(:headers) || {}
79
+
80
+ params = format_options_for_query(options)
81
+
82
+ return [path, params, headers]
83
+ end
84
+
85
+ # Dasherizes the param keys
86
+ def format_options_for_query(options)
87
+ options.reduce({}) do |list, kv|
88
+ key, value = kv.first.to_s.gsub("_","-"), kv.last
89
+ list[key] = value
90
+ list
91
+ end
92
+ end
93
+
94
+ def generate_field_selectors(options)
95
+ default = LinkedIn.config.default_profile_fields || {}
96
+ fields = options.delete(:fields) || default
97
+
98
+ if options.delete(:public)
99
+ return ":public"
100
+ elsif fields.empty?
101
+ return ""
102
+ else
103
+ return ":(#{build_fields_params(fields)})"
104
+ end
105
+ end
106
+
107
+ def build_fields_params(fields)
108
+ if fields.is_a?(Hash) && !fields.empty?
109
+ fields.map {|i,v| "#{i}:(#{build_fields_params(v)})" }.join(',')
110
+ elsif fields.respond_to?(:each)
111
+ fields.map {|field| build_fields_params(field) }.join(',')
112
+ else
113
+ fields.to_s.gsub("_", "-")
114
+ end
115
+ end
116
+
117
+ def profile_path(options={}, allow_multiple=true)
118
+ path = "/people"
119
+
120
+ id = options.delete(:id)
121
+ url = options.delete(:url)
122
+
123
+ ids = options.delete(:ids)
124
+ urls = options.delete(:urls)
125
+
126
+ if options.delete(:email) then raise deprecated end
127
+
128
+ if (id or url)
129
+ path += single_person_path(id, url)
130
+ elsif allow_multiple and (ids or urls)
131
+ path += multiple_people_path(ids, urls)
132
+ else
133
+ path += "/~"
134
+ end
135
+ end
136
+
137
+ def single_person_path(id=nil, url=nil)
138
+ if id
139
+ return "/id=#{id}"
140
+ elsif url
141
+ return "/url=#{CGI.escape(url)}"
142
+ else
143
+ return "/~"
144
+ end
145
+ end
146
+
147
+ # See syntax here: https://developer.linkedin.com/documents/field-selectors
148
+ def multiple_people_path(ids=[], urls=[])
149
+ if ids.nil? then ids = [] end
150
+ if urls.nil? then urls = [] end
151
+
152
+ ids = ids.map do |id|
153
+ if is_self(id) then "~" else "id=#{id}" end
154
+ end
155
+ urls = urls.map do |url|
156
+ if is_self(url) then "~" else "url=#{CGI.escape(url)}" end
157
+ end
158
+ return "::(#{(ids+urls).join(",")})"
159
+ end
160
+
161
+ def is_self(str)
162
+ str == "self" or str == "~"
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,40 @@
1
+ module LinkedIn
2
+ # Communications APIs
3
+ #
4
+ # @see http://developer.linkedin.com/documents/communications
5
+ class Communications < APIResource
6
+
7
+ # (Create) send a message from the authenticated user to a
8
+ # connection
9
+ #
10
+ # Permissions: w_messages
11
+ #
12
+ # @see http://developer.linkedin.com/documents/messaging-between-connections-api
13
+ # @see http://developer.linkedin.com/documents/invitation-api Invitation API
14
+ #
15
+ # @example
16
+ # api.send_message("subject", "body", ["person_1_id", "person_2_id"])
17
+ #
18
+ # @param [String] subject Subject of the message
19
+ # @param [String] body Body of the message, plain text only
20
+ # @param [Array<String>] recipient_paths a collection of
21
+ # profile paths that identify the users who will receive the
22
+ # message
23
+ # @return [void]
24
+ def send_message(subject, body, recipient_paths)
25
+ path = "/people/~/mailbox"
26
+
27
+ message = {
28
+ subject: subject,
29
+ body: body,
30
+ recipients: {
31
+ values: recipient_paths.map do |profile_path|
32
+ {person: {_path: "/people/#{profile_path}"} }
33
+ end
34
+ }
35
+ }
36
+
37
+ post(path, message)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,146 @@
1
+ module LinkedIn
2
+ # Companies API
3
+ #
4
+ # @see http://developer.linkedin.com/documents/companies Companies API
5
+ # @see http://developer.linkedin.com/documents/company-lookup-api-and-fields Company Fields
6
+ #
7
+ # The following API actions do not have corresponding methods in
8
+ # this module
9
+ #
10
+ # * Permissions Checking Endpoints for Company Shares
11
+ # * GET Suggested Companies to Follow
12
+ # * GET Company Products
13
+ #
14
+ # [(contribute here)](https://github.com/emorikawa/linkedin-oauth2)
15
+ class Companies < APIResource
16
+
17
+ # Retrieve a Company Profile
18
+ #
19
+ # @see http://developer.linkedin.com/documents/company-lookup-api-and-fields
20
+ #
21
+ # @macro company_path_options
22
+ # @option options [String] :scope
23
+ # @option options [String] :type
24
+ # @option options [String] :count
25
+ # @option options [String] :start
26
+ # @return [LinkedIn::Mash]
27
+ def company(options = {})
28
+ path = company_path(options)
29
+ get(path, options)
30
+ end
31
+
32
+ # Retrieve a feed of event items for a Company
33
+ #
34
+ # @see http://developer.linkedin.com/reading-company-shares
35
+ #
36
+ # @macro company_path_options
37
+ # @option options [String] :event-type
38
+ # @option options [String] :count
39
+ # @option options [String] :start
40
+ # @return [LinkedIn::Mash]
41
+ def company_updates(options={})
42
+ path = "#{company_path(options)}/updates"
43
+ get(path, options)
44
+ end
45
+
46
+ # Retrieve statistics for a particular company page
47
+ #
48
+ # Permissions: rw_company_admin
49
+ #
50
+ # @see http://developer.linkedin.com/documents/company-statistics
51
+ #
52
+ # @macro company_path_options
53
+ # @return [LinkedIn::Mash]
54
+ def company_statistics(options={})
55
+ path = "#{company_path(options)}/company-statistics"
56
+ get(path, options)
57
+ end
58
+
59
+ # Retrieve comments on a particular company update:
60
+ #
61
+ # @see http://developer.linkedin.com/reading-company-shares
62
+ #
63
+ # @param [String] update_key a update/update-key representing a
64
+ # particular company update
65
+ # @macro company_path_options
66
+ # @return [LinkedIn::Mash]
67
+ def company_updates_comments(update_key, options={})
68
+ path = "#{company_path(options)}/updates/key=#{update_key}/update-comments"
69
+ get(path, options)
70
+ end
71
+
72
+ # Retrieve likes on a particular company update:
73
+ #
74
+ # @see http://developer.linkedin.com/reading-company-shares
75
+ #
76
+ # @param [String] update_key a update/update-key representing a
77
+ # particular company update
78
+ # @macro company_path_options
79
+ # @return [LinkedIn::Mash]
80
+ def company_updates_likes(update_key, options={})
81
+ path = "#{company_path(options)}/updates/key=#{update_key}/likes"
82
+ get(path, options)
83
+ end
84
+
85
+ # Create a share for a company that the authenticated user
86
+ # administers
87
+ #
88
+ # Permissions: rw_company_admin
89
+ #
90
+ # @see http://developer.linkedin.com/creating-company-shares
91
+ # @see http://developer.linkedin.com/documents/targeting-company-shares Targeting Company Shares
92
+ #
93
+ # @param [String] company_id Company ID
94
+ # @macro share_input_fields
95
+ # @return [void]
96
+ def add_company_share(company_id, share)
97
+ path = "/companies/#{company_id}/shares"
98
+ defaults = {visibility: {code: "anyone"}}
99
+ post(path, defaults.merge(share))
100
+ end
101
+
102
+ # (Create) authenticated user starts following a company
103
+ #
104
+ # @see http://developer.linkedin.com/documents/company-follow-and-suggestions
105
+ #
106
+ # @param [String] company_id Company ID
107
+ # @return [void]
108
+ def follow_company(company_id)
109
+ path = "/people/~/following/companies"
110
+ post(path, {id: company_id})
111
+ end
112
+
113
+ # (Destroy) authenticated user stops following a company
114
+ #
115
+ # @see http://developer.linkedin.com/documents/company-follow-and-suggestions
116
+ #
117
+ # @param [String] company_id Company ID
118
+ # @return [void]
119
+ def unfollow_company(company_id)
120
+ path = "/people/~/following/companies/id=#{company_id}"
121
+ delete(path)
122
+ end
123
+
124
+
125
+ private ##############################################################
126
+
127
+
128
+ def company_path(options)
129
+ path = "/companies"
130
+
131
+ if domain = options.delete(:domain)
132
+ path += "?email-domain=#{CGI.escape(domain)}"
133
+ elsif id = options.delete(:id)
134
+ path += "/#{id}"
135
+ elsif url = options.delete(:url)
136
+ path += "/url=#{CGI.escape(url)}"
137
+ elsif name = options.delete(:name)
138
+ path += "/universal-name=#{CGI.escape(name)}"
139
+ elsif is_admin = options.delete(:is_admin)
140
+ path += "?is-company-admin=#{CGI.escape(is_admin)}"
141
+ else
142
+ path += "/~"
143
+ end
144
+ end
145
+ end
146
+ end