linkedin2 0.0.16 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +2 -2
  5. data/.travis.yml +1 -1
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/lib/linkedin/api.rb +16 -0
  9. data/lib/linkedin/api/authentication.rb +5 -7
  10. data/lib/linkedin/api/companies.rb +16 -10
  11. data/lib/linkedin/api/invitation.rb +39 -0
  12. data/lib/linkedin/api/messaging.rb +24 -0
  13. data/lib/linkedin/api/network_updates.rb +5 -9
  14. data/lib/linkedin/api/people.rb +18 -0
  15. data/lib/linkedin/client.rb +34 -72
  16. data/lib/linkedin/configuration.rb +26 -11
  17. data/lib/linkedin/credentials.rb +27 -0
  18. data/lib/linkedin/errors.rb +45 -0
  19. data/lib/linkedin/faraday_middleware.rb +6 -4
  20. data/lib/linkedin/faraday_middleware/credentials_request.rb +31 -0
  21. data/lib/linkedin/faraday_middleware/{linkedin_error_response.rb → error_response.rb} +2 -10
  22. data/lib/linkedin/faraday_middleware/format_request.rb +17 -0
  23. data/lib/linkedin/faraday_middleware/user_agent_request.rb +10 -0
  24. data/lib/linkedin/fields.rb +58 -0
  25. data/lib/linkedin/industries.rb +199 -0
  26. data/lib/linkedin/response.rb +19 -0
  27. data/lib/linkedin/version.rb +1 -1
  28. data/lib/linkedin2.rb +11 -19
  29. data/linkedin.gemspec +16 -7
  30. data/spec/api/companies_spec.rb +8 -9
  31. data/spec/api/groups_spec.rb +3 -4
  32. data/spec/api/jobs_spec.rb +2 -3
  33. data/spec/api/network_updates_spec.rb +9 -15
  34. data/spec/api/{profiles_spec.rb → people_spec.rb} +11 -13
  35. data/spec/faraday_middleware/{linkedin_error_response_spec.rb → error_response_spec.rb} +8 -21
  36. data/spec/fixtures/requests/companies.yml +25 -15
  37. data/spec/fixtures/requests/invalid.yml +7 -7
  38. data/spec/fixtures/requests/network_updates.yml +12 -12
  39. data/spec/fixtures/requests/people.yml +380 -0
  40. data/spec/spec_helper.rb +18 -19
  41. data/spec/support/coverage.rb +14 -0
  42. data/spec/support/vcr.rb +9 -0
  43. data/spec/test_app.yml +3 -2
  44. metadata +119 -38
  45. data/lib/linkedin/api/industries.rb +0 -171
  46. data/lib/linkedin/api/permissions.rb +0 -42
  47. data/lib/linkedin/api/profiles.rb +0 -71
  48. data/lib/linkedin/error.rb +0 -29
  49. data/lib/linkedin/faraday_middleware/linkedin_format_request.rb +0 -26
  50. data/lib/linkedin/industry.rb +0 -33
  51. data/spec/fixtures/requests/profiles.yml +0 -201
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac298d199dd70fa33629431f471bd38f7247ce8c
4
- data.tar.gz: 0487998919736006a7dcc828a1d847a29cf138f4
3
+ metadata.gz: 97c526507b2313e93d3e8f8774e21ddac5b9a64b
4
+ data.tar.gz: 59b303455324238c1d9328d7e20cb165ea982218
5
5
  SHA512:
