shutterstock-v2 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.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +6 -0
  6. data/Dockerfile +17 -0
  7. data/Gemfile +3 -0
  8. data/Guardfile +9 -0
  9. data/README.md +377 -0
  10. data/Rakefile +19 -0
  11. data/lib/client/auth.rb +48 -0
  12. data/lib/client/categories.rb +11 -0
  13. data/lib/client/category.rb +16 -0
  14. data/lib/client/client.rb +72 -0
  15. data/lib/client/collection.rb +151 -0
  16. data/lib/client/collections.rb +16 -0
  17. data/lib/client/configuration.rb +20 -0
  18. data/lib/client/contributor.rb +15 -0
  19. data/lib/client/driver.rb +38 -0
  20. data/lib/client/error.rb +20 -0
  21. data/lib/client/image.rb +139 -0
  22. data/lib/client/image_assets.rb +56 -0
  23. data/lib/client/image_size_details.rb +26 -0
  24. data/lib/client/images.rb +27 -0
  25. data/lib/client/license.rb +67 -0
  26. data/lib/client/licenses.rb +23 -0
  27. data/lib/client/model.rb +15 -0
  28. data/lib/client/models.rb +11 -0
  29. data/lib/client/request.rb +41 -0
  30. data/lib/client/subscription.rb +35 -0
  31. data/lib/client/subscription_allotment.rb +19 -0
  32. data/lib/client/subscription_license_format.rb +16 -0
  33. data/lib/client/subscription_license_formats.rb +12 -0
  34. data/lib/client/subscription_price.rb +13 -0
  35. data/lib/client/subscriptions.rb +37 -0
  36. data/lib/client/thumbnail.rb +17 -0
  37. data/lib/client/user.rb +68 -0
  38. data/lib/client/version.rb +3 -0
  39. data/lib/shutterstock-v2.rb +31 -0
  40. data/shutterstock-v2.gemspec +30 -0
  41. data/spec/cassettes/Shutterstock_Auth_get_access_token/returns_hash_with_access_token.yml +86 -0
  42. data/spec/cassettes/Shutterstock_Auth_get_authorize_url/returns_URL_to_redirect_user_to_passing_redirect_url_an.yml +48 -0
  43. data/spec/cassettes/Shutterstock_Categories/parses_params_correctly.yml +53 -0
  44. data/spec/cassettes/Shutterstock_Categories/returns_all_cats_for_an_image.yml +53 -0
  45. data/spec/cassettes/Shutterstock_Collection/add_image_adds_image_using_Image_new.yml +48 -0
  46. data/spec/cassettes/Shutterstock_Collection/add_image_adds_image_using_id.yml +48 -0
  47. data/spec/cassettes/Shutterstock_Collection/add_image_error_raises_error_if_collection_does_not_exist.yml +52 -0
  48. data/spec/cassettes/Shutterstock_Collection/create_creates_a_collection.yml +52 -0
  49. data/spec/cassettes/Shutterstock_Collection/destroy_destroys_a_collection.yml +93 -0
  50. data/spec/cassettes/Shutterstock_Collection/find_adds_an_image.yml +97 -0
  51. data/spec/cassettes/Shutterstock_Collection/find_converts_list_to_images.yml +101 -0
  52. data/spec/cassettes/Shutterstock_Collection/find_finds_a_collection_given_an_id.yml +52 -0
  53. data/spec/cassettes/Shutterstock_Collection/find_raises_error_for_invalid_collection_id.yml +50 -0
  54. data/spec/cassettes/Shutterstock_Collection/find_removes_an_image.yml +95 -0
  55. data/spec/cassettes/Shutterstock_Collection/list_returns_all_collections_for_the_current_user.yml +53 -0
  56. data/spec/cassettes/Shutterstock_Collection/remove_image_removes_image_using_Image_new.yml +46 -0
  57. data/spec/cassettes/Shutterstock_Collection/remove_image_removes_image_using_id.yml +46 -0
  58. data/spec/cassettes/Shutterstock_Collection/update_changes_the_name_of_a_collection.yml +97 -0
  59. data/spec/cassettes/Shutterstock_Collections/returns_all_collections_for_current_user.yml +53 -0
  60. data/spec/cassettes/Shutterstock_Image/fill_fills_all_details_of_current_image.yml +109 -0
  61. data/spec/cassettes/Shutterstock_Image/fills_all_details_of_current_image.yml +109 -0
  62. data/spec/cassettes/Shutterstock_Image/find_places_image_data_into_correct_fields.yml +56 -0
  63. data/spec/cassettes/Shutterstock_Image/find_places_more_image_data_into_correct_fields.yml +53 -0
  64. data/spec/cassettes/Shutterstock_Image/find_returns_an_Image_object.yml +56 -0
  65. data/spec/cassettes/Shutterstock_Image/finds_similar_images_given_an_image.yml +125 -0
  66. data/spec/cassettes/Shutterstock_Image/returns_an_Image_object.yml +56 -0
  67. data/spec/cassettes/Shutterstock_Image/returns_similar_images.yml +72 -0
  68. data/spec/cassettes/Shutterstock_Image/search_searches_for_images_based_on_searchterm.yml +73 -0
  69. data/spec/cassettes/Shutterstock_Image/search_searches_using_more_than_one_parameter.yml +77 -0
  70. data/spec/cassettes/Shutterstock_Image/searches_for_images_based_on_searchterm.yml +73 -0
  71. data/spec/cassettes/Shutterstock_Image/searches_using_more_than_one_parameter.yml +77 -0
  72. data/spec/cassettes/Shutterstock_Image/similar_finds_similar_images_given_an_image.yml +125 -0
  73. data/spec/cassettes/Shutterstock_Image/similar_returns_similar_images.yml +72 -0
  74. data/spec/cassettes/Shutterstock_ImageAssets/find_calculates_fields_correctly.yml +53 -0
  75. data/spec/cassettes/Shutterstock_ImageAssets/find_places_image_data_into_correct_fields.yml +53 -0
  76. data/spec/cassettes/Shutterstock_Images/fill_fills_details_of_all_images_in_list_all_images_now_have_keywords.yml +1144 -0
  77. data/spec/cassettes/Shutterstock_Images/returns_similar_images.yml +126 -0
  78. data/spec/cassettes/Shutterstock_License/license_editorial_acknowledgement_returns_error_if_requesting_an_image.yml +53 -0
  79. data/spec/cassettes/Shutterstock_License/license_editorial_acknowledgement_returns_licenced_image_if_ed_ack_is_.yml +52 -0
  80. data/spec/cassettes/Shutterstock_License/license_license_from_image_object_sends_editorial_acknowledgement_when.yml +160 -0
  81. data/spec/cassettes/Shutterstock_License/license_license_from_image_object_using_found_subscription.yml +108 -0
  82. data/spec/cassettes/Shutterstock_License/license_license_from_image_object_vector_image.yml +108 -0
  83. data/spec/cassettes/Shutterstock_License/license_multiple_licenses_multiple_images.yml +52 -0
  84. data/spec/cassettes/Shutterstock_License/license_multiple_licenses_multiple_images_from_Image_new.yml +52 -0
  85. data/spec/cassettes/Shutterstock_License/license_multiple_returns_error_in_Licenses_when_incorrect_image_used.yml +53 -0
  86. data/spec/cassettes/Shutterstock_License/license_single_licencing_same_image_twice_does_not_give_error.yml +101 -0
  87. data/spec/cassettes/Shutterstock_License/license_single_license_fails_when_subscription_doesn_t_allow.yml +49 -0
  88. data/spec/cassettes/Shutterstock_License/license_single_licenses_single_image.yml +52 -0
  89. data/spec/cassettes/Shutterstock_License/license_single_licenses_single_image_from_Image_new.yml +52 -0
  90. data/spec/cassettes/Shutterstock_License/license_single_licenses_single_image_from_hash.yml +52 -0
  91. data/spec/cassettes/Shutterstock_License/license_single_licenses_single_image_with_smaller_size.yml +52 -0
  92. data/spec/cassettes/Shutterstock_License/license_single_licenses_vector_image.yml +52 -0
  93. data/spec/cassettes/Shutterstock_License/license_single_licensing_same_image_twice_does_not_give_error.yml +101 -0
  94. data/spec/cassettes/Shutterstock_License/license_single_returns_error_when_incorrect_image_used.yml +103 -0
  95. data/spec/cassettes/Shutterstock_License/license_single_returns_error_when_incorrect_subscription_used.yml +53 -0
  96. data/spec/cassettes/Shutterstock_Models/parses_params_correctly.yml +53 -0
  97. data/spec/cassettes/Shutterstock_Models/returns_all_models_for_an_image.yml +53 -0
  98. data/spec/cassettes/Shutterstock_Subscription/allows_image_size_download_correctly_finds_if_image_size_is_possi.yml +59 -0
  99. data/spec/cassettes/Shutterstock_Subscription/new_parses_info_on_user_correctly.yml +59 -0
  100. data/spec/cassettes/Shutterstock_Subscription/new_returns_a_subscription_for_current_user.yml +59 -0
  101. data/spec/cassettes/Shutterstock_SubscriptionLicenseFormat/new_parses_info_correctly.yml +59 -0
  102. data/spec/cassettes/Shutterstock_SubscriptionLicenseFormat/new_returns_a_subscription_license_format_for_curren.yml +59 -0
  103. data/spec/cassettes/Shutterstock_Subscriptions/subscriptions_active_expired_total_subscriptions.yml +59 -0
  104. data/spec/cassettes/Shutterstock_Subscriptions/subscriptions_active_returns_only_active_subscriptions.yml +59 -0
  105. data/spec/cassettes/Shutterstock_Subscriptions/subscriptions_downloads_left_only_subscriptions_with_downloads_l.yml +59 -0
  106. data/spec/cassettes/Shutterstock_Subscriptions/subscriptions_expired_returns_only_expired_subscriptions.yml +59 -0
  107. data/spec/cassettes/Shutterstock_Subscriptions/subscriptions_find_subscription_for_image_size_finds_subs_to_use.yml +59 -0
  108. data/spec/cassettes/Shutterstock_Subscriptions/subscriptions_returns_all_subscriptions_for_current_user.yml +59 -0
  109. data/spec/cassettes/Shutterstock_User/find_parses_info_on_user_correctly.yml +52 -0
  110. data/spec/cassettes/Shutterstock_User/find_returns_object_for_current_user.yml +52 -0
  111. data/spec/client/auth_spec.rb +36 -0
  112. data/spec/client/categories_spec.rb +26 -0
  113. data/spec/client/category_spec.rb +16 -0
  114. data/spec/client/client_spec.rb +32 -0
  115. data/spec/client/collection_spec.rb +159 -0
  116. data/spec/client/collections_spec.rb +18 -0
  117. data/spec/client/configuration_spec.rb +10 -0
  118. data/spec/client/contributor_spec.rb +14 -0
  119. data/spec/client/driver_spec.rb +20 -0
  120. data/spec/client/image_assets_spec.rb +51 -0
  121. data/spec/client/image_size_details_spec.rb +33 -0
  122. data/spec/client/image_spec.rb +102 -0
  123. data/spec/client/images_spec.rb +31 -0
  124. data/spec/client/license_spec.rb +234 -0
  125. data/spec/client/model_spec.rb +14 -0
  126. data/spec/client/models_spec.rb +23 -0
  127. data/spec/client/request_spec.rb +79 -0
  128. data/spec/client/subscription_allotment_spec.rb +25 -0
  129. data/spec/client/subscription_license_format_spec.rb +29 -0
  130. data/spec/client/subscription_price_spec.rb +17 -0
  131. data/spec/client/subscription_spec.rb +60 -0
  132. data/spec/client/subscriptions_spec.rb +54 -0
  133. data/spec/client/thumbnail_spec.rb +21 -0
  134. data/spec/client/user_spec.rb +37 -0
  135. data/spec/spec_helper.rb +88 -0
  136. metadata +467 -0
