linkedin-oauth2 0.1.1 → 1.0.0

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.
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