6
- metadata.gz: 7db8a53f200a4d425ac735d6f92fc2521b3caaf6d6fb878069abd874d7114b719797e1b63bbc5f15240fdce1a1035ae1179d3c30199af46e02dcb8acfe3b9fa5
7
- data.tar.gz: 9472503155331275a6ae09f763a887b9985f65c48950c38f02b5beb0375625a9c37f66e5a4c2a89854694941b02c7841c5de4e95f4569d09d407c39c6c50a43f
6
+ metadata.gz: b48333d3d41d0ecb1ac40cb11853ea92378822cb30e7c1906eba0841851d6f7b11ee630853d472ec91193284b2d703fb6741696f8c4bb5fcb31e3c8667f2a69e
7
+ data.tar.gz: 8d3f033c03d7232f64825b7a0b3bb8577e33e4d2455506298c9ae07de9b9389a2ad6dca4f10f5bf43c2236f1f7c906635ad2684f87fe78d6a3a4ca36f535e319
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-pro
2
+ repo_token: eqey1MAhYCCz33208mWozM0Z3DwMxvGCR
data/.gitignore CHANGED
@@ -16,3 +16,5 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  linkedin.yml
19
+ .DS_Store
20
+ .ruby-version
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
+ --format documentation
1
2
  --color
2
- --format=nested
3
- --backtrace
3
+ --require spec_helper
data/.travis.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.9.3"
4
3
  - "2.0.0"