@@ -0,0 +1,19 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'rubocop/rake_task'
3
+
4
+ task :default => :spec
5
+ task :test => :spec
6
+
7
+ desc "Run spec tests"
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.rspec_opts = "--color"
10
+ end
11
+
12
+ desc 'Run RuboCop on the lib directory'
13
+ Rubocop::RakeTask.new(:rubocop) do |task|
14
+ task.patterns = ['lib/**/*.rb']
15
+ # only show the files with failures
16
+ task.formatters = ['progress']
17
+ # don't abort rake on failure
18
+ task.fail_on_error = false
19
+ end
@@ -0,0 +1,48 @@
1
+ module Shutterstock
2
+ class Auth < Driver
3
+
4
+ def self.get_authorize_url(redirect_uri:, scope:, state: 'state_%d' % [Time.now.to_i])
5
+ resp = client.request do |r|
6
+ r.path "/v2/oauth/authorize"
7
+ r.method :get
8
+ r.send_authorization false
9
+ r.success_status 302
10
+ r.params ({ scope: scope,
11
+ client_id: client.config.client_id,
12
+ response_type: 'code',
13
+ state: state,
14
+ redirect_uri: redirect_uri })
15
+ end
16
+ redirect_location = resp.headers[:location]
17
+ returned_state = ensure_next_url_contains_sent_state(redirect_location, state)
18
+
19
+ redirect_location
20
+ end
21
+
22
+ def self.get_access_token(code:, grant_type: 'authorization_code', contributor: 'customer')
23
+ resp = client.request do |r|
24
+ r.path "/v2/oauth/access_token"
25
+ r.method :post
26
+ r.send_authorization false
27
+ r.content_type 'application/x-www-form-urlencoded'
28
+ r.body ({ grant_type: grant_type,
29
+ client_id: client.config.client_id,
30
+ client_secret: client.config.client_secret,
31
+ code: code,
32
+ contributor: contributor })
33
+ end
34
+ resp.body
35
+ end
36
+
37
+ private
38
+
39
+ def self.ensure_next_url_contains_sent_state(url, state)
40
+ # https://accounts.shutterstock.com/login?next=%2Foauth%2Fauthorize%3Fclient_id%3D<SSTK_CLIENT_ID...
41
+ next_query = CGI.parse(URI.parse(url).query)["next"].first
42
+ # /oauth/authorize?client_id=<SSTK_CLIENT_ID>&redirect_uri=...
43
+ returned_state = CGI.parse(URI.parse(next_query).query)["state"].first
44
+ raise(RuntimeError, "State in redirected url (%s) not identical to sent state (%s)" % [returned_state,state]) unless (returned_state==state)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ module Shutterstock
2
+ class Categories < Array
3
+ attr_reader :raw_data
4
+ def initialize(raw_data)
5
+ @raw_data = raw_data
6
+ super( @raw_data.map{ |cat| Category.new(cat) } )
7
+ self
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Shutterstock
2
+ class Category < Driver
3
+ attr_reader :id, :name
4
+
5
+ include Equalizer.new(:id)
6
+
7
+ def initialize(params={})
8
+ @hash = params
9
+ @id = params["id"].to_i
10
+ @name = params["name"]
11
+ end
12
+
13
+ # TODO API calls
14
+
15
+ end
16
+ end
@@ -0,0 +1,72 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'json'
4
+ require 'singleton'
5
+
6
+ module Shutterstock
7
+
8
+ class Client
9
+ include Singleton
10
+
11
+ # @return [Configuration] Config instance
12
+ attr_reader :config
13
+
14
+ # Request options
15
+ attr_accessor :options
16
+
17
+ # Creates a new {Client} instance and yields {#config}.
18
+ # Requires a block to be given.
19
+ def configure
20
+ raise AppNotConfigured, "block not given" unless block_given?
21
+
22
+ @config = Configuration.new
23
+ yield config
24
+
25
+ @config.api_url ||= "https://api.shutterstock.com/v2"
26
+ raise AppNotConfigured, "Client ID not provided" if config.client_id.nil?
27
+ raise AppNotConfigured, "Client Secret not provided" if config.client_secret.nil?
28
+ # raise AppNotConfigured, "Access Token not provided" if config.access_token.nil? # Not needed for auth requests
29
+
30
+ @options = {
31
+ base_uri: config.api_url,
32
+ client_id: config.client_id,
33
+ client_secret: config.client_secret,
34
+ access_token: config.access_token
35
+ }
36
+
37
+ end
38
+
39
+ def configured?
40
+ !config.nil?
41
+ end
42
+
43
+ def connection
44
+ @connection ||= Faraday.new(config.api_url) do |conn|
45
+ conn.response :json, content_type: /\bjson$/
46
+ conn.adapter Faraday.default_adapter
47
+ end
48
+ end
49
+
50
+ def request(&block)
51
+ req = Request.new(&block)
52
+ api(req)
53
+ end
54
+
55
+ def api(request)
56
+
57
+ raise AppNotConfigured, "Access Token not provided" if options[:access_token].nil? && request.send_authorization
58
+
59
+ content_type = request.content_type
60
+ response = connection.run_request(request.method, nil, nil, nil) do |req|
61
+ req.url request.path
62
+ req.headers['Authorization'] = 'Bearer '+options[:access_token] if request.send_authorization
63
+ req.headers['Content-Type'] = request.content_type
64
+ req.params = request.params if request.params
65
+ req.body = (content_type =~ /json/ ? request.body.to_json : URI.encode_www_form(request.body)) if request.body
66
+ end
67
+ raise FailedResponse.new("Something went wrong: #{response.status}: #{response.body}", response.status) unless (response.status == request.success_status)
68
+ response
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,151 @@
1
+ module Shutterstock
2
+ class Collection < Driver
3
+ attr_reader :id, :name, :share_url, :share_code, :total_count, :created, :updated, :items_updated, :cover_item, :hash
4
+
5
+ include Equalizer.new(:id)
6
+
7
+ def initialize(params={})
8
+ @hash = params
9
+ @id = params["id"].to_i
10
+ @name = params["name"]
11
+ @share_code = params["share_code"]
12
+ @share_url = params["share_url"]
13
+ @total_count = params["total_item_count"].to_i
14
+ @created = to_date(params["created_time"])
15
+ @updated = to_date(params["updated_time"])
16
+ @items_updated = to_date(params["items_updated_time"])
17
+ @cover_item = Image.new(params["cover_item"]) if params["cover_item"]
18
+ end
19
+
20
+ def self.find(id)
21
+ resp = client.request do
22
+ path "/v2/images/collections/#{id}"
23
+ method :get
24
+ end
25
+ self.new(resp.body)
26
+ end
27
+
28
+ def find
29
+ self.class.find(self.id)
30
+ end
31
+
32
+ def self.create(name)
33
+ raise IncorrectArguments, "collection name not provided" unless name
34
+
35
+ resp = client.request do |r|
36
+ r.path "/v2/images/collections"
37
+ r.method :post
38
+ r.body ({ name: name })
39
+ r.success_status 201
40
+ end
41
+
42
+ resp_hash = resp.body
43
+ resp_hash.merge!("name" => name)
44
+ self.new(resp_hash)
45
+ end
46
+
47
+ def self.list
48
+ resp = client.request do
49
+ path "/v2/images/collections"
50
+ method :get
51
+ end
52
+ Collections.new(resp.body)
53
+ end
54
+
55
+ def self.update(id:, name:)
56
+ raise IncorrectArguments, "collection name not provided" unless name
57
+
58
+ resp = client.request do |r|
59
+ r.path "/v2/images/collections/#{id}"
60
+ r.method :post
61
+ r.body ({ name: name })
62
+ r.success_status 204
63
+ end
64
+ end
65
+
66
+ def self.destroy(id)
67
+ resp = client.request do |r|
68
+ r.path "/v2/images/collections/#{id}"
69
+ r.method :delete
70
+ r.success_status 204
71
+ end
72
+ nil
73
+ end
74
+
75
+ def destroy
76
+ self.class.destroy(self.id)
77
+ end
78
+
79
+ # collection.add_image({collection_id: 1234556, image_id: 98765432})
80
+ def self.add_image(id:, image_id:)
81
+
82
+ raise IncorrectArguments, "collection id not provided" unless id
83
+ raise IncorrectArguments, "image id not provided" unless image_id
84
+
85
+ resp = client.request do |r|
86
+ r.path "/v2/images/collections/#{id}/items"
87
+ r.method :post
88
+ r.body ({ items: image_ids_to_array_of_hash(image_id)})
89
+ r.success_status 204
90
+ end
91
+ self
92
+ end
93
+
94
+ # image_id can be a single or array of either image ids or Image objects
95
+ def add_image(image_id)
96
+ self.class.add_image({id: self.id, image_id: image_id})
97
+ end
98
+
99
+ def self.remove_image(id:, image_id:)
100
+
101
+ resp = client.request do |r|
102
+ r.path "/v2/images/collections/#{id}/items"
103
+ r.method :delete
104
+ r.params image_ids_to_hash_of_array(image_id)
105
+ r.success_status 204
106
+ end
107
+ self
108
+ end
109
+
110
+ def remove_image(image_id)
111
+ self.class.remove_image(id: self.id, image_id: image_id)
112
+ end
113
+
114
+ def self.items(id)
115
+ raise IncorrectArguments, "collection id not provided" unless id
116
+
117
+ resp = client.request do |r|
118
+ r.path "/v2/images/collections/#{id}/items"
119
+ r.method :get
120
+ end
121
+ Images.new(resp.body["data"])
122
+ end
123
+
124
+ def items
125
+ self.class.items(self.id)
126
+ end
127
+
128
+
129
+
130
+ private
131
+
132
+ # for post request
133
+ # image_id from [1,2,Image.new("id" => 3)] => [{id: "1"},{id: "2"},{id: "3"}]
134
+ # from 1 => [{id: "1"}]
135
+ # from Image.new("id" => 1) => [{id: "1"}]
136
+ def self.image_ids_to_array_of_hash(image_ids)
137
+ Array(image_ids).map{|iid| { id: (iid.is_a?(Image) ? iid.id : iid).to_s} }
138
+ end
139
+ # for delete request
140
+ # image_id from [1,2,Image.new("id" => 3)] => {id: [1,2,3]}
141
+ # from 1 => {id: 1}
142
+ # from Image.new("id" => 1) => {id: 1}
143
+ def self.image_ids_to_hash_of_array(image_ids)
144
+ if image_ids.is_a? Array
145
+ {id: image_ids.map{|iid| iid.is_a?(Image) ? iid.id : iid}}
146
+ else
147
+ {id: image_ids.is_a?(Image) ? image_ids.id : image_ids}
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,16 @@
1
+ module Shutterstock
2
+ class Collections < Array
3
+ attr_reader :raw_data
4
+ def initialize(raw_data)
5
+ @raw_data = raw_data
6
+
7
+ if raw_data.kind_of? Hash
8
+ super(@raw_data["data"].map{ |collection| Collection.new(collection) })
9
+ elsif raw_data.kind_of? Array
10
+ super( @raw_data.map{ |collection| Collection.new(collection) } )
11
+ end
12
+
13
+ self
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Shutterstock
2
+ class Configuration
3
+ # @return [String] The client id.
4
+ attr_accessor :client_id
5
+
6
+ # @return [String] The client secret.
7
+ attr_accessor :client_secret
8
+
9
+ # @return [String] The access token.
10
+ attr_accessor :access_token
11
+
12
+ # @return [String] The API url.
13
+ attr_accessor :api_url
14
+
15
+ def initialize
16
+ @client_options = {}
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module Shutterstock
2
+ class Contributor < Driver
3
+ attr_reader :id
4
+
5
+ include Equalizer.new(:id)
6
+
7
+ def initialize(params={})
8
+ @hash = params
9
+ @id = params["id"].to_i
10
+ end
11
+
12
+ # TODO API calls
13
+
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ require 'date'
2
+
3
+ module Shutterstock
4
+ class Driver
5
+
6
+ def respond_to(method)
7
+ return true if @hash[method.to_s]
8
+ super()
9
+ end
10
+
11
+ def methods
12
+ methods = super()
13
+ [methods, @hash.keys.map{ |k| k.to_sym} ].flatten
14
+ end
15
+
16
+ TRUTHY_JSON_VALUES = [ "1", 1, true, "true"]
17
+ def json_true?(thing)
18
+ TRUTHY_JSON_VALUES.include? thing
19
+ end
20
+
21
+ def to_date(text_date)
22
+ return nil if text_date.nil?
23
+ DateTime.parse(text_date)
24
+ end
25
+
26
+ def client
27
+ Client.instance
28
+ end
29
+
30
+ def self.client
31
+ Client.instance
32
+ end
33
+
34
+ def self.api(*params)
35
+ client.api(*params)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ module Shutterstock
2
+ class Error < StandardError
3
+ end
4
+
5
+ class IncorrectArguments < Error
6
+ end
7
+
8
+ class AppNotConfigured < Error
9
+ end
10
+
11
+ class FailedResponse < Error
12
+ attr_reader :code
13
+ alias :msg :message
14
+ def initialize(msg, code = 0)
15
+ @code = code
16
+ super(msg)
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,139 @@
1
+ module Shutterstock
2
+ class Image < Driver
3
+ attr_reader :hash, :id, :description, :added, :media_type, :contributor, :aspect, :image_type, :is_editorial,
4
+ :is_adult, :is_illustration, :has_model_release, :has_property_release, :model_releases, :categories,
5
+ :keywords, :assets, :models
6
+
7
+ include Equalizer.new(:id)
8
+
9
+ # {"id":"118139110",
10
+ # "added_date":"2012-11-09",
11
+ # "aspect":1.5,
12
+ # "assets":{"small_jpg":{"display_name":"Small","dpi":72,"file_size":88064,"format":"jpg","height":333,"is_licensable":true,"width":500},
13
+ # "medium_jpg":{"display_name":"Med","dpi":300,"file_size":301056,"format":"jpg","height":667,"is_licensable":true,"width":1000},
14
+ # "huge_jpg":{"display_name":"Huge","dpi":300,"file_size":2790400,"format":"jpg","height":3090,"is_licensable":true,"width":4635},
15
+ # "supersize_jpg":{"display_name":"Super","dpi":300,"file_size":10112609,"format":"jpg","height":6180,"is_licensable":false,"width":9270},
16
+ # "huge_tiff":{"display_name":"Huge","dpi":300,"file_size":42966450,"format":"tiff","height":3090,"is_licensable":false,"width":4635},
17
+ # "supersize_tiff":{"display_name":"Super","dpi":300,"file_size":171865800,"format":"tiff","height":6180,"is_licensable":false,"width":9270},
18
+ # "preview":{"height":300,"url":"https://image.shutterstock.com/display_pic_with_logo/1306729/118139110/stock-photo-adorable-labrador-puppy-playing-with-a-chew-toy-on-white-backdrop-118139110.jpg","width":450},
19
+ # "small_thumb":{"height":67,"url":"https://thumb1.shutterstock.com/thumb_small/1306729/118139110/stock-photo-adorable-labrador-puppy-playing-with-a-chew-toy-on-white-backdrop-118139110.jpg","width":100},
20
+ # "large_thumb":{"height":100,"url":"https://thumb1.shutterstock.com/thumb_large/1306729/118139110/stock-photo-adorable-labrador-puppy-playing-with-a-chew-toy-on-white-backdrop-118139110.jpg","width":150}
21
+ # },
22
+ # "categories":[{"id":"1","name":"Animals/Wildlife"},{"id":"20","name":"NOT-CATEGORIZED"}],
23
+ # "contributor":{"id":"1306729"},
24
+ # "description":"Adorable Labrador Puppy Playing with a Chew Toy on White Backdrop",
25
+ # "image_type":"photo",
26
+ # "is_adult":false,
27
+ # "keywords":["animal","animal themes","backgrounds","cute","dog","domestic animals","friendship","full length","isolated"],
28
+ # "media_type":"image"}
29
+
30
+ def initialize(params = {})
31
+ load_ivars(params)
32
+ end
33
+
34
+ def load_ivars(params)
35
+ @hash = params
36
+ @id = params["id"].to_i
37
+ @description = params["description"]
38
+ @added = to_date(params["added_date"])
39
+ @media_type = params["media_type"]
40
+ @contributor = Contributor.new(params["contributor"]) if params["contributor"]
41
+ @aspect = params["aspect"]
42
+ @image_type = params["image_type"]
43
+ @is_editorial = json_true? params["is_editorial"]
44
+ @is_adult = json_true? params["is_adult"]
45
+ @is_illustration = json_true? params["is_illustration"]
46
+ @has_model_release = json_true? params["has_model_release"]
47
+ @has_property_release = json_true? params["has_property_release"]
48
+ @model_releases = params["model_releases"]
49
+ @categories = Categories.new(params["categories"]) if params["categories"]
50
+ @keywords = params["keywords"]
51
+ @assets = ImageAssets.new(params["assets"]) if params["assets"]
52
+ @models = Models.new(params["models"]) if params["models"]
53
+ end
54
+
55
+ # boolean readers
56
+ def editorial?
57
+ @is_editorial
58
+ end
59
+
60
+ def adult?
61
+ @is_adult
62
+ end
63
+
64
+ def illustration?
65
+ @is_illustration
66
+ end
67
+
68
+ def model_release?
69
+ @has_model_release
70
+ end
71
+
72
+ def property_release?
73
+ @has_property_release
74
+ end
75
+
76
+
77
+
78
+ def self.find(id)
79
+ resp = client.request do |r|
80
+ r.path "/v2/images/#{id}"
81
+ r.method :get
82
+ end
83
+ self.new(resp.body)
84
+ end
85
+
86
+ def find
87
+ self.class.find(self.id)
88
+ end
89
+
90
+ def self.similar(id)
91
+ resp = client.request do |r|
92
+ r.path "/v2/images/#{id}/similar"
93
+ r.method :get
94
+ end
95
+ Images.new(resp.body)
96
+ end
97
+
98
+ def similar
99
+ @images = self.class.similar(self.id)
100
+ end
101
+
102
+ def self.search(search)
103
+ search_params = {}
104
+ if search.kind_of? String
105
+ search_params[:query] = search
106
+ else
107
+ search_params = search
108
+ end
109
+
110
+ resp = client.request do |r|
111
+ r.path "/v2/images/search"
112
+ r.method :get
113
+ r.params search_params
114
+ end
115
+ Images.new(resp.body)
116
+ end
117
+
118
+ # Fetch all details of this image
119
+ def fill
120
+ load_ivars(find.hash)
121
+ self
122
+ end
123
+
124
+ # License current image with available subscription
125
+ def license(format: nil, size: nil)
126
+ sub = find_subscription_for_this_size(size)
127
+ License.license(subscription_id: sub.id, image_id: self, format: format, size: size, editorial_acknowledgement: (editorial? ? true : nil))
128
+ end
129
+
130
+ private
131
+
132
+ def find_subscription_for_this_size(size = nil)
133
+ User.subscriptions.find_subscription_for_image_size
134
+ end
135
+
136
+
137
+
138
+ end
139
+ end