4
+ - "2.1.2"
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # LinkedIn 2
2
- [![Build Status](https://travis-ci.org/bobbrez/linkedin2.png?branch=master)](https://travis-ci.org/bobbrez/linkedin2)
3
- [![Code Climate](https://codeclimate.com/github/bobbrez/linkedin2.png)](https://codeclimate.com/github/bobbrez/linkedin2)
4
-
2
+ [![Build Status](https://travis-ci.org/bobbrez/linkedin2.svg?branch=master)](https://travis-ci.org/bobbrez/linkedin2)
3
+ [![Coverage Status](https://img.shields.io/coveralls/bobbrez/linkedin2.svg)](https://coveralls.io/r/bobbrez/linkedin2)
4
+ [![Code Climate](https://codeclimate.com/github/bobbrez/linkedin2/badges/gpa.svg)](https://codeclimate.com/github/bobbrez/linkedin2)
5
5
  A modernized LinkedIn Ruby client.
6
6
 
7
7
  ## Installation
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  desc 'Open an irb session preloaded with this library'
5
5
  task :console do
6
- sh 'irb -rubygems -I lib -r linkedin2.rb'
6
+ sh 'irb -rubygems -rpry -I lib -r linkedin2.rb'
7
7
  end
8
8
 
9
9
  RSpec::Core::RakeTask.new
@@ -0,0 +1,16 @@
1
+ module LinkedIn
2
+ module API
3
+ Dir[ File.expand_path('../api/**/*.rb', __FILE__) ].each { |f| require f }
4
+
5
+ def self.included(base)
6
+ base.send :include, LinkedIn::API::Authentication,
7
+ LinkedIn::API::Companies,
8
+ LinkedIn::API::Groups,
9
+ LinkedIn::API::Invitation,
10
+ LinkedIn::API::Jobs,
11
+ LinkedIn::API::Messaging,
12
+ LinkedIn::API::NetworkUpdates,
13
+ LinkedIn::API::People
14
+ end
15
+ end
16
+ end
@@ -1,21 +1,19 @@
1
1
  module LinkedIn
2
2
  module API
3
3
  module Authentication
4
- attr_reader :state
5
-
6
- def authorize_url(params = {})
7
- params.reverse_merge! config.to_h.slice :scope, :state, :redirect_uri
4
+ def authorize_url(**params)
5
+ params.reverse_merge! configuration.to_h.slice :scope, :state, :redirect_uri
8
6
  params[:scope] = serialize_scope params[:scope]
9
- auth_code.authorize_url params
7
+ credentials.auth_code.authorize_url params
10
8
  end
11
9
 
12
10
  def request_access_token(authorization_code, params = {})
13
11
  raise Error::CSRF.new state, params[:state] if params[:state] && params[:state] != state
14
12
 
15
- params.reverse_merge! redirect_uri: config.redirect_uri
13
+ params.reverse_merge! redirect_uri: configuration.redirect_uri
16
14
  opts = { mode: :query, param_name: 'oauth2_access_token' }
17
15
 
18
- self.access_token = auth_code.get_token authorization_code, params, opts
16
+ credentials.auth_code.get_token authorization_code, params, opts
19
17
  end
20
18
 
21
19
  private
@@ -1,19 +1,25 @@
1
1
  module LinkedIn
2
2
  module API
3
3
  module Companies
4
- def company(options={})
5
- selector = if options[:selector].respond_to? :each
6
- "::(#{options[:selector].join(',')})"
7
- else
8
- "/#{options[:selector]}"
9
- end
4
+ def company(*selector, filter: nil, **opts)
5
+ root = 'companies'
6
+
7
+ selector.compact!
8
+ selector = selector.first if selector.size == 1
10
9
 
11
- fields = options[:fields]
12
- fields_string = fields.blank? ? '' : ":(#{Permissions.render_permissions fields})"
10
+ unless filter.blank?
11
+ filter = Hash[ *filter.to_s.split('=') ] unless filter.respond_to? :keys
12
+ opts[:params] = {} if opts[:params].blank?
13
+ opts[:params].to_h.merge! filter
14
+ selector = nil
15
+ end
13
16
 
14
- filter = "?#{options[:filter]}" if options[:filter].present?
17
+ if selector.respond_to? :each
18
+ selector = "companies::(#{ selector.map(&:to_param).join(',') })"
19
+ root = nil
20
+ end
15
21
 
16
- get "v1/companies#{selector}#{fields_string}#{filter}"
22
+ execute root, opts.merge(selector: selector)
17
23
  end
18
24
  end
19
25
  end
@@ -0,0 +1,39 @@
1
+ module LinkedIn
2
+ module API
3
+ module Invitation
4
+ def connect_with(recipient_selector, subject, message, type: :friend, x_auth_token: nil)
5
+ if x_auth_token.blank?
6
+ target_profile = profile recipient_selector, Fields::PROFILE_API_STD_PROFILE_REQ
7
+ x_auth_token = target_profile.body.apiStandardProfileRequest_.headers_.values_.first.value
8
+ end
9
+
10
+ x_auth_name, x_auth_value = *x_auth_token.split(':')
11
+
12
+ connection_body = build_connection_body selector: recipient_selector, subject: subject,
13
+ message: message, type: type,
14
+ auth_name: x_auth_name, auth_value: x_auth_value
15
+
16
+ execute 'people/~/mailbox', method: :post, body: connection_body
17
+ end
18
+
19
+ private
20
+
21
+ def build_connection_body(**args)
22
+ [:selector, :subject, :message, :type, :auth_name, :auth_value].each do |key|
23
+ raise ArgumentError.new "Missing / blank argument `#{key}`" if args[key].blank?
24
+ end
25
+
26
+ { recipients: { values: [ { person: { _path: "/people/#{args[:selector].to_param}" } } ] },
27
+ subject: args[:subject],
28
+ body: args[:message],
29
+ 'item-content' => {
30
+ 'invitation-request' => {
31
+ 'connect-type' => args[:type],
32
+ authorization: { name: args[:auth_name], value: args[:auth_value] }
33
+ }
34
+ }
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ module LinkedIn
2
+ module API
3
+ module Messaging
4
+ def message(subject, message, recipient_selectors, **opts)
5
+ message_body = build_message_body selectors: [recipient_selectors].flatten,
6
+ subject: subject, message: message
7
+
8
+ execute 'people/~/mailbox', opts.merge(method: :post, body: message_body)
9
+ end
10
+
11
+ private
12
+
13
+ def build_message_body(**args)
14
+ [:selectors, :subject, :message].each do |key|
15
+ raise ArgumentError.new "Missing / blank argument `#{key}`" if args[key].blank?
16
+ end
17
+
18
+ recipients = args[:selectors].map { |sel| { person: { _path: "/people/#{sel.to_param}" } } }
19
+
20
+ { recipients: { values: recipients }, subject: args[:subject], body: args[:message] }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,16 +1,12 @@
1
1
  module LinkedIn
2
2
  module API
3
3
  module NetworkUpdates
4
- def network_updates(options = {})
5
- get ['v1/people/~/network/updates', options[:selector], options[:attached_object_type]].compact.join '/'
6
- end
7
-
8
- def network_update_comments(options = {})
9
- network_updates(options.merge attached_object_type: 'update-comments')
10
- end
4
+ def network_updates(selector = '~', key: nil, type: nil, **opts)
5
+ path = ['network','updates']
6
+ path << { key: key }.to_param if key
7
+ path << type
11
8
 
12
- def network_update_likes(options = {})
13
- network_updates(options.merge attached_object_type: 'likes')
9
+ execute 'people', opts.merge(selector: selector, path: path.compact.join('/'))
14
10
  end
15
11
  end
16
12
  end
@@ -0,0 +1,18 @@
1
+ module LinkedIn
2
+ module API
3
+ module People
4
+ def profile(selector = '~', **opts)
5
+ execute 'people', opts.merge(selector: selector)
6
+ end
7
+
8
+ def connections(selector = '~', **opts)
9
+ execute 'people', opts.merge(selector: selector, path: 'connections')
10
+ end
11
+
12
+ def people_search(query, **opts)
13
+ wrapped_fields = opts[:fields].blank? ? nil : { people: opts[:fields] }
14
+ execute 'people-search', opts.merge(params: query, fields: wrapped_fields)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,96 +1,58 @@
1
1
  module LinkedIn
2
2
  class Client
3
- extend Forwardable
4
-
5
3
  include Configuration
6
- include LinkedIn::API::Authentication
7
- include LinkedIn::API::Profiles
8
- include LinkedIn::API::NetworkUpdates
9
- include LinkedIn::API::Companies
10
-
11
- HTTP_METHODS = [:get, :post, :put, :patch, :delete, :headers].freeze
12
-
13
- attr_writer :profile_fields
14
- attr_reader :access_token
4
+ include LinkedIn::API
15
5
 
16
- def_delegators :@access_token, :expires?, :expired?, :request
17
-
18
- def initialize(options={}, &block)
19
- configure options, &block
20
- self.access_token ||= self.config.access_token.to_s
6
+ def initialize(**config, &block)
7
+ configure config, &block
21
8
  end
22
9
 
23
- def auth_code
24
- connection.auth_code
10
+ def credentials
11
+ @credentials ||= Credentials.new configuration
25
12
  end
26
13
 
27
14
  def connection
28
- @connection ||= OAuth2::Client.new config.key, config.secret, oauth2_options do |faraday|
29
- faraday.request :url_encoded
30
- faraday.request :json
31
- faraday.request :linkedin_format, config.request_format
32
-
33
- faraday.response :mashify
34
- faraday.response :linkedin_errors
35
- faraday.response :logger, config.logger
36
- faraday.response :json, content_type: /\bjson$/
37
-
38
- faraday.adapter :net_http
15
+ @connection ||= Faraday.new 'https://api.linkedin.com' do |conn|
16
+ conn.request :json
17
+ conn.request :url_encoded
18
+ conn.request :linkedin_credentials, configuration
19
+ conn.request :linkedin_format
20
+ conn.request :linkedin_user_agent
21
+
22
+ conn.response :linkedin_errors
23
+ conn.response :mashify
24
+ conn.response :logger, configuration.logger
25
+ conn.response :json, content_type: /\bjson$/
26
+
27
+ conn.adapter Faraday.default_adapter
39
28
  end
40
29
  end
41
30
 
42
- def access_token=(token)
43
- if token.kind_of? String
44
- options = { access_token: token, mode: :query, param_name: 'oauth2_access_token' }
45
- return @access_token = OAuth2::AccessToken.from_hash(connection, options)
46
- end
47
-
48
- @access_token = token
31
+ def headers
32
+ @headers ||= {}
49
33
  end
50
34
 
51
- def profile_fields
52
- return @profile_fields if @profile_fields
53
- scopes = config.scope unless config.scope.respond_to?(:values)
54
- scopes ||= config.scope
55
-
56
- @profile_fields = scopes.reduce([]) { |fields, scope| fields + LinkedIn.send(scope) }
57
- end
58
-
59
- def method_missing(method, *args, &body)
60
- return simple_request(method, args[0], (args[1] || {}), &body) if HTTP_METHODS.include? method
61
- super
62
- end
63
-
64
- def self.default_config
65
- {
66
- request_format: :json,
67
-
68
- key: nil,
69
- secret: nil,
70
- access_token: nil,
71
-
72
- scope: ['r_basicprofile'],
73
- state: Utils.generate_random_state,
74
- redirect_uri: 'http://localhost',
75
-
76
- logger: Logger.new('/dev/null')
77
- }
35
+ def params
36
+ @params ||= {}
78
37
  end
79
38
 
80
39
  private
81
40
 
82
- def simple_request(method, path, options={}, &body)
83
- request(method, path, options, &body).body
41
+ def override(global, overrides)
42
+ global.to_h.merge overrides.to_h
84
43
  end
85
44
 
86
- def auth_code
87
- connection.auth_code unless connection.nil?
88
- end
45
+ def execute(root, method: :get, selector: nil, fields: nil, **opts)
46
+ rendered_fields = Fields.render fields
47
+ query = ['v1', root, selector.to_param, opts[:path]].compact.join('/').concat(rendered_fields)
48
+
49
+ response = connection.send method, query do |req|
50
+ req.headers.update override(@headers, opts[:headers])
51
+ req.params.update override(@params, opts[:params])
52
+ req.body = opts[:body].to_json if opts[:body]
53
+ end
89
54
 
90
- def oauth2_options
91
- { site: 'https://api.linkedin.com',
92
- authorize_url: 'https://www.linkedin.com/uas/oauth2/authorization',
93
- token_url: 'https://www.linkedin.com/uas/oauth2/accessToken' }
55
+ Response.new response
94
56
  end
95
57
  end
96
58
  end
@@ -1,41 +1,56 @@
1
1
  module LinkedIn
2
2
  module Configuration
3
3
  module ClassConfiguration
4
- def config
5
- @config ||= reset
4
+ def default_config
5
+ {
6
+ request_format: :json,
7
+
8
+ app_key: nil,
9
+ app_secret: nil,
10
+ access_token: nil,
11
+
12
+ scope: ['r_basicprofile'],
13
+ redirect_uri: 'http://localhost',
14
+
15
+ logger: Logger.new('/dev/null')
16
+ }
17
+ end
18
+
19
+ def configuration
20
+ @configuration ||= OpenStruct.new default_config
6
21
  end
7
22
 
8
23
  def reset
9
- @config = OpenStruct.new default_config
24
+ configuration.marshal_load default_config
10
25
  end
11
26
  end
12
27
 
13
28
  module InstanceConfiguration
14
- def config
15
- @config ||= reset
29
+ def configuration
30
+ @configuration ||= self.class.configuration.dup
16
31
  end
17
32
 
18
33
  def reset
19
- @config = self.class.config.dup
34
+ @configuration.marshal_load self.class.configuration
20
35
  end
21
36
  end
22
37
 
23
38
  module BaseConfiguration
24
39
  def configure(config={}, &block)
25
- self.config.marshal_load self.config.marshal_dump.merge(config)
40
+ self.configuration.marshal_load self.configuration.marshal_dump.merge(config)
26
41
 
27
- yield self.config if block_given?
42
+ yield self.configuration if block_given?
28
43
 
29
- self.config
44
+ self.configuration
30
45
  end
31
46
 
32
- def load(file_path='linkedin.yml')
47
+ def load(file_path = 'linkedin.yml')
33
48
  config = YAML::load(File.open(file_path)).symbolize_keys
34
49
  configure config
35
50
  end
36
51
 
37
52
  def config(*keys)
38
- config.marshal_dump.slice(*keys)
53
+ configuration.marshal_dump.slice(*keys)
39
54
  end
40
55
  end
41
56