directlink 0.0.8.8 → 0.0.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '097dd2d9c1fd6437aeeda4edb35f26ba8cbac99c'
4
- data.tar.gz: 191d639d9b83f310d2497f735257d34868694da9
3
+ metadata.gz: 30657b1850442ca6f1267344dc356cdd3e0e55e8
4
+ data.tar.gz: b8dc86a2fb80cadb1583207934840fc28fdb6177
5
5
  SHA512:
6
- metadata.gz: 7be490bc74cb4dd909cfc866735170c26bdf498047aa61ad8c6f22f7d3131f8b6ae532f228a07174dabf763248d99c60b02a16b7f4b464b58308c2d2359cc833
7
- data.tar.gz: b92394db6e24b26553b93d87af54ceecc9ee4b079de6c2d2823ce65a995022e870006045b4b4b4f24aedba53822499a4ac9b3a6402c90e61bcad703b86caf0a0
6
+ metadata.gz: d11e9b0f07127be8b711495f1496e527bd979c015a0376512e916c5f92fda19fcfd617d67584c768c1c223ebd41cb57c28bbf258b39659cdd12d5db4653f4120
7
+ data.tar.gz: 8d6beddd649ec2f1316009abe70c9224de3a34d8234443a040d492066b792b79d9f3fe3771b9906949982f1f5d12ff00a4dbebd9468bda2db63941e74e34f663
data/bin/directlink CHANGED
@@ -68,18 +68,13 @@ begin
68
68
  (t.is_a?(Array) ? t : [t]).each{ |s| puts "=> #{s.url}\n #{s.type} #{s.width}x#{s.height}" }
69
69
  end
70
70
  end
71
- rescue SocketError,
72
- Net::OpenTimeout,
73
- Errno::ECONNRESET,
74
- NetHTTPUtils::Error,
75
- FastImage::UnknownImageType,
76
- FastImage::ImageFetchFailure,
77
- # DirectLink::ErrorMissingEnvVar,
78
- # DirectLink::ErrorAssert,
79
- DirectLink::ErrorNotFound,
80
- DirectLink::ErrorBadLink => e
71
+ rescue *DirectLink::NORMAL_EXCEPTIONS => e
81
72
  puts e.backtrace if debug
82
73
  cause = e.cause if e.cause if e.respond_to? :cause
83
74
  c = e.class.to_s
84
75
  abort "#{c}#{": #{e}" if c != e.to_s}#{": #{cause}" if cause && c != cause.to_s}"
76
+ rescue
77
+ raise unless $!.cause
78
+ raise $!.cause unless $!.cause.cause
79
+ raise $!.cause.cause
85
80
  end
data/directlink.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "directlink"
3
- spec.version = "0.0.8.8"
4
- spec.summary = "converts any kind of image hyperlink to direct link, type of image and its resolution"
3
+ spec.version = "0.0.11.0"
4
+ spec.summary = "obtains from any kind of hyperlink a link to an image, its format and resolution"
5
5
 
6
6
  spec.author = "Victor Maslov aka Nakilon"
7
7
  spec.email = "nakilon@gmail.com"
@@ -9,21 +9,23 @@ Gem::Specification.new do |spec|
9
9
  spec.homepage = "https://github.com/nakilon/directlink"
10
10
  spec.metadata = {"source_code_uri" => "https://github.com/nakilon/directlink"}
11
11
 
12
- spec.add_dependency "fastimage", "~>2.1.3"
13
- spec.add_dependency "nokogiri"
14
- spec.add_dependency "nethttputils", "~>0.4.0.0"
15
- spec.add_dependency "reddit_bot", "~>1.7.0"
12
+ spec.required_ruby_version = ">=2.3" # because <<~ heredocs in tests
13
+
14
+ spec.add_dependency "fastimage", "~>2.2.0"
15
+ spec.add_dependency "nokogiri", "<1.11" # 1.11 requires ruby 2.5
16
+ spec.add_dependency "nethttputils", "~>0.4.1.0"
17
+ spec.add_dependency "reddit_bot", "~>1.9.0"
16
18
  spec.add_dependency "kramdown"
17
19
  spec.add_dependency "addressable"
18
- spec.add_development_dependency "minitest"
19
- spec.add_development_dependency "byebug"
20
+ spec.add_development_dependency "minitest-around"
21
+ spec.add_development_dependency "webmock"
20
22
 
21
23
  spec.require_path = "lib"
22
24
  spec.bindir = "bin"
23
25
  spec.executable = "directlink"
24
- spec.test_file = "test.rb"
25
- spec.files = `git ls-files -z`.split(?\0) - spec.test_files
26
+ spec.test_file = "unit.test.rb"
27
+ spec.files = %w{ LICENSE directlink.gemspec lib/directlink.rb bin/directlink }
26
28
 
27
- spec.requirements << "you may need to create apps and provide their API tokens:"
29
+ spec.requirements << "you may want to create apps and provide API tokens:"
28
30
  spec.requirements << "IMGUR_CLIENT_ID, FLICKR_API_KEY, REDDIT_SECRETS"
29
31
  end
data/lib/directlink.rb CHANGED
@@ -2,17 +2,16 @@ module DirectLink
2
2
 
3
3
  class << self
4
4
  attr_accessor :silent
5
- end
6
- self.silent = false
7
- class << self
8
5
  attr_accessor :logger
6
+ attr_accessor :timeout
9
7
  end
8
+ self.silent = false
10
9
  self.logger = Object.new
11
10
  self.logger.define_singleton_method :error do |str|
12
11
  puts str unless Module.nesting.first.silent
13
12
  end
14
13
 
15
- class ErrorAssert < RuntimeError
14
+ class ErrorAssert < RuntimeError # gem user should not face this error
16
15
  def initialize msg
17
16
  super "#{msg} -- consider reporting this issue to GitHub"
18
17
  end
@@ -35,6 +34,22 @@ module DirectLink
35
34
  end
36
35
  end
37
36
 
37
+ require "nethttputils"
38
+ require "fastimage"
39
+ NORMAL_EXCEPTIONS = [
40
+ SocketError,
41
+ Net::OpenTimeout,
42
+ Errno::ECONNRESET,
43
+ Errno::ECONNREFUSED,
44
+ Errno::ETIMEDOUT, # from FastImage
45
+ NetHTTPUtils::Error,
46
+ NetHTTPUtils::EOFError_from_rbuf_fill,
47
+ FastImage::UnknownImageType,
48
+ FastImage::ImageFetchFailure,
49
+ DirectLink::ErrorNotFound,
50
+ DirectLink::ErrorBadLink,
51
+ ] # all known exceptions that can be raised while using Directlink but not as its fault
52
+
38
53
 
39
54
  def self.google src, width = 0
40
55
  # this can handle links without schema because it's used for parsing community HTML pages
@@ -44,10 +59,10 @@ module DirectLink
44
59
  "#{$1}s#{width}/"
45
60
  when /\A(\/\/lh3\.googleusercontent\.com\/proxy\/[a-zA-Z0-9_-]{66,523}=)(?:w(?:[45]\d\d)-h\d\d\d-[np]|s530-p|s110-p-k)\z/
46
61
  "https:#{$1}s#{width}/"
47
- when /\A(\/\/lh3\.googleusercontent\.com\/cOh2Nsv7EGo0QbuoKxoKZVZO_NcBzufuvPtzirMJfPmAzCzMtnEncfA7zGIDTJfkc1YZFX2MhgKnjA=)w530-h398-p\z/
62
+ when /\A(\/\/lh3\.googleusercontent\.com\/[a-zA-Z0-9]{24}_[a-zA-Z]{30}7zGIDTJfkc1YZFX2MhgKnjA=)w530-h398-p\z/
48
63
  "https:#{$1}s#{width}/"
49
64
  when /\A(\/\/lh3\.googleusercontent\.com\/-[a-zA-Z0-9-]{11}\/[VW][a-zA-Z0-9_-]{9}I\/AAAAAAA[AC][a-zA-Z0-9]{3}\/[a-zA-Z0-9_-]{32}[gwAQ]CJoC\/)w530-h[23]\d\d-p\/[^\/]+\z/,
50
- /\A(?:https?:)?(\/\/[1-4]\.bp\.blogspot\.com\/-[a-zA-Z0-9_-]{11}\/[UVWX][a-zA-Z0-9_-]{9}I\/AAAAAAAA[A-Z][a-zA-Z0-9_-]{2}\/[a-zA-Z0-9_-]{33}C(?:EwYBhgL|(?:Lc|Kg)BGAs)\/)(?:s640|w\d\d\d?-h\d\d\d?-p(?:-k-no-nu)?)\/[^\/]+\z/,
65
+ /\A(?:https?:)?(\/\/[1-4]\.bp\.blogspot\.com\/-[a-zA-Z0-9_-]{11}\/[UVWX][a-zA-Z0-9_-]{9}I\/AAAAAAAA[A-Z][a-zA-Z0-9_-]{2}\/[a-zA-Z0-9_-]{33}C(?:EwYBhgL|(?:Lc|Kg)BGAs(?:YHQ)?)\/)(?:s640|w\d{2,4}-h\d\d\d?-p(?:-k-no-nu)?)\/[^\/]+\z/,
51
66
  /\A(?:https?:)?(\/\/[1-4]\.bp\.blogspot\.com\/-[a-zA-Z0-9-]{11}\/[UV][a-zA-Z0-9_-]{9}I\/AAAAAAAA[A-Z][a-zA-Z0-9]{2}\/[a-zA-Z0-9-]{11}\/)w72-h72-p-k-no-nu\/[^\/]+\z/
52
67
  "https:#{$1}s#{width}/"
53
68
  when /\A(https:\/\/lh3\.googleusercontent\.com\/-[a-zA-Z0-9_]{11}\/AAAAAAAAAAI\/AAAAAAAAAAQ\/[a-zA-Z0-9_]{11}\/)w530-h[13]\d\d-n\/[^\/]+\z/,
@@ -65,18 +80,24 @@ module DirectLink
65
80
  /\A(https:\/\/lh[356]\.googleusercontent\.com\/-[a-zA-Z0-9]{11}\/AAAAAAAAAAI\/AAAAAAAAAAA\/[a-zA-Z0-9_]{34}\/)s(?:46|64)-c(?:-k(?:-no)?)?-mo\/photo\.jpg\z/
66
81
  "#{$1}s#{width}/"
67
82
  # Google Keep
68
- when /\A(https:\/\/lh\d\.googleusercontent\.com\/[a-zA-Z0-9_-]{104,106}=s)\d\d\d\d?\Z/
83
+ when /\A(https:\/\/lh\d\.googleusercontent\.com\/[a-zA-Z0-9_-]{104,106}=s)\d\d\d\d?\z/
69
84
  "#{$1}#{width}"
85
+ # opensea
86
+ when /\A(https:\/\/lh3\.googleusercontent\.com\/[a-zA-Z0-9]{78}-nGx_jf_XGqqiVANe_Jr8u2g=)w1400-k\z/
87
+ "#{$1}s#{width}"
70
88
  # mp4
71
89
  when /\A(https:\/\/lh3\.googleusercontent\.com\/-[a-zA-Z]{11}\/W[a-zA-Z0-9]{9}I\/AAAAAAAAODw\/[a-zA-Z0-9]{32}QCJoC\/)w530-h883-n-k-no\/[^\/]+\.mp4\z/
72
90
  "#{$1}s#{width}/"
91
+ # something else
92
+ when /\A(https:\/\/lh3\.googleusercontent\.com\/-[a-zA-Z0-9_]{11}\/X-[a-zA-Z0-9]{8}I\/AAAAAAAAALE\/[a-zA-Z0-9]{23}_[a-zA-Z0-9]{19}\/)w1200-h630-p-k-no-nu\/[\d-]+\.png\z/
93
+ "#{$1}s#{width}/"
73
94
  else
74
95
  raise ErrorBadLink.new src
75
96
  end
76
97
  end
77
98
 
99
+
78
100
  require "json"
79
- require "nethttputils"
80
101
 
81
102
  # TODO make the timeout handling respect the way the Directlink method works with timeouts
82
103
  def self.imgur link, timeout = 1000
@@ -88,7 +109,7 @@ module DirectLink
88
109
  NetHTTPUtils.request_data url, header: { Authorization: "Client-ID #{ENV["IMGUR_CLIENT_ID"]}" }
89
110
  rescue NetHTTPUtils::Error => e
90
111
  raise ErrorNotFound.new url.inspect if 404 == e.code
91
- if t < timeout && [400, 500, 503].include?(e.code)
112
+ if t < timeout && [400, 500, 502, 503].include?(e.code)
92
113
  logger.error "retrying in #{t} seconds because of Imgur HTTP ERROR #{e.code}"
93
114
  sleep t
94
115
  t *= 2
@@ -107,16 +128,16 @@ module DirectLink
107
128
  elsif data["images"]
108
129
  raise ErrorNotFound.new link.inspect if data["images"].empty?
109
130
  data["images"]
110
- elsif data["type"] && data["type"].start_with?("image/")
131
+ elsif data["type"] && %w{ image/jpeg image/png image/gif video/mp4 }.include?(data["type"])
111
132
  # TODO check if this branch is possible at all
112
133
  [ data ]
113
134
  # elsif data["comment"]
114
135
  # fi["https://imgur.com/" + data["image_id"]]
115
136
  else
116
137
  # one day single-video item should hit this but somehow it didn't yet
117
- raise ErrorAssert.new "unknown data format #{data.inspect} for #{link}"
138
+ raise ErrorAssert.new "unknown data format #{json} for #{link}"
118
139
  end
119
- when /\Ahttps?:\/\/(?:(?:i|m|www)\.)?imgur\.com\/([a-zA-Z0-9]{7,8})(?:\.(?:gifv|jpg(?:\?fb)?|png))?\z/,
140
+ when /\Ahttps?:\/\/(?:(?:i|m|www)\.)?imgur\.com\/([a-zA-Z0-9]{7,8})(?:\.(?:gifv|jpe?g(?:\?fb)?|png))?\z/,
120
141
  /\Ahttps?:\/\/(?:(?:i|m|www)\.)?imgur\.com\/([a-zA-Z0-9]{5})\.mp4\z/,
121
142
  /\Ahttps?:\/\/imgur\.com\/([a-zA-Z0-9]{5}(?:[a-zA-Z0-9]{2})?)\z/,
122
143
  /\Ahttps?:\/\/imgur\.com\/([a-zA-Z0-9]{7})(?:\?\S+)?\z/,
@@ -128,7 +149,7 @@ module DirectLink
128
149
  raise ErrorBadLink.new link
129
150
  end.map do |image|
130
151
  case image["type"]
131
- when "image/jpeg", "image/png", "image/gif", "video/mp4"
152
+ when *%w{ image/jpeg image/png image/gif video/mp4 }
132
153
  image.values_at "link", "width", "height", "type"
133
154
  else
134
155
  raise ErrorAssert.new "unknown type of #{link}: #{image}"
@@ -137,9 +158,8 @@ module DirectLink
137
158
  end
138
159
 
139
160
  def self._500px link
140
- raise ErrorBadLink.new link unless %r{\Ahttps://500px\.com/photo/(?<id>[^/]+)/[^/]+\z} =~ link
161
+ raise ErrorBadLink.new link unless %r{\Ahttps://500px\.com/photo/(?<id>[^/]+)/[-[a-zA-Z0-9]%]+\/?\z} =~ link
141
162
  require "nokogiri"
142
- resp = NetHTTPUtils.request_data link
143
163
  f = lambda do |form|
144
164
  JSON.load(NetHTTPUtils.request_data "https://api.500px.com/v1/photos", form: form).fetch("photos").values.first
145
165
  end
@@ -191,11 +211,11 @@ module DirectLink
191
211
  attr_accessor :reddit_bot
192
212
  end
193
213
  def self.reddit link, timeout = 1000
194
- unless id = URI(link).path[/\A(?:\/r\/[0-9a-zA-Z_]+)?(?:\/comments|\/duplicates)?\/([0-9a-z]{5,6})(?:\/|\z)/, 1]
195
- raise DirectLink::ErrorBadLink.new link unless URI(link).host &&
196
- URI(link).host.split(?.) == %w{ i redd it } &&
197
- URI(link).path[/\A\/[a-z0-9]{12,13}\.(gif|jpg)\z/]
198
- return [true, link]
214
+ return [true, link] if URI(link).host &&
215
+ URI(link).host.split(?.) == %w{ i redd it } &&
216
+ URI(link).path[/\A\/[a-z0-9]{12,13}\.(gif|jpg)\z/]
217
+ unless id = link[/\Ahttps:\/\/www\.reddit\.com\/gallery\/([0-9a-z]{5,6})\z/, 1]
218
+ raise DirectLink::ErrorBadLink.new link unless id = URI(link).path[/\A(?:\/r\/[0-9a-zA-Z_]+)?(?:\/comments|\/duplicates)?\/([0-9a-z]{5,6})(?:\/|\z)/, 1]
199
219
  end
200
220
  retry_on_json_parseerror = lambda do |&b|
201
221
  t = 1
@@ -221,14 +241,21 @@ module DirectLink
221
241
  raise ErrorAssert.new "our knowledge about Reddit API seems to be outdated" unless json.size == 2
222
242
  json.find{ |_| _["data"]["children"].first["kind"] == "t3" }
223
243
  end
244
+ # TODO: do we handle linking Imgur albums?
224
245
  data = json["data"]["children"].first["data"]
225
- if data["media"]["reddit_video"]
226
- return [true, data["media"]["reddit_video"]["fallback_url"]]
227
- else
246
+ if data["media"]
247
+ return [true, data["media"]["reddit_video"]["fallback_url"]] if data["media"]["reddit_video"]
228
248
  raise ErrorAssert.new "our knowledge about Reddit API seems to be outdated" unless data["media"].keys.sort == %w{ oembed type } && %w{ youtube.com gfycat.com imgur.com }.include?(data["media"]["type"])
229
249
  return [true, data["media"]["oembed"]["thumbnail_url"]]
230
- end if data["media"]
231
- return [true, data["url"]] if data["crosspost_parent"]
250
+ end
251
+ if data["media_metadata"]
252
+ return [true, data["media_metadata"].values.map do |media|
253
+ next if media == {"status"=>"failed"} || media == {"status"=>"unprocessed"}
254
+ raise ErrorAssert.new "our knowledge about Reddit API seems to be outdated (media == #{media.inspect})" unless media["status"] == "valid"
255
+ [media["m"], *media["s"].values_at("x", "y"), CGI.unescapeHTML(media["s"][media["m"]=="image/gif" ? "gif" : "u"])]
256
+ end.compact]
257
+ end
258
+ return [true, "#{"https://www.reddit.com" if /\A\/r\/[0-9a-zA-Z_]+\/comments\/[0-9a-z]{5,6}\// =~ data["url"]}#{data["url"]}"] if data["crosspost_parent"]
232
259
  return [true, data["url"]] unless data["is_self"]
233
260
  raise ErrorAssert.new "our knowledge about Reddit API seems to be outdated" if data["url"] != "https://www.reddit.com" + data["permalink"]
234
261
  return [false, data["selftext"]]
@@ -236,46 +263,54 @@ module DirectLink
236
263
 
237
264
  def self.vk link
238
265
  id, mtd, field, f = case link
239
- when %r{\Ahttps://vk\.com/id(?<user_id>\d+)\?z=photo(?<id>\k<user_id>_\d+)(%2F(album\k<user_id>_0|photos\k<user_id>))\z},
266
+ when %r{\Ahttps://vk\.com/id(?<user_id>\d+)\?z=photo(?<id>\k<user_id>_\d+)(%2F(album\k<user_id>_0|photos\k<user_id>))?\z},
240
267
  %r{\Ahttps://vk\.com/[a-z_]+\?z=photo(?<_>)(?<id>(?<user_id>\d+)_\d+)%2Fphotos\k<user_id>\z},
241
- %r{\Ahttps://vk\.com/photo(?<_>)(?<id>-?\d+_\d+)(\?all=1)?\z},
268
+ %r{\Ahttps://vk\.com/photo(?<_>)(?<id>-?\d+_\d+)(\?(?:all|rev)=1)?\z},
242
269
  %r{\Ahttps://vk\.com/feed\?section=likes&z=photo(?<_>)(?<id>-(?<user_id>\d+)_\d+)%2F(liked\d+|album\k<user_id>_0)\z},
243
270
  %r{\Ahttps://vk\.com/[a-z_]+\?z=photo(?<_>)(?<id>(?<user_id>-\d+)_\d+)%2F(wall\k<user_id>_\d+|album\k<user_id>_0)\z},
244
271
  %r{\Ahttps://vk\.com/wall(?<user_id>-\d+)_\d+\?z=photo(?<id>\k<user_id>_\d+)%2F(wall\k<user_id>_\d+|album\k<user_id>_00%2Frev|\d+)\z}
245
272
  [$2, :photos, :photos, lambda do |t|
246
273
  raise ErrorAssert.new "our knowledge about VK API seems to be outdated" unless 1 == t.size
247
- t.first
274
+ t
248
275
  end ]
249
- when %r{\Ahttps://vk\.com/wall(?<id>-\d+_\d+)\z}
276
+ when %r{\Ahttps://vk\.com/wall(?<id>-?\d+_\d+)\z},
277
+ %r{\Ahttps://vk\.com/[a-z\.]+\?w=wall(?<id>-?\d+_\d+)\z}
250
278
  [$1, :wall, :posts, lambda do |t|
251
- t.first.fetch("attachments").tap do |t|
252
- raise ErrorAssert.new "our knowledge about VK API seems to be outdated" unless 1 == t.size
253
- end.first.tap do |t|
254
- raise ErrorAssert.new "our knowledge about VK API seems to be outdated" unless %w{ type photo } == t.keys
255
- end.fetch("photo")
279
+ t.first.fetch("attachments").select do |item|
280
+ case item.keys
281
+ when %w{ type photo }
282
+ raise ErrorAssert.new "our knowledge about VK API seems to be outdated" unless item["type"] == "photo"
283
+ next true
284
+ when %w{ type audio }
285
+ raise ErrorAssert.new "our knowledge about VK API seems to be outdated" unless item["type"] == "audio"
286
+ else
287
+ raise ErrorAssert.new "our knowledge about VK API seems to be outdated"
288
+ end
289
+ end.map{ |i| i.fetch "photo" }
256
290
  end ]
257
291
  else
258
292
  raise ErrorBadLink.new link
259
293
  end
260
294
  raise ErrorMissingEnvVar.new "define VK_ACCESS_TOKEN and VK_CLIENT_SECRET env vars" unless ENV["VK_ACCESS_TOKEN"] && ENV["VK_CLIENT_SECRET"]
261
- sleep 0.25 # "error_msg"=>"Too many requests per second"
295
+ sleep 0.25 unless ENV["CI"] # "error_msg"=>"Too many requests per second"
262
296
  f.call( JSON.load( NetHTTPUtils.request_data "https://api.vk.com/method/#{mtd}.getById",
263
297
  :POST, form: { field => id, :access_token => ENV["VK_ACCESS_TOKEN"], :client_secret => ENV["VK_CLIENT_SECRET"], :v => "5.101" }
264
- ).fetch("response") ).fetch("sizes").map do |s|
265
- s.values_at("width", "height", "url").tap do |whu|
266
- w, h, u = whu
267
- whu[0, 2] = FastImage.new(u, raise_on_failure: true).size if [w, h].include? 0
268
- end
269
- end.max_by{ |w, h, u| w * h }
298
+ ).fetch("response") ).map do |photos|
299
+ photos.fetch("sizes").map do |size|
300
+ size.values_at("width", "height", "url").tap do |whu|
301
+ w, h, u = whu
302
+ whu[0, 2] = FastImage.new(u, raise_on_failure: true).size if [w, h].include? 0 # wtf?
303
+ end
304
+ end.max_by{ |w, h, u| w * h }
305
+ end
270
306
  end
271
307
 
272
308
  class_variable_set :@@directlink, Struct.new(:url, :width, :height, :type)
273
309
  end
274
310
 
275
311
 
276
- require "fastimage"
277
-
278
- def DirectLink link, timeout = nil, giveup: false, ignore_meta: false
312
+ def DirectLink link, timeout = nil, proxy = nil, giveup: false, ignore_meta: false
313
+ timeout ||= DirectLink.timeout
279
314
  ArgumentError.new("link should be a <String>, not <#{link.class}>") unless link.is_a? String
280
315
  begin
281
316
  URI link
@@ -313,15 +348,16 @@ def DirectLink link, timeout = nil, giveup: false, ignore_meta: false
313
348
  **( %w{ reddit com } == URI(link).host.split(?.).last(2) ||
314
349
  %w{ redd it } == URI(link).host.split(?.) ? {Cookie: "over18=1"} : {} ),
315
350
  }
316
- head = NetHTTPUtils.request_data link, :head, header: header, **(timeout ? {
351
+ head = NetHTTPUtils.request_data link, :HEAD, header: header, **(proxy ? {proxy: proxy} : {}), **(timeout ? {
317
352
  timeout: timeout,
318
353
  max_start_http_retry_delay: timeout,
319
- max_read_retry_delay: timeout
354
+ max_read_retry_delay: timeout,
320
355
  } : {})
321
- rescue Net::ReadTimeout
356
+ rescue Net::ReadTimeout, Errno::ETIMEDOUT
322
357
  rescue NetHTTPUtils::Error => e
323
358
  raise unless 418 == e.code
324
359
  else
360
+ raise DirectLink::ErrorAssert.new "last_response.uri is not set" unless head.instance_variable_get(:@last_response).uri
325
361
  link = head.instance_variable_get(:@last_response).uri.to_s
326
362
  end
327
363
 
@@ -329,10 +365,10 @@ def DirectLink link, timeout = nil, giveup: false, ignore_meta: false
329
365
  # because they can be hidden behind URL shorteners
330
366
  # also it can resolve NetHTTPUtils::Error(404) before trying the adapter
331
367
 
332
- t = google_without_schema_crutch[] and return t
368
+ t = google_without_schema_crutch[] and return t # TODO: why again?
333
369
 
334
370
  begin
335
- imgur = DirectLink.imgur(link).sort_by{ |u, w, h, t| - w * h }.map do |u, w, h, t|
371
+ imgur = DirectLink.imgur(link, timeout).sort_by{ |u, w, h, t| - w * h }.map do |u, w, h, t|
336
372
  struct.new u, w, h, t
337
373
  end
338
374
  # `DirectLink.imgur` return value is always an Array
@@ -370,40 +406,51 @@ def DirectLink link, timeout = nil, giveup: false, ignore_meta: false
370
406
  f = ->_{ _.type == :a ? _.attr["href"] : _.children.flat_map(&f) }
371
407
  require "kramdown"
372
408
  return f[Kramdown::Document.new(u).root].flat_map do |sublink|
373
- DirectLink URI.join(link, sublink).to_s, timeout, giveup: giveup
409
+ DirectLink URI.join(link, sublink).to_s, timeout, giveup: giveup # TODO: maybe subtract from timeout the time we've already wasted
410
+ end
411
+ end
412
+ if u.is_a? Hash
413
+ return struct.new *u.values_at(*%w{ fallback_url width height }), "video"
414
+ elsif u.is_a? Array
415
+ return u.map do |t, x, y, u|
416
+ struct.new u, x, y, t
374
417
  end
375
418
  end
376
- return struct.new *u.values_at(*%w{ fallback_url width height }), "video" if u.is_a? Hash
377
- return DirectLink u
378
- fail if link == u
419
+ raise DirectLink::ErrorNotFound.new link.inspect if link == u
420
+ return DirectLink u, timeout, giveup: giveup
379
421
  rescue DirectLink::ErrorMissingEnvVar
380
422
  end if %w{ reddit com } == URI(link).host.split(?.).last(2) ||
381
423
  %w{ redd it } == URI(link).host.split(?.)
382
424
 
383
425
  begin
384
- w, h, u = DirectLink.vk(link)
385
- return struct.new u, w, h
426
+ return DirectLink.vk(link).map do |w, h, u|
427
+ struct.new u, w, h
428
+ end
386
429
  rescue DirectLink::ErrorMissingEnvVar
387
430
  end if %w{ vk com } == URI(link).host.split(?.)
388
431
 
389
432
  begin
390
- f = FastImage.new(link, raise_on_failure: true, timeout: 5, http_header: {"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"})
433
+ f = FastImage.new link,
434
+ raise_on_failure: true,
435
+ timeout: timeout,
436
+ **(proxy ? {proxy: "http://#{proxy}"} : {}),
437
+ http_header: {"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"}
391
438
  rescue FastImage::UnknownImageType
392
439
  raise if giveup
393
440
  require "nokogiri"
394
- head = NetHTTPUtils.request_data link, :head, header: {"User-Agent" => "Mozilla"},
441
+ head = NetHTTPUtils.request_data link, :HEAD, header: {"User-Agent" => "Mozilla"},
395
442
  max_start_http_retry_delay: timeout,
396
443
  timeout: timeout, # NetHTTPUtild passes this as read_timeout to Net::HTTP.start
397
444
  max_read_retry_delay: timeout # and then compares accumulated delay to this
398
445
  # if we use :get here we will download megabytes of files just to giveup on content_type we can't process
399
- case head.instance_variable_get(:@last_response).content_type
446
+ case head.instance_variable_get(:@last_response).content_type # webmock should provide this
400
447
  when "text/html" ; nil
401
448
  else ; raise
402
449
  end
403
- html = Nokogiri::HTML NetHTTPUtils.request_data link, header: {"User-Agent" => "Mozilla"}
450
+ html = Nokogiri::HTML NetHTTPUtils.request_data link, :GET, header: {"User-Agent" => "Mozilla"}
404
451
  if t = html.at_css("meta[@property='og:image']")
405
452
  begin
406
- return DirectLink URI.join(link, t[:content]), nil, giveup: true
453
+ return DirectLink URI.join(link, t[:content]).to_s, nil, *proxy, giveup: true
407
454
  rescue URI::InvalidURIError
408
455
  end
409
456
  end unless ignore_meta
@@ -419,9 +466,10 @@ def DirectLink link, timeout = nil, giveup: false, ignore_meta: false
419
466
  end
420
467
  end
421
468
  end
422
- l[html].group_by(&:first).map{ |k, v| [k.join(?>), v.map(&:last)] }.tap do |results|
423
- raise if results.empty?
424
- end.max_by{ |_, v| v.map{ |i| i.width * i.height }.inject(:+) / v.size }.last
469
+ l[html].
470
+ tap{ |results| raise if results.empty? }.
471
+ group_by(&:first).map{ |k, v| [k.join(?>), v.map(&:last)] }.
472
+ max_by{ |_, v| v.map{ |i| i.width * i.height }.inject(:+) }.last
425
473
  else
426
474
  # TODO: maybe move this to right before `rescue` line
427
475
  w, h = f.size
data/unit.test.rb ADDED
@@ -0,0 +1,1548 @@
1
+ require "minitest/autorun"
2
+ require "minitest/around/spec"
3
+ require "minitest/mock"
4
+ require "webmock/minitest"
5
+ require_relative "webmock_patch"
6
+
7
+ require_relative "lib/directlink"
8
+ DirectLink.silent = true # TODO: remove?
9
+ DirectLink.timeout = 30 # TODO: tests about this attribute
10
+
11
+ describe DirectLink do
12
+ around{ |test| Timeout.timeout(4){ test.call } }
13
+ before{ WebMock.reset! }
14
+
15
+ describe "./lib" do
16
+
17
+ describe "google" do
18
+
19
+ # TODO: expand this for every branch in the lib
20
+ %w{
21
+ https_long_blogspot https://1.bp.blogspot.com/-__qsdLxNtcQ/XhaOQle-ECI/AAAAAAAABQ4/S_7SGG_abcDE6XIU2wyPvTj9OyBfr_1sQCLcBGAsYHQ/w1200-h630-p-k-no-nu/iceland_poppies_orange_flowers_field-wallpaper-3840x2160.jpg https://1.bp.blogspot.com/-__qsdLxNtcQ/XhaOQle-ECI/AAAAAAAABQ4/S_7SGG_abcDE6XIU2wyPvTj9OyBfr_1sQCLcBGAsYHQ/s0/
22
+ http_short_blogspot http://4.bp.blogspot.com/-poH-QXn7YGg/U-3ZTDkeF_I/AAAAAAAAISE/mabcDE6-v-g/w72-h72-p-k-no-nu/Top-24-Inspired-181.jpg https://4.bp.blogspot.com/-poH-QXn7YGg/U-3ZTDkeF_I/AAAAAAAAISE/mabcDE6-v-g/s0/
23
+ just_gplus https://lh3.googleusercontent.com/-NiGph3ObOPg/XE3DgnavXlI/AAAAAAABvgE/pcPPCe88rsU1r941wwP76TVf_abcDE6kwCJoC/w530-h353-n/DSCF0753.JPG https://lh3.googleusercontent.com/-NiGph3ObOPg/XE3DgnavXlI/AAAAAAABvgE/pcPPCe88rsU1r941wwP76TVf_abcDE6kwCJoC/s0/
24
+ google_keep https://lh5.googleusercontent.com/fRmAL_04p7oomNHCiV4tH4-agHSDBtLaWi_Tb6bgE5ZSHVu5OjQF3iRn06nNwP3ywZwdFP92zWM-abcDE6n6m0tDTBARuO6F9e0wYu_1=s685 https://lh5.googleusercontent.com/fRmAL_04p7oomNHCiV4tH4-agHSDBtLaWi_Tb6bgE5ZSHVu5OjQF3iRn06nNwP3ywZwdFP92zWM-abcDE6n6m0tDTBARuO6F9e0wYu_1=s0
25
+ }.each_slice 3 do |name, link, o|
26
+ it "replaces s0 and schema correctly #{name}" do
27
+ assert_equal o, DirectLink.google(link)
28
+ end
29
+ end
30
+
31
+ describe "does not fail" do
32
+ # TODO: also check the #google is being called
33
+
34
+ "
35
+ 1 //1.bp.blogspot.com/-rYk_u-qROQc/WngolJ8M0LI/AAAAAAAAD-w/abcDE6IVzasBPG5C2T1t-VrWKRd_U6lMgCLcBGAs/w530-h278-p/i-469.jpg
36
+ 2 //4.bp.blogspot.com/-1RlHbRShONs/Wngp9WfxbfI/AAAAAAAAD-8/abcDE6SZdvUMz4VjJPBpprLBrJ5QpBaqACLcBGAs/w530-h278-p/i-468.jpg
37
+ 3 //4.bp.blogspot.com/-5kP8ndL0kuM/Wpt82UCqvmI/AAAAAAAAEjI/abcDE60-kgwRXEJ9JEGioR0bm6U8MOkvQCKgBGAs/w530-h278-p/IMG_20171223_093922.jpg
38
+ 4 //4.bp.blogspot.com/-bS1SZxFkvek/Wnjz3D5Il4I/AAAAAAAAD_I/abcDE6GB7r4NGJ0aR1F-ugsbVm4HUjf5ACLcBGAs/w530-h278-p/i-466.jpg
39
+ 5 //lh3.googleusercontent.com/-1CF2jge1MYI/Wmr_kfCQQUI/AAAAAAAClKs/abcDE64v0q0i5N7rgso6G8SrhJNzsHEagCJoC/w530-h352-p/2809525%2B%25D0%2593%25D0%2590%25D0%259B%25D0%259E%2B%25D0%259F%25D0%2590%25D0%25A0%25D0%2593%25D0%2595%25D0%259B%25D0%2598%25D0%2599%2B-%2B%25D0%25A7%25D0%25A3%25D0%259A%25D0%259E%25D0%25A2%25D0%259A%25D0%2590%2B-%2B%25D0%25AE%25D0%25A0%25D0%2598%25D0%2599%2B%25D0%25A5%25D0%2590%25D0%25A0%25D0%25A7%25D0%2595%25D0%259D%25D0%259A%25D0%259E.jpg
40
+ 6 //lh3.googleusercontent.com/-h1siEcRFqJ8/Wiv1iDQhNVI/AAAAAAAAQV4/abcDE6mYkDMLyVTG_1EBCMmj-UhmclXWwCJoC/w530-h353-p/001
41
+ 7 //lh3.googleusercontent.com/-qnZygRb-Tn8/Wlyj4UuKbaI/AAAAAAAATqE/abcDE6-XBZwsh843xJ3cSd0JPt1LedU9gCJoC/w530-h330-p/001
42
+ 8 //lh3.googleusercontent.com/-rXO9PzbjNmk/WlNfjJkJBmI/AAAAAAAATNY/abcDE6tWBzUKWufNXWkK82etw3RKA6jVgCJoC/w530-h361-p/001
43
+
44
+ 13 //lh3.googleusercontent.com/proxy/2fsWfEOeeCMNEP8MGvHiJPkBlg47Ifaf8DwpgDPJSPSYsh9iomTrfOA9OuwB60laZimtNr4-1GKA5J2csuVB6-1DLNO30K-abcDE656ivICWRFAUuOwzizaztBx5gQ=w530-h707-p
45
+ 14 //lh3.googleusercontent.com/proxy/2qy3gOwhBT77Ie_qa9VgGz6kgN14tnOh1AMmV9FB65oiXOSAIll1_qmOZdupL3QCcJQL4701YskEgeDUnxJTnFGq4mFEzNFvjpNC-QMX9Jg7mdar3e89u_siA4AD-j1SJ6mrkl2MwSJSu1s2guYn0NR5ND1985lK34_iGiwzjoGHG2a65IbxKDSEzgSGbPQ_abcDE6BIvfcgvAxtS6E=s530-p
46
+ 28 //lh3.googleusercontent.com/proxy/7N0QCidzIFysO6OhmrG8twRJ74-sboNenI3Nhy3mIWP8C0SSa8J6xFQU3iyBTw4k3QeleiAgXOME3abYutPvBrHGx-0TBUcI-_abcDE69Zaw6PAGdLndkxp5ReRJnsg=w530-h298-p
47
+ 31 //lh3.googleusercontent.com/proxy/7pN3aTE159nEXXQb_KajU1vcnDDzJVpGOv9vlWWPd3CbVuCKu-v4ndr423Mmhx-Zn5vqkVf6-TfZgPA5NyHECsr4w7kaw1jTmQDJbWO-9jU-Cs7ywrzcIRhyYTTZT-abcDE6wI1oEdbooIk=w464-h640-p
48
+ 48 //lh3.googleusercontent.com/proxy/DZtTi5KL7PqiBwJc8weNGLk_Wi2UTaQH0AC_abcDE6iu0AbwyI2ywOk2XgdAjL7ceg=w530-h354-n
49
+ 74 //lh3.googleusercontent.com/proxy/P4FQdTYwWN8cU_PWmFja1cm3oYu57L_flea7LmKvFjx0Ibdxl3glxcp-abcDE6xMFTQ3gQkWsWzlrkyCm9QrSjCUcGIgnbsYBCXewnhEwSn0ZgT4XDg485GdXMoGWpNETrLYa8IGvG20_iw=w504-h597-p
50
+ 137 //lh3.googleusercontent.com/proxy/kX1ABK6_VjHDU12p-zZN6ciTOzqFcXm7IV41uR9F2sveM2t148nmWBEN1clA-VHvowpkd9T4NOPrAfDaip1o-jJBtBnRBElJuCsKo8FviSIkToZWCY9nQFEj06nHkMNuReBtdbVBlIRSodNJM-_cmqiJ-DnmWNe4dk62EF_kGLS28OWmckDLWVG0YFiZ-lVi8JhgR-ZgRmYWTs8bb_u6v6ucgfo8eh7ewchuZAD9_CoxUlQChAqz0QIfuVuKvmcXE06Ylv2H330btswQotjKDf8AXPe6xcyE9X1JXDYQTsPv-7lftLG9WQp1Mlc9Wu1XbHV7f541L-smt--7YpkW6fbDFYk4_87-pIOcg9UqHKB__DWyMCtH3gs8rv6VCmVwL1a-oAeBWSWk0Tc9R6uSuyCjt0nEBGlZY35aW1Pchz0jXr4lq8Il9m-wbRN9E7Kff1JR89YFOrV2N86w_72YTefFerLFfEsgzmhx3WYwok-abcDE6H7MyiHBQt04uRnb58GA4vl5VBQ=w530-h398-p
51
+
52
+ 245 https://lh3.googleusercontent.com/-2JzV0ErXvv8/Wsdr-m5Q_aI/AAAAAAAA_-I/Lw_e6qEMPUck4yW9yWhkC-abcDE6m8c3QCJoC/w530-h795-n/888052_800n.jpg
53
+ 346 https://lh3.googleusercontent.com/-8YmBt-SUaJo/War0Un_7YTI/AAAAAAAB9xs/kgXp9QrHZX8mpDfyLwuS9cabcDE6oH6NgCL0BGAs/w530-d-h443-n/%25D0%259B%25D0%25B5%25D1%2581%2B%25D0%25BD%25D0%25B0%2B%25D0%25B8%25D1%2581%25D1%2585%25D0%25BE%25D0%25B4%25D0%25B5%2B%25D0%25BB%25D0%25B5%25D1%2582%25D0%25B0.JPG
54
+ 1317 https://lh3.googleusercontent.com/-tGAyJv4S6gM/WSLy4A7AFaI/AAAAAAABxk4/eq0Aprp2koM6GEti9xEIn-abcDE6bRRdQCL0B/w530-d-h660-n/%25D0%2592%25D0%25B5%25D1%2581%25D0%25BD%25D1%258B%2B%25D1%2586%25D0%25B2%25D0%25B5%25D1%2582%25D0%25B5%25D0%25BD%25D0%25B8%25D0%25B5.JPG
55
+
56
+ 178 https://lh3.googleusercontent.com/--Ggbfs9HJZM/Wog1DA2WcFI/AAAAAAAAbdQ/SZKPcnabcDE6Ffl9pddfiLPQnOiJ37muQCJoC/w409-h727-n/11a1c54f37c07d3c04588b40d108b4c2.jpg
57
+ 260 https://lh3.googleusercontent.com/-3Fe6CL9RH4o/WnipWY4889I/AAAAAAAAI4I/CPqdKpabcDE6aBlCqGQ3RdZ9bLJ4nzBwwCJoC/w239-h318-n/gplus1855963830.jpg
58
+ 303 https://lh3.googleusercontent.com/-5vPhLnUILsA/WqFhJhu7VlI/AAAAAAAAAhQ/wWSrC8abcDE6OuzeU3UoXJkNG19tqtAGwCJoC/w265-h353-n/DSC08260.JPG
59
+ 668 https://lh3.googleusercontent.com/-NLW68hNxEzU/WrSNfBxFX2I/AAAAAAAAAT8/7cvRM-abcDE6dlb6CS8pL5-eJ3wbwohlQCJoC/w179-h318-n/23.03.2018%2B-%2B3
60
+ 669 https://lh3.googleusercontent.com/-NVJgqmI_2Is/WqMM2OMYg-I/AAAAAAAALrk/5-p3JLabcDE6o9dOf_p3gpddzqwr3Wp0ACJoC/w424-h318-n/001
61
+ 783 https://lh3.googleusercontent.com/-SqAvt_F__bg/Wq0huHcX2NI/AAAAAAAAVj0/XfnwCUabcDE60Knw8rcXA-bhpKYkI4hdwCJoC/w358-h318-n/IMG_20180317_120218-01-01.jpeg
62
+ 830 https://lh3.googleusercontent.com/-VB3YlLVL_tQ/Wo21yTbKl_I/AAAAAAAAApU/3DURmsabcDE60kif2ZRjhoLG4mTHYf8OwCJoC/w254-h318-n/180218_00001.jpg
63
+ 1002 https://lh3.googleusercontent.com/-d8FkfLWq-_4/WpQXzEfFgBI/AAAAAAAADww/J32xKSabcDE6c3pyrZnNmRec6kjdnJBHgCJoC/w239-h318-n/gplus-485739203.jpg
64
+ ".scan(/(\S+) (\S+)/).each do |i, link| # some pre-May-2018 urls dump from a community
65
+ it "Google Plus community post image ##{i}" do
66
+ assert DirectLink.google link
67
+ end
68
+ end
69
+ %w{
70
+ https://lh3.googleusercontent.com/-abcDE62vWuE/AAAAAAAAAAI/AAAAAAAAAAQ/wVFZagieszU/w530-h176-n/photo.jpg
71
+ //lh3.googleusercontent.com/abcDE6v7EGo0QbuoKxoKZVZO_NcBzufuvPtzirMJfPmAzCzMtnEncfA7zGIDTJfkc1YZFX2MhgKnjA=w530-h398-p
72
+ //lh3.googleusercontent.com/-0Vt5HFpPTVQ/Wnh02Jb4SFI/AAAAAAAAbCU/MtusII6-abcDE6QWofAaWF01IBUVykMyACJoC/w530-h351-p/DSC_3804%2B-%2B%25D0%25BA%25D0%25BE%25D0%25BF%25D0%25B8%25D1%258F.JPG
73
+ https://lh3.googleusercontent.com/-abcDE6-b85c/WvOtHJn-DxI/AAAAAAAAAKg/A_PoO64o0VkX1erPKebiO7Xcs5Iy3idbACJoC/w424-h318-n/IMG_20180510_082023.jpg
74
+ https://lh3.googleusercontent.com/-abcDE6FntJY/Wvl8HLwfF_I/AAAAAAAAA20/mqOtMCFuBkkvtv7jdpVkJv2XSXlOLoXVQCJoC/w212-h318-n/DPP_0006.JPG
75
+ https://lh3.googleusercontent.com/-abcDE64EIiw/WuxqzXHQCHI/AAAAAAAABAI/rA0R2UsbxoswvH6nSePnwa5VHG2f5kNOQCJoC/w398-h318-n/Fox-Desktop-1024x1280.jpg
76
+ https://lh3.googleusercontent.com/-abcDE62-1OA/WvMUBWXI0PI/AAAAAAAAC60/gdtkQz9YI0knYkFB8VjC2CpKOD6-Zs6hQCJoC/w408-h318-n/%25D0%25BF%25D0%25BE%25D1%2581.jpg
77
+ https://lh3.googleusercontent.com/--abcDE6Gk-A/WveQe_YKbAI/AAAAAAAACkQ/eTz2maXvw7A0iKoPjfuwEgfTiZS3a3HSgCJoC/w318-h318-n/gplus1165736192.jpg
78
+ https://lh3.googleusercontent.com/-abcDE6hwMOg/WtOxVJ1DNxI/AAAAAAAAMGs/pJf8awKJcyMFlx2g89p9_QjqlyQDmYMmACJoC/w424-h318-n/DSC03178.JPG
79
+ https://lh3.googleusercontent.com/-abcDE67fY1o/WtaUJCaDGmI/AAAAAAAAmBQ/jZhatiBmHPEiAAABcY2DoJ6KuzVBvqGxQCJoC/w530-h150-n/%25D0%25A1%25D0%25BD%25D0%25B8%25D0%25BC%25D0%25BE%25D0%25BA%2B%25D1%258D%25D0%25BA%25D1%2580%25D0%25B0%25D0%25BD%25D0%25B0%2B2018-04-18%2B%25D0%25B2%2B3.32.42.png
80
+ https://lh3.googleusercontent.com/-40QwR_c58sw/WsLyS3a8uhI/AAAAAAAAAas/ojaQoF1-abcDE6S6c5kLs7bOl_TAOU6oACJoC/w424-h318-n/271091.jpg
81
+ https://lh3.googleusercontent.com/-abcDE6yNBjY/WvOtHOyaj_I/AAAAAAAAAKo/gOAn__a75NwYSgaBaEBGeCTAFI9MyjqlwCJoC/w239-h318-n/IMG_20180510_081956.jpg
82
+ https://lh3.googleusercontent.com/-R19p_abcDE6/Wwma1oEvD6I/AAAAAAAAEX8/tQc4JOq58REEWlukw2jDCTUjH3ejGZI8gCJoC/w486-h864-n/gplus1392977354.jpg
83
+ //2.bp.blogspot.com/-mOnRg-mkojA/W22m79XcT_I/AAAAAAAAHDs/C8yQaOA-ZeEAr3WBG--abcDE65nWV0p7QCEwYBhgL/w530-h353-p/_MG_8809-Edit.jpg
84
+ }.each_with_index do |link, i| # March contenstants
85
+ it "another (March) Google Plus community post image ##{i + 1}" do
86
+ assert DirectLink.google link
87
+ end
88
+ end
89
+ %w{
90
+ https://lh3.googleusercontent.com/-abRcDE6_gic/WwUz3_Www7I/AAAAAAAABio/MNYvB57tBvIl6Bxp0GsKHvg0hSyHyVWhwCJoC/w265-h353-n/IMG_20180522_210620_317.jpg
91
+ https://lh3.googleusercontent.com/-abAcDE61QVc/Wu4Khtduz-I/AAAAAAAAA_A/lvpwTuatrtkhL31co1n9xa7kwKEVwc6IwCJoC/w301-h318-n/gplus662578195.jpg
92
+ https://lh3.googleusercontent.com/-abKcDE6NG2Q/Wvt9mUDC1QI/AAAAAAAANEw/2uWvIzQmG-Y3CGHuB8-UU114z4Zr8lkLQCJoC/w530-h175-n/PANO_20180516_025747.jpg
93
+ https://lh3.googleusercontent.com/-abWcDE66_Ew/WwvJcNQ39BI/AAAAAAAABvI/xb1bdwuEZGM2SmbwamwiMmZKFOW44bhPgCJoC/w239-h318-n/DSC03627.JPG
94
+ https://lh3.googleusercontent.com/-abacDE67WWI/Wu4Khq0IkaI/AAAAAAAAA_A/Dxs9JYJ6f6sv_7jNWMdNl3eo2jh9nYqDwCJoC/w296-h318-n/gplus-328480287.jpg
95
+ https://lh3.googleusercontent.com/-ab3cDE6qjyo/Wu4Khr5ZKhI/AAAAAAAAA_A/725HNeKw2hYtuddakPreAkaucQR6HOYGgCJoC/w424-h318-n/20180317_085037.jpg
96
+ https://lh3.googleusercontent.com/-abYcDE6GABY/Wu4Khov2uGI/AAAAAAAAA_A/xFVaLt1emxUtOvys7Mw8QZIF2599lNV6ACJoC/w424-h318-n/20180317_085508.jpg
97
+ https://lh3.googleusercontent.com/-abMcDE6jtXg/Wu4KhkZD9nI/AAAAAAAAA_A/lBHtrho7HBQgkZNztMTdR3o1RSu47OnigCJoC/w239-h318-n/gplus1795981938.jpg
98
+ https://lh3.googleusercontent.com/-ab-cDE6kT88/Wu4Khs76OSI/AAAAAAAAA_A/2rsXBfNM4Isi3YBNWlgLnPgQpVN72dXiQCJoC/w318-h318-n/gplus-379421668.jpg
99
+ }.each_with_index do |link, i| # April contenstants
100
+ it "another (April) Google Plus community post image ##{i + 1}" do
101
+ assert DirectLink.google link
102
+ end
103
+ end
104
+ %w{
105
+ https://lh3.googleusercontent.com/-abcDE6XoL_k/WvMUBQ6TXMI/AAAAAAAAC60/XRgJlEr5hbgKzYzOQkM8b4_fq2Yp5vNbACJoC/w239-h318-n/%25D0%25B7%25D0%25B0%25D1%2581.jpg
106
+ https://lh3.googleusercontent.com/-abcDE6ZYF4Y/Wvl8HH7crSI/AAAAAAAAA20/0vaXtOyqDL0DLx841Gj45ePol01OCOAewCJoC/w212-h318-n/DPP_0007.JPG
107
+ https://lh3.googleusercontent.com/-abcDE6DAoH8/WyCS3fapzjI/AAAAAAAAB5M/hKzS0cR0MEgnM-YJENsL7dX8Tbch5Z04wCJoC/w265-h353-n/DSC03805.JPG
108
+ https://lh3.googleusercontent.com/-abcDE6hweSI/WvMUBS4uAJI/AAAAAAAAC60/tE7aakvztZgTqvRMcrk2FbrauU8LLhGPgCJoC/w239-h318-n/11.jpg
109
+ https://lh3.googleusercontent.com/-abcDE6T_v6g/WyCS3VJ-hVI/AAAAAAAAB5M/qeUFKiNXnwkJ-q2q2VoM2f9ukTqPOGhAACJoC/w265-h353-n/DSC03807.JPG
110
+ //2.bp.blogspot.com/-abcDE6kbjOk/WwlXLbeiF9I/AAAAAAAAKKE/zT7sAlaNYIc7lTxZMY1tyND0u3UH5DuKACLcBGAs/w530-h398-p/EdnjF3yEMM8.jpg
111
+ https://lh3.googleusercontent.com/-1_J-abcDE6E/Wx9lWOEXUjI/AAAAAAAAB34/183rqDWZipkaSWyV6qF_Fm7_XQrtYiwdACJoC/w265-h353-n/DSC03800.JPG
112
+ https://lh3.googleusercontent.com/-I72-abcDE64/Wx9lWLfINqI/AAAAAAAAB34/EIOySV-4ny8xMECMSC4TvHV43qUn0HLLwCJoC/w265-h353-n/DSC03797.JPG
113
+ https://lh3.googleusercontent.com/-L-RnabcDE6I/WxZnv-NRXWI/AAAAAAAARVw/Z3yVnOEDU7kDZ-WbRoLbEh8Tao1_DtQbACJoC/w179-h318-n/20160305_134926.jpg
114
+ //lh3.googleusercontent.com/-abcDE6HFM6E/WtRMPtqimXI/AAAAAAAACIo/txJyaoEvnEQu7r2-35SgwFYCWHGmXDQ3QCJoC/w530-h353-p/IMG_3695.jpg
115
+ }.each_with_index do |link, i| # May contenstants
116
+ it "another (May) Google Plus community post image ##{i + 1}" do
117
+ assert DirectLink.google link
118
+ end
119
+ end
120
+ %w{
121
+ https://lh3.googleusercontent.com/-dWul1-dnMGE/W1npdlvSenI/AAAAAAAAAJI/abcDE65xS98xvFJDNgMgcxrRzPd6gOp9QCJoC/w208-h318-n/alg3_1520-2.jpg
122
+ https://lh3.googleusercontent.com/-t_ab__91ChA/VeLaObkUlgI/AAAAAAAAL4s/abcDE6_lkRw/w530-h351-n/30.08.15%2B-%2B1
123
+ https://lh3.googleusercontent.com/-Ry3uDLjGG0E/W13DzrEntEI/AAAAAAAAAdY/abcDE6-2JPsnuqYq7qtif_eVWhTd0EmnwCJoC/w480-h853-n/gplus-790559743.jpg
124
+ }.each_with_index do |link, i| # June contenstants
125
+ it "another (June) Google Plus community post image ##{i + 1}" do
126
+ assert DirectLink.google link
127
+ end
128
+ end
129
+ %w{
130
+ https://lh3.googleusercontent.com/-s655sojwyvw/VcNB4YMCz-I/AAAAAAAALqo/abcDE6cJJ0g/w530-h398-n/06.08.15%2B-%2B1
131
+ //4.bp.blogspot.com/-TuMlpg-Q1YY/W3PXkW1lkaI/AAAAAAAAHHg/Bh9IsuLV01kbctIu6lcRJHKkY-abcDE6gCLcBGAs/w530-h353-p/_MG_2688-Edit.jpg
132
+ //lh3.googleusercontent.com/proxy/SEfB6tFuim6X0HdZfEBSxrXtumUdf4Q4y05rUW4wc_clWWVrowuWAGZghx71xwPUmf_abcDE6wnRivsM7PfD2gp3kA=w480-h360-n
133
+ https://lh3.googleusercontent.com/-u3FhiUTmLCY/Vk7dMQnxR2I/AAAAAAAAMc0/I76_abcDE6s/w530-h322-n/Harekosh_A%252520Concert_YkRqQg.jpg
134
+ https://lh3.googleusercontent.com/-t_ab__91ChA/VeLaObkUlgI/AAAAAAAAL4s/abcDE6_lkRw/w530-d-h351-n/30.08.15%2B-%2B1
135
+ //lh3.googleusercontent.com/-u2NzdIQfVyQ/Wy83AzoFT8I/AAAAAAAAh6M/fdpxOUkj5mUIfpvYol_abcDE6nF2nDIEACJoC/w530-h298-p/_DSC9134.jpg
136
+ }.each_with_index do |link, i| # July contenstants
137
+ it "another (July) Google Plus community post image ##{i + 1}" do
138
+ assert DirectLink.google link
139
+ end
140
+ end
141
+ %w{
142
+ https://lh3.googleusercontent.com/-f37xWyiyP8U/WvmxOxCd-0I/AAAAAAAACpw/3A2tRj02oY40MzJqZBJyWabcDE6r0lwMgCJoC/s0/140809%2B029.jpg
143
+ https://lh3.googleusercontent.com/-1s_eiQB4x2k/WvXQEx59z2I/AAAAAAAAcI0/DvKYzWw3g6UNelqAQdOwrabcDE6qKgkxwCJoC/s0/001
144
+ https://lh3.googleusercontent.com/-1Rcbffs4iRI/WvaSsRCrxJI/AAAAAAAAcJs/e-N9tmjaTCIxEBE_jXwFjabcDE6Fwh4owCJoC/s0/001
145
+ https://lh3.googleusercontent.com/-VXUjuSl-dZY/WvK340_E9uI/AAAAAAAAVlg/HqKf1LgUcPUJNrLxHebCMabcDE6q36_bQCJoC/s0/gplus248254159.jpg
146
+ https://lh3.googleusercontent.com/-NlZRwcX_Lj8/WvQTijeAfJI/AAAAAAABNyo/jgoDgbZdTvsnLOGmmYlXMabcDE6ieZV4QCJoC/s0/67u8iii.png
147
+ https://lh3.googleusercontent.com/-8baBz80pf8Y/Wu8KG5lyGhI/AAAAAAACSyU/s3hasZzObK0VlntA1EBj-abcDE6gzRnLQCJoC/s0/%25D0%2592%25D0%25B5%25D1%2581%25D0%25B5%25D0%25BD%25D0%25BD%25D0%25B8%25D0%25B5%2B%25D0%25BA%25D1%2580%25D0%25B0%25D1%2581%25D0%25B0%25D0%25B2%25D1%2586%25D1%258B.JPG
148
+ https://lh3.googleusercontent.com/-BBjhu17YIgg/W-gnZNaZeMI/AAAAAAABA-k/UMlSbNuE0DsSEPV8u3yf_abcDE69vFoBgCJoC/s0/gplus320347186.png
149
+ }.each_with_index do |link, i|
150
+ it "already high res image ##{i + 1}" do
151
+ assert DirectLink.google link
152
+ end
153
+ end
154
+ %w{
155
+ https://lh3.googleusercontent.com/-5USmSTZHWWI/WMi3q1GdgiI/AAAAAAAAQIA/lu9FTcUL6xMw4wdoW_I148Vm1abcDE6mwCJoC/w346-h195-n-k-no/Good%2BJob.gif
156
+ https://lh3.googleusercontent.com/-G8eSL3Xx2Kc/WxWt9M6LptI/AAAAAAAAB_c/KNF9RBxjL04Reb3fBtwYLI2AlabcDE67gCJoC/s0/20180602212016_IMG_0244.JPG
157
+ https://lh3.googleusercontent.com/-aUVoiLNsmAg/WzcsUU2xfNI/AAAAAAAAODw/DOBual6E1rkVLHh3SKZSzbpNQabcDE6OQCJoC/w530-h883-n-k-no/gplus-1797734754.mp4
158
+ //lh3.googleusercontent.com/proxy/hOIoIpMEmoVDSP40VRzM92Zw2AeLvEEhxfyKHCOxiNVPyiGvZik5rMvl3jYISLabcDE6mhZuk8pFEYJhX5BU2wy_dw=w530-h822-p
159
+ https://lh3.googleusercontent.com/-GP3BA3zGR5A/W0IwuVXlfmI/AAAAAAADROs/SH8rRlBDYTsHZiHpM45S3zpEiabcDE6PwCJoC/s0/%25D1%2582%25D0%25B0%25D0%25B4%25D0%25B6%25D0%25B8%25D0%25BA%25D1%2581%25D0%25BA%25D0%25BE%25D0%25B5%2B%25D1%2580%25D0%25B0%25D0%25B7%25D0%25BD%25D0%25BE%25D1%2582%25D1%2580%25D0%25B0%25D0%25B2%25D1%258C%25D0%25B5.png
160
+ https://lh3.googleusercontent.com/-DLODAbD9W7E/W27ob5XGCOI/AAAAAAADV8g/J_6RYR6UkKsc2RJOWRx6Q-NBVabcDE6xwCJoC/s0/1236080.jpg
161
+ https://lh3.googleusercontent.com/-cJajRreI87w/W4gW5uF4Q7I/AAAAAAADZKI/mw1YayYE-MY2-1OCCmjvgM3kbabcDE6ggCJoC/s0/2504855.jpg
162
+ https://lh3.googleusercontent.com/-rm-m1meCOMY/W92GhExMG-I/AAAAAAADsTw/bIAm5-1CIOYEpyPJLnxT8VmI_abcDE6dACJoC/s0/2659806_800n.jpg
163
+ https://lh3.googleusercontent.com/-z1nwsq4NOT4/XETT5qhXP-I/AAAAAAAAC0s/03kJ22drB1EdqZ97gUXCPHkLZabcDE6OACJoC/w530-h942-n/gplus1629127930.jpg
164
+ https://lh3.googleusercontent.com/-NiGph3ObOPg/XE3DgnavXlI/AAAAAAABvgE/pcPPCe88rsU1r941wwP76TVf_abcDE6kwCJoC/w530-h353-n/DSCF0753.JPG
165
+ https://lh3.googleusercontent.com/-QfChCi9Lj6A/XEciVlXmDhI/AAAAAAACuZw/iYzoMIxr7SsGzEFbk1LrqIdCdabcDE6HACJoC/w795-h1193-n-rw/z7765765765757575.jpg
166
+ }.each_with_index do |link, i|
167
+ it "gpluscomm_105636351696833883213_86400 ##{i + 1}" do
168
+ assert DirectLink.google link
169
+ end
170
+ end
171
+ %w{
172
+ https://4.bp.blogspot.com/--L451qLtGq8/W2MiFy8SVCI/AAAAAAAARgM/9qr4fr2PiV8IE4dabcDE6dEnlchx1V6vwCLcBGAs/w72-h72-p-k-no-nu/blood_moon_5k.jpg
173
+ https://3.bp.blogspot.com/-aZETNznngSo/W2xbe673_6I/AAAAAAAARhs/hkXvEk85ol4SqVeabcDE6uDGLyT4V45kACLcBGAs/w72-h72-p-k-no-nu/waterfall-1280x800-ireland-4k-19245.jpg
174
+ https://3.bp.blogspot.com/-ZmCuJkPliO8/W3WjIWvdjXI/AAAAAAAARi4/qO9I8kzJvCMRWsgabcDE6cOCQPCyu_xDQCLcBGAs/w72-h72-p-k-no-nu/ford_mustang_neon_lights_5k.jpg
175
+ https://2.bp.blogspot.com/-rb2PXLGZy0s/W2nQe3mXOSI/AAAAAAAARhQ/P8gV-bMtYbY2xxpabcDE6xu3XDTUaugxQCLcBGAs/w72-h72-p-k-no-nu/beach-bora-bora-clouds-753626.jpg
176
+ https://1.bp.blogspot.com/-FGnZn0040os/W3g7wnT2o-I/AAAAAAAARjE/xfgcR4fyvQgBgV5abcDE6YOVK4d4dTouwCLcBGAs/w72-h72-p-k-no-nu/axl_2018_movie_4k_8k-7680x4320.jpg
177
+ https://1.bp.blogspot.com/-h9kL603XSgY/XCe5rYiOczI/AAAAAAAARmY/qz_kMyR1q-scfs-abcDE6of_QocYK7RegCLcBGAs/w72-h72-p-k-no-nu/spider_man_into_the_spider_verse_hd_4k.jpg
178
+ https://2.bp.blogspot.com/-rb2PXLGZy0s/W2nQe3mXOSI/AAAAAAAARhQ/P8gV-bMtYbY2xxpabcDE6xu3XDTUaugxQCLcBGAs/s640/beach-bora-bora-clouds-753626.jpg
179
+ http://4.bp.blogspot.com/-poH-QXn7YGg/U-3ZTDkeF_I/AAAAAAAAISE/abcDE6b-v-g/w72-h72-p-k-no-nu/Top-24-Inspired-181.jpg
180
+ http://1.bp.blogspot.com/-iSU4orVuR9Y/VFYrwQZ5qYI/AAAAAAAAMnc/abcDE6aeplw/w72-h72-p-k-no-nu/Wolf%2Bphotography2.jpg
181
+ http://1.bp.blogspot.com/-vPQSh6RKijU/VEi7r3D-jJI/AAAAAAAAL2Q/abcDE6oDp5M/w72-h72-p-k-no-nu/Building%2BIn%2BLondon1-4__880.jpeg
182
+ http://1.bp.blogspot.com/-W4xKJSsVf3M/Uz73jPlctbI/AAAAAAAAGz4/abcDE6ILMeY/w72-h72-p-k-no-nu/Beautiful+Japanese+places4.jpg
183
+ https://1.bp.blogspot.com/-__qsdLxNtcQ/XhaOQle-ECI/AAAAAAAABQ4/S_7SGG_K8eQ7abcDE6yPvTj9OyBfr_1sQCLcBGAsYHQ/w1200-h630-p-k-no-nu/iceland_poppies_orange_flowers_field-wallpaper-3840x2160.jpg
184
+ https://lh3.googleusercontent.com/-tV86KJvppss/XE2Nb2Z2aAI/AAAAAAAAGu4/94E_abcDE6AaJ59n43wmmd9rFa--OUuSQCJoC/w530-h338-n/IMG_6845%252C.png
185
+ https://lh3.googleusercontent.com/-cr-2ZSQGMPg/XFWLfetwr7I/AAAAAAAAQQQ/TbwDabcDE6wb4IDDO0SwfArFSZyDG0i0wCJoC/w530-h360-n/DSC07294.JPG
186
+ https://lh3.googleusercontent.com/-7ey25Wg_cQ4/X-Xy8LJPjMI/AAAAAAAAALE/raXQabcDE6EzEDfAg3TcsOU_xc9z4szcwCLcBGAsYHQ/w1200-h630-p-k-no-nu/1608905381407960-0.png
187
+ https://lh3.googleusercontent.com/C84udoMpvJmi95AqshlF9acaR11TCsUgO9rGB9gG1w5Wp3WpB7U52ntft4jBHAyDg2guo6zqI4KZim-nGx_jf_XGqqiVANe_Jr8u2g=w1400-k
188
+ }.each_with_index do |link, i|
189
+ it "largeimages ##{i + 1}" do
190
+ assert DirectLink.google link
191
+ end
192
+ end
193
+ %w{
194
+ //lh3.googleusercontent.com/proxy/S-Z1P92Dd_u0DjYrz5Tb7j0mbZsGjPVffx9lHPQZCqqCFr6vAigCPOG0fEYKU6d-wIvwYr2WriAfh97KjBr9Bq1RKgyHzHq2fpAotTnJYOLd3x_abcDE6GBVuAewE7qp2QDtCYyomyn3dGjZ6cKUnYIC8w=s110-p-k
195
+ }.each_with_index do |link, i|
196
+ it "posted_website_preview_##{i + 1}" do
197
+ assert DirectLink.google link
198
+ end
199
+ end
200
+ %w{
201
+ 0 https://lh3.googleusercontent.com/-okfIabcDE6g/AAAAAAAAAAI/AAAAAAAAZa0/FEv9H8woCBg/s30-p-rw-no/photo.jpg
202
+ 7 https://lh3.googleusercontent.com/-okfIabcDE6g/AAAAAAAAAAI/AAAAAAAAZa0/FEv9H8woCBg/s75-p-rw-no/photo.jpg
203
+ _ https://lh3.googleusercontent.com/-bhgxabcDE6I/AAAAAAAAAAI/AAAAAAAA4MI/_KuKE-Goa7E/s35-p-k-rw-no/photo.jpg
204
+ - https://lh3.googleusercontent.com/-tl9-abcDE6Y/AAAAAAAAAAI/AAAAAAAA8uY/vVeZX8SbTXI/s35-p-k-rw-no/photo.jpg
205
+ 4 https://lh3.googleusercontent.com/-Rb83abcDE64/AAAAAAAAAAI/AAAAAAAAEJc/DawCLQGnaSA/s45-p-k-rw-no/photo.jpg
206
+ }.each_slice 2 do |i, link|
207
+ it "Google Plus userpic #{i}" do
208
+ assert DirectLink.google link
209
+ end
210
+ end
211
+ %w{
212
+ - https://lh3.googleusercontent.com/-abcDE6oWuoU/AAAAAAAAAAI/AAAAAAAAl90/ed314-fNMGg/s20-c-k-no/photo.jpg
213
+ just https://lh5.googleusercontent.com/-abcDE6YpZzU/AAAAAAAAAAI/AAAAAAAAAO4/WISrqFs1vT8/s46-c-k-no/photo.jpg
214
+ no-no https://lh5.googleusercontent.com/-abcDE6MkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/s64-c-k/photo.jpg
215
+ no-k-no https://lh3.googleusercontent.com/-abcDE66JAgg/AAAAAAAAAAI/AAAAAAAAAAA/AIcfdXD1lA9yGoSbsihWmGfnl6Z3Rn43WA/s64-c-mo/photo.jpg
216
+ _ https://lh6.googleusercontent.com/-abcDE6PRFk4/AAAAAAAAAAI/AAAAAAAAAAA/AIcfdXDBweE5VzGGy4zMraO_pqiLuFN3yQ/s46-c-k-no-mo/photo.jpg
217
+ }.each_slice 2 do |i, link|
218
+ it "Hangout userpic #{i}" do
219
+ assert DirectLink.google link
220
+ end
221
+ end
222
+ %w{
223
+ https://lh4.googleusercontent.com/abcDE6lyjNKXqSqszuiCgPMi7_oE5fVv1Sw373HXBYlguP8AnIPAqefTS6DbBuurVGYRSxGrtDyKei1hae9Djf6rjiwjJd0aauA5Bg-z=s615
224
+ https://lh5.googleusercontent.com/fRmAL_04p7oomNHCiV4tH4-agHSDBtLaWi_Tb6bgE5ZSHVu5OjQF3iRn06nNwP3ywZwdFP92zWM-abcDE6n6m0tDTBARuO6F9e0wYu_1=s685
225
+ https://lh5.googleusercontent.com/FcYUQBKLXWtFLEvbQduvu7FHUm2f7U_MVdMBVnNbpwfzKHIU-xABkudxw-abcDE60jFYRHedh7Is5Dg6qlgIQF1iSndlWjiKCTTsUo1w=s1080
226
+ https://lh5.googleusercontent.com/gcDb3dxbch9jUKPzKGawJiwtHcNS9wp6o2sc0aJj9wOWQA-u8kXmKIrZ-abcDE6ee6qLaT2p6OK2N7klTJ9CxU66OnpBhYJMg0q9QEdq5Q=s2160
227
+ }.each_with_index do |link, i|
228
+ it "google keep ##{i + 1}" do
229
+ assert DirectLink.google link
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+
237
+ describe "imgur" do
238
+
239
+ %w{
240
+ https://imgur.com/a/badlinkpattern
241
+ http://example.com/
242
+ https://imgur.com/gallery/abcD5.
243
+ }.each_with_index do |url, i|
244
+ it "ErrorBadLink_##{i + 1}" do
245
+ assert_raises DirectLink::ErrorBadLink do
246
+ DirectLink.imgur url
247
+ end
248
+ end
249
+ end
250
+
251
+ it "ErrorNotFound when album is empty" do
252
+ stub_request(:head, "https://api.imgur.com/3/album/abcDEF7/0.json")#.to_return(status: 200, body: "", headers: {})
253
+ stub_request(:get, "https://api.imgur.com/3/album/abcDEF7/0.json").to_return body: {data: {images: {}}}.to_json
254
+ e = assert_raises DirectLink::ErrorNotFound do
255
+ DirectLink.imgur "https://imgur.com/a/abcDEF7"
256
+ end
257
+ assert_nil e.cause if Exception.instance_methods.include? :cause # Ruby 2.1
258
+ end
259
+
260
+ valid_imgur_image_url_direct = "https://i.imgur.com/abcDEF7.jpg"
261
+ valid_imgur_image_url_album = "https://imgur.com/a/abcDEF7"
262
+ [404].each do |error_code|
263
+ it error_code do
264
+ e = assert_raises DirectLink::ErrorNotFound do
265
+ NetHTTPUtils.stub :request_data, ->*{ raise NetHTTPUtils::Error.new "", error_code } do
266
+ DirectLink.imgur valid_imgur_image_url_direct
267
+ end
268
+ end
269
+ assert_equal error_code, e.cause.code if Exception.instance_methods.include? :cause # Ruby 2.1
270
+ end
271
+ end
272
+ [599].each do |error_code|
273
+ it error_code do
274
+ e = assert_raises DirectLink::ErrorAssert do
275
+ NetHTTPUtils.stub :request_data, ->*{ raise NetHTTPUtils::Error.new "", error_code } do
276
+ DirectLink.imgur valid_imgur_image_url_direct
277
+ end
278
+ end
279
+ assert_equal error_code, e.cause.code if Exception.instance_methods.include? :cause # Ruby 2.1
280
+ end
281
+ end
282
+ [400, 500, 502, 503].each do |error_code|
283
+ [
284
+ [valid_imgur_image_url_direct, :direct],
285
+ [valid_imgur_image_url_album, :album],
286
+ ].each do |url, kind|
287
+ it "retries a limited number of times on error #{error_code} (#{kind})" do
288
+ tries = 0
289
+ e = assert_raises DirectLink::ErrorAssert do
290
+ NetHTTPUtils.stub :request_data, ->*{ tries += 1; raise NetHTTPUtils::Error.new "", error_code } do
291
+ DirectLink.imgur url, 1.5
292
+ end
293
+ end
294
+ assert_equal error_code, e.cause.code if Exception.instance_methods.include? :cause # Ruby 2.1
295
+ assert_equal 2, tries
296
+ end
297
+ end
298
+ end
299
+ it "does not throw 400 after a successfull retry" do
300
+ stub_request(:head, "https://api.imgur.com/3/image/abcDEF7/0.json")
301
+ stub_request(:get, "https://api.imgur.com/3/image/abcDEF7/0.json").to_return body: {data: {width: 100, height: 200, type: "image/jpeg", link: "https://i.imgur.com/abcDEF7.jpg"}}.to_json
302
+ f = 0
303
+ m = NetHTTPUtils.method :request_data
304
+ NetHTTPUtils.stub :request_data, lambda{ |*args, **kwargs|
305
+ raise NetHTTPUtils::Error.new "", 400 if 1 == f += 1
306
+ m.call *args, **kwargs
307
+ } do
308
+ assert_equal [[valid_imgur_image_url_direct, 100, 200, "image/jpeg"]],
309
+ DirectLink.imgur(valid_imgur_image_url_direct, 1.5)
310
+ end
311
+ end
312
+
313
+ [
314
+ ["gifv" , "http://i.imgur.com/abcDEF7.gifv", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"http://i.imgur.com/abcDEF7h.gif", "width"=>100, "height"=>200, "type"=>"image/gif"}],
315
+ ["gif" , "https://i.imgur.com/abcD5.mp4", "https://api.imgur.com/3/image/abcD5/0.json", {"link"=>"https://i.imgur.com/abcD5.gif", "width"=>100, "height"=>200, "type"=>"image/gif"}],
316
+ ["mp4" , "https://i.imgur.com/abcDEF7.png", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.png", "width"=>100, "height"=>200, "type"=>"image/png"}],
317
+ ["fb" , "https://i.imgur.com/abcDEF7.jpg?fb", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
318
+ ["jpEg" , "https://i.imgur.com/abcDEF7.jpeg", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
319
+ [nil , "http://m.imgur.com/abcDEF7", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
320
+ [nil , "https://imgur.com/abcDEF7", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.mp4", "width"=>100, "height"=>200, "type"=>"video/mp4"}],
321
+ [nil , "http://imgur.com/abcDEF7", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
322
+ ["5 char photo" , "https://imgur.com/abcD5", "https://api.imgur.com/3/image/abcD5/0.json", {"link"=>"https://i.imgur.com/abcD5.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
323
+ ["single photo album" , "https://imgur.com/a/abcD5", "https://api.imgur.com/3/album/abcD5/0.json", {"images"=>["link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"]}],
324
+ ["5 char https album" , "https://imgur.com/a/abcD5", "https://api.imgur.com/3/album/abcD5/0.json", {"images"=>["link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"]*6}, 6],
325
+ ["7 char https album" , "https://imgur.com/a/abcDEF7", "https://api.imgur.com/3/album/abcDEF7/0.json", {"images"=>["link"=>"https://i.imgur.com/abcDEF7.png", "width"=>100, "height"=>200, "type"=>"image/png"]*2}, 2],
326
+ ["album zoomable" , "http://imgur.com/a/abcD5", "https://api.imgur.com/3/album/abcD5/0.json", {"images"=>["link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"]*49}, 49],
327
+ ["album not zoomable" , "http://imgur.com/a/abcD5", "https://api.imgur.com/3/album/abcD5/0.json", {"images"=>["link"=>"https://i.imgur.com/abcDEF7.png", "width"=>100, "height"=>200, "type"=>"image/png"]*20}, 20],
328
+ [nil , "http://imgur.com/gallery/abcDEF7/new", "https://api.imgur.com/3/gallery/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
329
+ [nil , "http://imgur.com/gallery/abcD5", "https://api.imgur.com/3/gallery/abcD5/0.json", {"images"=>["link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"]*7}, 7],
330
+ ["single image gallery?" , "http://imgur.com/gallery/abcDEF7", "https://api.imgur.com/3/gallery/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.png", "width"=>100, "height"=>200, "type"=>"image/png"}],
331
+ [nil , "http://imgur.com/gallery/abcD5/new", "https://api.imgur.com/3/gallery/abcD5/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
332
+ ["gallery mp4" , "https://imgur.com/gallery/abcDEF7", "https://api.imgur.com/3/gallery/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.mp4", "width"=>100, "height"=>200, "type"=>"video/mp4"}],
333
+ ["id belongs to an image", "http://imgur.com/gallery/abcDEF7", "https://api.imgur.com/3/gallery/abcDEF7/0.json", {status: [404, "Not Found"]}, DirectLink::ErrorNotFound],
334
+ ["id belongs to an album", "http://imgur.com/gallery/abcD5", "https://api.imgur.com/3/gallery/abcD5/0.json", {status: [404, "Not Found"]}, DirectLink::ErrorNotFound],
335
+ [nil , "http://imgur.com/r/wallpaper/abcDEF7", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg?1", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
336
+ [nil , "https://imgur.com/abcDEF7?third_party=1#_=_", "https://api.imgur.com/3/image/abcDEF7/0.json", {"link"=>"https://i.imgur.com/abcDEF7.jpg", "width"=>100, "height"=>200, "type"=>"image/jpeg"}],
337
+ ].each_with_index do |t, i|
338
+ desc, url, stub_head, stub, m = t
339
+ it "kinds of post ##{i + 1}#{" (#{desc})" if desc}" do
340
+ case m
341
+ when Class
342
+ stub_request(:head, stub_head).to_return(stub) if stub_head
343
+ assert_raises m do
344
+ DirectLink.imgur url
345
+ end
346
+ when NilClass
347
+ stub_request(:head, stub_head) if stub_head
348
+ stub_request(:get, stub_head).to_return body: {data: stub}.to_json if stub
349
+ real = DirectLink.imgur url
350
+ assert_equal 1, real.size
351
+ assert_equal((stub["images"] ? stub["images"][0].values : stub.values), real.first)
352
+ when Numeric
353
+ stub_request(:head, stub_head) if stub_head
354
+ stub_request(:get, stub_head).to_return body: {data: stub}.to_json if stub
355
+ real = DirectLink.imgur url
356
+ assert_equal m, real.size
357
+ assert_equal stub["images"][0].values, real.first
358
+ else
359
+ fail "bug in tests"
360
+ end
361
+ end
362
+ end
363
+ end
364
+
365
+ describe "_500px" do
366
+ [
367
+ ["https://500px.com/photo/123456789/morning-by", [1200, 800, "https://drscdn.500px.org/photo/123456789/m%3D900/v2?sig=#{Array.new(64){rand(16).to_s(16)}.join}", "jpeg"]],
368
+ ["https://500px.com/photo/1234567890/-flowers-by/", [1819, 2500, "https://drscdn.500px.org/photo/1234567890/m%3D900/v2?sig=#{Array.new(64){rand(16).to_s(16)}.join}", "jpeg"]],
369
+ ["https://500px.com/photo/1234567890/is%E5-by-%E7ku-/", [2048, 2048, "https://drscdn.500px.org/photo/1234567890/m%3D2048/v2?sig=#{Array.new(64){rand(16).to_s(16)}.join}", "jpeg"]]
370
+ ].each_with_index do |(input, (w, h, u, t)), i|
371
+ it "kinds of links" do
372
+ id = URI(input).path[/\d+/]
373
+ stub_request(:head, "https://api.500px.com/v1/photos?ids=#{id}")
374
+ stub_request(:get, "https://api.500px.com/v1/photos?ids=#{id}").to_return body: {"photos"=>{id=>{"width"=>w,"height"=>h}}}.to_json
375
+ stub_request(:head, "https://api.500px.com/v1/photos?ids=#{id}&image_size%5B%5D=#{w}")
376
+ stub_request(:get, "https://api.500px.com/v1/photos?ids=#{id}&image_size%5B%5D=#{w}").to_return body: {"photos"=>{id=>{"images"=>[{"format"=>t,"url"=>u}]}}}.to_json
377
+ result = DirectLink.method(:_500px).call input
378
+ assert_equal [w, h, u, t], result, "#{input} :: #{result.inspect} != #{[w, h, u, t].inspect}"
379
+ end
380
+ end
381
+ end
382
+
383
+ describe "flickr" do
384
+ [
385
+ ["https://www.flickr.com/photos/tomas-/12345678901/", DirectLink::ErrorNotFound],
386
+ ["https://www.flickr.com/photos/12345678@N07/12345678901", DirectLink::ErrorNotFound],
387
+ ["https://www.flickr.com/photos/12345678@N00/12345678901/", [3000, 2000, "https://live.staticflickr.com/7757/12345678901_ed5178cc6a_o.jpg"]], # trailing slash
388
+ ["https://www.flickr.com/photos/jacob_schmidt/12345678901/in/album-12345678901234567/", DirectLink::ErrorNotFound], # username in-album
389
+ ["https://www.flickr.com/photos/tommygi/1234567890/in/dateposted-public/", [1600, 1062, "https://live.staticflickr.com/5249/1234567890_29fae96e38_h.jpg"]], # username in-public
390
+ ["https://www.flickr.com/photos/123456789@N02/12345678901/in/album-12345678901234567/", DirectLink::ErrorNotFound],
391
+ ["https://www.flickr.com/photos/123456789@N03/12345678901/in/dateposted-public/", [4621, 3081, "https://live.staticflickr.com/3796/12345678901_f751b35aeb_o.jpg"]], # userid in-public
392
+ ["https://www.flickr.com/photos/frank3/1234567890/in/photolist#{"-6KVb92"*50}", [4096, 2723, "https://live.staticflickr.com/2499/1234567890_dfa75a41cc_4k.jpg"]],
393
+ ["https://www.flickr.com/photos/patricksloan/12345678901/sizes/l", [2048, 491, "https://live.staticflickr.com/5572/12345678900_fec4783d79_k.jpg"]],
394
+ ["https://flic.kr/p/abcDEF", [5120, 3413, "https://live.staticflickr.com/507/12345678901_1bd49c5ebd_5k.jpg"]],
395
+ ].each_with_index do |(input, expectation), i|
396
+ it "kinds of links" do
397
+ stub_request(:head, /\Ahttps:\/\/api\.flickr\.com\/services\/rest\/\?api_key=#{ENV["FLICKR_API_KEY"]}&format=json&method=flickr\.photos\.getSizes&nojsoncallback=1&photo_id=[\da-zA-Z]+\z/)
398
+ if expectation.is_a? Class
399
+ stub_request(:get, /\Ahttps:\/\/api\.flickr\.com\/services\/rest\/\?api_key=#{ENV["FLICKR_API_KEY"]}&format=json&method=flickr\.photos\.getSizes&nojsoncallback=1&photo_id=[\da-zA-Z]+\z/).to_return body: {"stat"=>"fail", "code"=>1, "message"=>"Photo not found"}.to_json
400
+ assert_raises expectation, input do
401
+ DirectLink.method(:flickr).call input
402
+ end
403
+ else
404
+ w, h, u = expectation
405
+ stub_request(:get, /\Ahttps:\/\/api\.flickr\.com\/services\/rest\/\?api_key=#{ENV["FLICKR_API_KEY"]}&format=json&method=flickr\.photos\.getSizes&nojsoncallback=1&photo_id=[\da-zA-Z]+\z/).to_return body: {"stat"=>"ok", "sizes"=>{ "size"=>[
406
+ {"width"=>w/2, "height"=>h , "source"=>u*2},
407
+ {"width"=>w , "height"=>h , "source"=>u },
408
+ {"width"=>w , "height"=>h/2, "source"=>u*2},
409
+ ] } }.to_json
410
+ result = DirectLink.method(:flickr).call input
411
+ assert_equal expectation, result, "#{input} :: #{result.inspect} != #{expectation.inspect}"
412
+ end
413
+ end
414
+ end
415
+ end
416
+
417
+ describe "wiki" do
418
+ [
419
+ ["https://en.wikipedia.org/wiki/Prostitution_by_country#/media/File:Prostitution_laws_of_the_world.PNG", "https://upload.wikimedia.org/wikipedia/commons/e/e8/Prostitution_laws_of_the_world.PNG"],
420
+ ["https://en.wikipedia.org/wiki/Third_Party_System#/media/File:United_States_presidential_election_results,_1876-1892.svg", DirectLink::ErrorAssert],
421
+ ["http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg", "https://upload.wikimedia.org/wikipedia/commons/0/0d/Eduard_Bohlen_anagoria.jpg"],
422
+ ["https://en.wikipedia.org/wiki/Spanish_Civil_War#/media/File:Alfonso_XIIIdeEspa%C3%B1a.jpg", "https://upload.wikimedia.org/wikipedia/commons/f/fb/Alfonso_XIIIdeEspa%C3%B1a.jpg"], # escaped input URI
423
+ ["https://en.wikipedia.org/wiki/File:Tyson_Lewis.jpg", "https://upload.wikimedia.org/wikipedia/en/c/cd/Tyson_Lewis.jpg"], # exists only in en.wiki, not in commons
424
+ ].each_with_index do |(input, expectation), i|
425
+ it "kinds of links" do
426
+ url = URI(input).tap{ |_| _.fragment = nil }.to_s
427
+ stub_request(:head, url)
428
+ stub_request(:head, "https://#{URI(input).hostname}/w/api.php?action=query&format=json&iiprop=url&prop=imageinfo&titles=#{input[/(File:.+)/]}")
429
+ if expectation.is_a? Class
430
+ stub_request(:get, "https://en.wikipedia.org/w/api.php?action=query&format=json&iiprop=url&prop=imageinfo&titles=#{input[/(File:.+)/]}").to_return body: {
431
+ "query"=>{"pages"=>{"-1"=>{}}}
432
+ }.to_json
433
+ assert_raises expectation, input do
434
+ DirectLink.wiki input
435
+ end
436
+ else
437
+ stub_request(:get, "https://#{URI(input).hostname}/w/api.php?action=query&format=json&iiprop=url&prop=imageinfo&titles=#{input[/(File:.+)/]}").to_return body: {
438
+ "query"=>{"pages"=>{"-1"=>{"imageinfo"=>[{"url"=>expectation}]}}}
439
+ }.to_json
440
+ result = DirectLink.wiki input
441
+ assert_equal expectation, result, "#{input} :: #{result.inspect} != #{expectation.inspect}"
442
+ end
443
+ end
444
+ end
445
+ end
446
+
447
+ describe "reddit" do
448
+ [
449
+ ["https://i.redd.it/abcde6k7r5xz.jpg", [true, "https://i.redd.it/abcde6k7r5xz.jpg"]],
450
+ ["https://www.reddit.com/r/cacography/comments/abcde6/c/", [true, "http://i.imgur.com/abcde6Z.jpg"], false, "abcde6"],
451
+ ["http://redd.it/abcde6", [true, "http://i.imgur.com/abcde6Z.jpg"], false, "abcde6"], # TODO maybe check that it calls #imgur recursively
452
+ ["http://redd.it/abcde6", [true, "https://i.redd.it/abcde66ehrg11.jpg"], false, "abcde6"],
453
+ ["https://reddit.com/abcde6", [true, "https://i.ytimg.com/vi/abcde6RbIeU/hqdefault.jpg"],
454
+ false, "abcde6",
455
+ {"type"=>"youtube.com","oembed"=>{"thumbnail_url"=>"https://i.ytimg.com/vi/abcde6RbIeU/hqdefault.jpg"}},
456
+ nil,
457
+ "http://www.youtube.com/watch?v=abcde6RbIeU&amp;feature=g-vrec",
458
+ ],
459
+ ["https://www.reddit.com/r/hangers/comments/abcde6/tara_radovic/", [true, "https://i.imgur.com/abcDE6u.jpg"],
460
+ false, "abcde6",
461
+ nil,
462
+ "t3_abcde7",
463
+ ], # "crossport" from Imgur
464
+ ["https://www.reddit.com/gallery/abcde6",
465
+ [true, [
466
+ ["image/jpg", 1440, 1440, "https://preview.redd.it/abcde6j6vee51.jpg?width=1440&format=pjpg&auto=webp&s=b79952f8364bb98692d978944347f19e28774d1b"],
467
+ ["image/jpg", 2441, 2441, "https://preview.redd.it/abcde6j6vee51.jpg?width=2441&format=pjpg&auto=webp&s=455e669356550351e6b8768d8009de616c11142a"],
468
+ ] ],
469
+ false, "abcde6",
470
+ nil,
471
+ nil,
472
+ "https://www.reddit.com/gallery/abcde6",
473
+ {
474
+ "x31msdjabcde6"=>{
475
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
476
+ "y"=>1440,
477
+ "x"=>1440,
478
+ "u"=>"https://preview.redd.it/abcde6j6vee51.jpg?width=1440&amp;format=pjpg&amp;auto=webp&amp;s=b79952f8364bb98692d978944347f19e28774d1b",
479
+ },
480
+ },
481
+ "mwkzq6jabcde6"=>{
482
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
483
+ "y"=>2441,
484
+ "x"=>2441,
485
+ "u"=>"https://preview.redd.it/abcde6j6vee51.jpg?width=2441&amp;format=pjpg&amp;auto=webp&amp;s=455e669356550351e6b8768d8009de616c11142a",
486
+ },
487
+ },
488
+ },
489
+ ],
490
+ ["https://www.reddit.com/abcde6",
491
+ [true, [
492
+ ["image/jpg", 1440, 1440, "https://preview.redd.it/abcde6j6vee51.jpg?width=1440&format=pjpg&auto=webp&s=b79952f8364bb98692d978944347f19e28774d1b"],
493
+ ["image/jpg", 2441, 2441, "https://preview.redd.it/abcde6j6vee51.jpg?width=2441&format=pjpg&auto=webp&s=455e669356550351e6b8768d8009de616c11142a"],
494
+ ] ],
495
+ false, "abcde6",
496
+ nil,
497
+ nil,
498
+ "https://www.reddit.com/gallery/abcde6",
499
+ {
500
+ "x31msdjabcde6"=>{
501
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
502
+ "y"=>1440,
503
+ "x"=>1440,
504
+ "u"=>"https://preview.redd.it/abcde6j6vee51.jpg?width=1440&amp;format=pjpg&amp;auto=webp&amp;s=b79952f8364bb98692d978944347f19e28774d1b",
505
+ },
506
+ },
507
+ "mwkzq6jabcde6"=>{
508
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
509
+ "y"=>2441,
510
+ "x"=>2441,
511
+ "u"=>"https://preview.redd.it/abcde6j6vee51.jpg?width=2441&amp;format=pjpg&amp;auto=webp&amp;s=455e669356550351e6b8768d8009de616c11142a",
512
+ },
513
+ },
514
+ },
515
+ ],
516
+ ["https://www.reddit.com/gallery/abcde6", [true, "https://www.reddit.com/gallery/abcde6"], false, "abcde6"], # deleted gallery
517
+ ["https://www.reddit.com/abcde6", [true, "https://www.reddit.com/r/Firewatch/comments/abcde7/new_wallpaper/"],
518
+ false, "abcde6",
519
+ nil,
520
+ "t3_abcde7",
521
+ "/r/Firewatch/comments/abcde7/new_wallpaper/",
522
+ ], # deleted gallery
523
+ ["https://www.reddit.com/abcde6",
524
+ [true, [
525
+ ["image/jpg", 500, 500, "https://preview.redd.it/abcde6eexo461.jpg?width=500&format=pjpg&auto=webp&s=df211fe0699e3970681ffe493ed1af79725857e8"],
526
+ ["image/jpg", 720, 446, "https://preview.redd.it/abcde6hexo461.jpg?width=720&format=pjpg&auto=webp&s=5e34ab0e6d54c0acfdb47f1daaf283087c5ad6a6"],
527
+ ["image/jpg", 713, 588, "https://preview.redd.it/abcde6lexo461.jpg?width=713&format=pjpg&auto=webp&s=969dfb52bedd6f0055249aa8b7454b23adaa946e"],
528
+ ] ],
529
+ false, "abcde6",
530
+ nil,
531
+ nil,
532
+ "https://www.reddit.com/gallery/abcde6",
533
+ {
534
+ "71t8ljeabcde6"=>{
535
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
536
+ "y"=>500,
537
+ "x"=>500,
538
+ "u"=>"https://preview.redd.it/abcde6eexo461.jpg?width=500&amp;format=pjpg&amp;auto=webp&amp;s=df211fe0699e3970681ffe493ed1af79725857e8",
539
+ },
540
+ },
541
+ "bfmjbtjabcde6"=>{"status"=>"failed"},
542
+ "c11nt7habcde6"=>{
543
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
544
+ "y"=>446,
545
+ "x"=>720,
546
+ "u"=>"https://preview.redd.it/abcde6hexo461.jpg?width=720&amp;format=pjpg&amp;auto=webp&amp;s=5e34ab0e6d54c0acfdb47f1daaf283087c5ad6a6",
547
+ },
548
+ },
549
+ "67mqvllabcde6"=>{
550
+ "status"=>"valid", "m"=>"image/jpg", "s"=>{
551
+ "y"=>588,
552
+ "x"=>713,
553
+ "u"=>"https://preview.redd.it/abcde6lexo461.jpg?width=713&amp;format=pjpg&amp;auto=webp&amp;s=969dfb52bedd6f0055249aa8b7454b23adaa946e"
554
+ },
555
+ },
556
+ },
557
+ ], # failed media
558
+ ["https://www.reddit.com/r/CatsStandingUp/duplicates/abcde6/cat/",
559
+ [true, "https://v.redd.it/abcde6fb6w721/DASH_2_4_M?source=fallback"],
560
+ false, "abcde6",
561
+ {"reddit_video"=>{"fallback_url"=>"https://v.redd.it/abcde6fb6w721/DASH_2_4_M?source=fallback"}},
562
+ nil,
563
+ "https://v.redd.it/abcde6fb6w721",
564
+ ],
565
+ ["https://www.reddit.com/r/MapPorn/comments/abd123/________aaaabdeeeeeefggghhhiilmmnnoorrrssttuvyyz/",
566
+ [true, [
567
+ ["image/gif", 500, 375, "https://i.redd.it/00146fghhjkox.gif"],
568
+ ["image/jpg", 1000, 600, "https://preview.redd.it/1369aacghkswy.jpg?width=1000&format=pjpg&auto=webp&s=0122334444557777778889aaabbbbcdddddeeeef"],
569
+ ]],
570
+ false, "abd123",
571
+ nil,
572
+ nil,
573
+ "https://www.reddit.com/gallery/abd123",
574
+ {
575
+ "00146fghhjkox"=>{
576
+ "status"=>"valid",
577
+ "e"=>"AnimatedImage",
578
+ "m"=>"image/gif",
579
+ "p"=>[],
580
+ "s"=>{
581
+ "y"=>375,
582
+ "gif"=>"https://i.redd.it/00146fghhjkox.gif",
583
+ "mp4"=>"",
584
+ "x"=>500
585
+ },
586
+ "id"=>"00146fghhjkox"
587
+ },
588
+ "1369aacghkswy"=>{
589
+ "status"=>"valid",
590
+ "e"=>"Image",
591
+ "m"=>"image/jpg",
592
+ "p"=>[],
593
+ "s"=>{
594
+ "y"=>600,
595
+ "x"=>1000,
596
+ "u"=>"https://preview.redd.it/1369aacghkswy.jpg?width=1000&amp;format=pjpg&amp;auto=webp&amp;s=0122334444557777778889aaabbbbcdddddeeeef"
597
+ },
598
+ "id"=>"1369aacghkswy"
599
+ }
600
+ },
601
+ ]
602
+ ].each_with_index do |(input, expectation, is_self, id, media, crosspost_parent, url, media_metadata), i|
603
+ it "kinds of links" do
604
+ unless is_self.nil?
605
+ fail "bug in test: no id" unless id
606
+ stub_request(:post, "https://www.reddit.com/api/v1/access_token").to_return body: {"access_token"=>"123456789012-abcDE6CFsglE63e5F1v8ThrVa9HThg"}.to_json
607
+ stub_request(:get, "https://oauth.reddit.com/by_id/t3_#{id}?api_type=json").to_return body: {
608
+ "data"=>{"children"=>[{"data"=>{
609
+ "is_self"=>is_self,
610
+ "media"=>media,
611
+ "crosspost_parent"=>crosspost_parent,
612
+ "url"=>(url||expectation[1]),
613
+ "media_metadata"=>media_metadata,
614
+ # "permalink"=>"/r/cacography/comments/32tq0i/c/",
615
+ # "selftext"=>"",
616
+ }}]}
617
+ }.to_json
618
+ end
619
+ result = DirectLink.reddit input
620
+ assert_equal expectation, result, "#{input} :: #{result.inspect} != #{expectation.inspect}"
621
+ end
622
+ end
623
+ end
624
+
625
+ describe "vk" do
626
+ [
627
+ ["https://vk.com/wall-123456091_7806", [960, 1280, "https://userapi.com/impf/c123456/v123456900/a72f1/7OZ8ux9Wcwo.jpg"], "wall", [
628
+ [97, 130, "https://sun9-3.userapi.com/impf/c123456/v123456900/a72f1/7OZ8ux9Wcwo.jpg?size=98x130&quality=96&sign=1f2bdc7b856b2a8f60297e3ac2c58689&c_uniq_tag=ggVFjBTdFDFuIz7Ee5o48mEP3fWLixCvjuPmehhyaTI&type=album"],
629
+ [960, 1280, "https://sun9-3.userapi.com/impf/c123456/v123456900/a72f1/7OZ8ux9Wcwo.jpg?size=960x1280&quality=96&proxy=1&sign=985328651fa11a0daef2c2a324f2de09&c_uniq_tag=vKCnEFEDol5QYM0qPrYAkbDwzYo6U_jo0SOR8iQO7HA&type=album"],
630
+ ] ],
631
+ ["https://vk.com/wall-123456611_454?z=photo-123456611_457239340%2Fwall-123456611_454", [1280, 960, "https://userapi.com/impf/c123456/v123456578/1a62f6/VB4SdR1O6Tg.jpg"], "photos", [
632
+ [130, 97, "https://sun9-59.userapi.com/impf/c123456/v123456578/1a62f6/VB4SdR1O6Tg.jpg?size=130x98&quality=96&sign=1e56a7310fd780cd48bed7304a369b1c&c_uniq_tag=xJlpXkJvi0P9WQhcM5acgLHqSUzXK3FGahuZHHncZQY&type=album"],
633
+ [1280, 960, "https://sun9-59.userapi.com/impf/c123456/v123456578/1a62f6/VB4SdR1O6Tg.jpg?size=1280x960&quality=96&proxy=1&sign=f5825d997937393a5c0a95c6775dce19&c_uniq_tag=2ulEOYXMOAN-KJm7HPNZYNcScyikyHdIsRmeqM8i16w&type=album"],
634
+ ] ],
635
+ ["https://vk.com/wall-123456091_7946?z=photo-123456091_457243312%2Falbum-123456091_00%2Frev", [1280, 875, "https://userapi.com/impf/c123456/v123456134/1b6b36/0IsDFb-Hda4.jpg"], "photos", [
636
+ [130, 89, "https://sun9-20.userapi.com/impf/c123456/v123456134/1b6b36/0IsDFb-Hda4.jpg?size=130x89&quality=96&sign=20f98d85da83704a93641c258dd9fb98&c_uniq_tag=PcNYWJFing2At2LiJVOYQOTUd_MbMLxiyPybwnyzaN4&type=album"],
637
+ [1280, 875, "https://sun9-20.userapi.com/impf/c123456/v123456134/1b6b36/0IsDFb-Hda4.jpg?size=1280x875&quality=96&proxy=1&sign=9b1e295c741c53b9485f4156da36ecde&c_uniq_tag=o7eFSi5F74SmkTirdrIS19nh8CEG32Od6yPvX9IPXds&type=album"],
638
+ ] ],
639
+ ["https://vk.com/id12345627?z=photo12345627_456241143%2Falbum12345627_0", [1920, 1440, "https://userapi.com/impf/c123456/v123456944/167836/bP9z41BybhI.jpg"], "photos", [
640
+ [130, 97, "https://sun9-9.userapi.com/impf/c123456/v123456944/167836/bP9z41BybhI.jpg?size=130x98&quality=96&sign=5a7785886e75cde41f7ddc190775c941&c_uniq_tag=uJX7tU35qumnZFlAUe-EhyVVT1FDBoHmp_8Z-dFAH_I&type=album"],
641
+ [1920, 1440, "https://sun9-9.userapi.com/impf/c123456/v123456944/167836/bP9z41BybhI.jpg?size=1920x1440&quality=96&proxy=1&sign=56acc1844d38b491c1b2e4dbedd76ba4&c_uniq_tag=06VziJvbo7gkdo5t0AUEQRs6EX8UrmiT2XkfGRmfRz8&type=album"],
642
+ ] ],
643
+ ["https://vk.com/id12345627?z=photo12345627_456241143", [1920, 1440, "https://userapi.com/impf/c123456/v123456944/167836/bP9z41BybhI.jpg"], "photos", [
644
+ [130, 97, "https://sun9-9.userapi.com/impf/c123456/v123456944/167836/bP9z41BybhI.jpg?size=130x98&quality=96&sign=5a7785886e75cde41f7ddc190775c941&c_uniq_tag=uJX7tU35qumnZFlAUe-EhyVVT1FDBoHmp_8Z-dFAH_I&type=album"],
645
+ [1920, 1440, "https://sun9-9.userapi.com/impf/c123456/v123456944/167836/bP9z41BybhI.jpg?size=1920x1440&quality=96&proxy=1&sign=56acc1844d38b491c1b2e4dbedd76ba4&c_uniq_tag=06VziJvbo7gkdo5t0AUEQRs6EX8UrmiT2XkfGRmfRz8&type=album"],
646
+ ] ],
647
+ ["https://vk.com/photo1_123187843?all=1", [2560, 1913, "https://userapi.com/impf/c123/v123001/6/53_VwoACy4I.jpg"], "photos", [
648
+ [130, 98, "https://sun1-23.userapi.com/impf/c123/v123001/6/53_VwoACy4I.jpg?size=130x97&quality=96&sign=f8f5b706de9bf4cba377c4192ca0faeb&c_uniq_tag=Hq3mDN7NbJvgRd82cKsQpJ45Tze8deKbUX4FZDwVZGQ&type=album"],
649
+ [2560, 1913, "https://sun1-23.userapi.com/impf/c123/v123001/6/53_VwoACy4I.jpg?size=2560x1913&quality=96&proxy=1&sign=c55f340348a35dd86542875a57ad8537&c_uniq_tag=yANq6f3QAVxu7M1xuy6fMu1JgkYOPqB1jBV4bQVjc0Y&type=album"],
650
+ ] ],
651
+ ["https://vk.com/photo123456340_456243948?rev=1", [1583, 1080, "https://userapi.com/impf/c123456/v123456479/321be/9rZaJ2QTdz4.jpg"], "photos", [
652
+ [130, 89, "https://sun9-21.userapi.com/impf/c123456/v123456479/321be/9rZaJ2QTdz4.jpg?size=130x89&quality=96&sign=4095eaa6d55dbb6af3a6d3baed593ecb&c_uniq_tag=9comrJGukZcMLlCDrmQMWhFtne217kVaZcafgoQmCVM&type=album"],
653
+ [1583, 1080, "https://sun9-21.userapi.com/impf/c123456/v123456479/321be/9rZaJ2QTdz4.jpg?size=1583x1080&quality=96&proxy=1&sign=27a9d92fcbd2cf748531a89278281cc3&c_uniq_tag=iwiLvdT52wwR8avHhjDUwPUozsvMpVyuFHHACavY2zA&type=album"],
654
+ ] ],
655
+ ["https://vk.com/photo-123456973_456242404", [1486, 1000, "https://userapi.com/impf/c123456/v123456877/8578e/m6AJWiskiKE.jpg"], "photos", [
656
+ [130, 87, "https://sun9-51.userapi.com/impf/c123456/v123456877/8578e/m6AJWiskiKE.jpg?size=130x87&quality=96&sign=c89c47cebc77fd0063dbe4891245aebe&c_uniq_tag=pSxKInGItqdMMW_83z9SXDzIi74Zl9UJ-P-snVmzHHY&type=album"],
657
+ [1486, 1000, "https://sun9-51.userapi.com/impf/c123456/v123456877/8578e/m6AJWiskiKE.jpg?size=1486x1000&quality=96&proxy=1&sign=f67bdef805a49775776ba9fe3ddddfdd&c_uniq_tag=2IwG6BAja36QQl3mDsUcWS6PC7ozt6TSExLsfRXRyzA&type=album"],
658
+ ] ],
659
+ ["https://vk.com/feed?section=likes&z=photo-123456754_456261460%2Fliked1234566", [1024, 1335, "https://userapi.com/impf/c123456/v123456353/895b6/izQJresLdf0.jpg"], "photos", [
660
+ [100, 130, "https://sun9-55.userapi.com/impf/c123456/v123456353/895b6/izQJresLdf0.jpg?size=100x130&quality=96&sign=56c2f6f2e772c5cac6e891d62c6c563e&c_uniq_tag=HHJ8PsYFgnwmuq9OfND51luLXJve81QbpWvudWXv5aw&type=album"],
661
+ [1024, 1335, "https://sun9-55.userapi.com/impf/c123456/v123456353/895b6/izQJresLdf0.jpg?size=1024x1335&quality=96&proxy=1&sign=b66a4c70eeb4083a05907e36d18cc481&c_uniq_tag=42fraqliOifFUG1CvgahG1lg1txVMF5hbSVlAUkLwf8&type=album"],
662
+ ] ],
663
+ ["https://vk.com/likizimy?z=photo-12345651_456239941%2Fwall-12345651_1908", [1179, 1731, "https://userapi.com/impf/c123456/v123456571/60f7b/ryCPJIMyMkI.jpg"], "photos", [
664
+ [89, 130, "https://sun9-37.userapi.com/impf/c123456/v123456571/60f7b/ryCPJIMyMkI.jpg?size=89x130&quality=96&sign=3ae95211e73168c7b66e12daa854f922&c_uniq_tag=uaDVTYCfixIN4JKSGzgNSTsjiuOUi0658_CgoIF-mqc&type=album"],
665
+ [1179, 1731, "https://sun9-37.userapi.com/impf/c123456/v123456571/60f7b/ryCPJIMyMkI.jpg?size=1179x1731&quality=96&proxy=1&sign=7930a6d1b4a88ce1fa4954fc6643d3e1&c_uniq_tag=rTxHctkqrP4SQsbcECYGDCVt03A43cLYRN8eTnOFtD0&type=album"],
666
+ ] ],
667
+ ["https://vk.com/e_rod?z=photo123456340_457247118%2Fphotos123456340", [1728, 2160, "https://userapi.com/impf/c123456/v123456596/c7714/oImGe4o1ZJI.jpg"], "photos", [
668
+ [104, 130, "https://sun9-53.userapi.com/impf/c123456/v123456596/c7714/oImGe4o1ZJI.jpg?size=104x130&quality=96&sign=6ad356e84dcd6bbf2069d9b869a7bdb1&c_uniq_tag=8KN44nSlDjD-FMDiRhHqk6udwiqZfn5n1qqPNFwu_nI&type=album"],
669
+ [1728, 2160, "https://sun9-53.userapi.com/impf/c123456/v123456596/c7714/oImGe4o1ZJI.jpg?size=1728x2160&quality=96&proxy=1&sign=c7961913ff3efd5064f2ed6c394288f2&c_uniq_tag=CA61KgCRsRXbHm4VJEYYskjpjpSiTHI7UPV4FsCPRmI&type=album"],
670
+ ] ],
671
+ ["https://vk.com/wasserman.anatoliy?w=wall-123457771_353", [864, 1080, "https://userapi.com/impg/13456AABBCDDKKOQSTUWXZbcfghhhjlpqquwyz/z0UtQg2M1s4.jpg"], "wall", [
672
+ [104, 130, "https://sun9-38.userapi.com/impg/13456AABBCDDKKOQSTUWXZbcfghhhjlpqquwyz/z0UtQg2M1s4.jpg?size=104x130&quality=96&sign=a6012bb508e208c7b2b22c42872215e4&c_uniq_tag=qX06fI2p7bmyvnta_Pt2omQ-l5RBLcrG1DBsLWx1jJA&type=album"],
673
+ [864, 1080, "https://sun9-38.userapi.com/impg/13456AABBCDDKKOQSTUWXZbcfghhhjlpqquwyz/z0UtQg2M1s4.jpg?size=864x1080&quality=96&sign=8f02c4c6794efd653ed79609bf0850c8&c_uniq_tag=6WP6TY0RTDiG_8NK1lkYUOge22jtjkzGLLwc6IDxjjg&type=album"],
674
+ ] ],
675
+ ].each_with_index do |(input, expectation, mtd, stub), i|
676
+ it "kinds of links" do
677
+ stub = {sizes: stub.map{ |w,h,u| {width: w, height: h, url: u} }}
678
+ stub_request(:post, "https://api.vk.com/method/#{mtd}.getById").to_return body: {response: [
679
+ mtd == "photos" ? stub : {attachments: [{type: :photo, photo: stub}]}
680
+ ] }.to_json
681
+ result = DirectLink.method(:vk).call input
682
+ assert_equal 1, result.size
683
+ result[0][-1].tap do |url|
684
+ url.replace( URI.parse(url).tap do |_|
685
+ _.host = _.host.split(?.).drop(1).join(?.)
686
+ _.query = nil
687
+ end.to_s )
688
+ end
689
+ assert_equal [expectation], result, "#{input} :: #{result.inspect} != #{expectation.inspect}"
690
+ end
691
+ end
692
+ end
693
+
694
+ describe "google" do
695
+ method = :google
696
+ [
697
+ "https://lh3.googleusercontent.com/-NVJgqmI_2Is/WqMM2OMYg-I/AAAAAAAALrk/5-p3JL3iZt0Ho9dOf_abcDE6zqwr3Wp0ACJoC/w424-h318-n/001",
698
+ "//lh3.googleusercontent.com/proxy/DZtTi5KL7PqiBwJc8weNGLk_Wi2UTaQH0AC_abcDE6iu0AbwyI2ywOk2XgdAjL7ceg=w530-h354-n",
699
+ "//1.bp.blogspot.com/-rYk_u-qROQc/WngolJ8M0LI/AAAAAAAAD-w/woivnaIVzasBPG5C2T1t-VrWKRd_abcDE6LcBGAs/w530-h278-p/i-469.jpg",
700
+ "https://2.bp.blogspot.com/-rb2PXLGZy0s/W2nQe3mXOSI/AAAAAAAARhQ/P8gV-abcDE62xxpTJNcYVxu3XDTUaugxQCLcBGAs/s640/beach-bora-bora-clouds-753626.jpg",
701
+ "http://4.bp.blogspot.com/-poH-QXn7YGg/U-3ZTDkeF_I/AAAAAAAAISE/mabcDE6-v-g/w72-h72-p-k-no-nu/Top-24-Inspired-181.jpg",
702
+ "https://lh5.googleusercontent.com/FcYUQBKLXWtFLEvbQduvu7FHUm2f7U_MVdMBVnNbpwfzKHIU-xABkudxw-abcDE60jFYRHedh7Is5Dg6qlgIQF1iSndlWjiKCTTsUo1w=s1080",
703
+ ].each do |input|
704
+ it "DirectLink() choses a method '#{method}' according to a domain" do
705
+ DirectLink.stub method, ->(link, timeout = nil){
706
+ assert_equal input, link
707
+ throw :_
708
+ } do
709
+ catch :_ do
710
+ DirectLink input
711
+ fail "DirectLink.#{method} was not called"
712
+ end
713
+ end
714
+ end
715
+ end
716
+ end
717
+
718
+ {
719
+ imgur: [
720
+ [%w{ https://imgur.com/abcD5 https://imgur.com/abcD5 }, [
721
+ %w{ https://imgur.com/abcD5 },
722
+ ] ],
723
+ [%w{ https://i.imgur.com/abcD5 https://imgur.com/abcD5 }, [
724
+ [*%w{ https://i.imgur.com/abcD5 https://imgur.com/abcD5 }, [302, "Moved Temporarily"]],
725
+ %w{ https://imgur.com/abcD5 },
726
+ ] ],
727
+ [%w{ https://m.imgur.com/abcD5 https://imgur.com/abcD5 }, [
728
+ %w{ https://m.imgur.com/abcD5 https://imgur.com/abcD5 },
729
+ %w{ https://imgur.com/abcD5 },
730
+ ] ],
731
+ [%w{ https://www.imgur.com/abcD5 https://imgur.com/abcD5 }, [
732
+ [*%w{ https://www.imgur.com/abcD5 https://imgur.com/abcD5 }, [301, "Moved Permanently"]],
733
+ %w{ https://imgur.com/abcD5 },
734
+ ] ],
735
+ [%w{ https://goo.gl/abcDE6 https://i.imgur.com/abcDEFY.png }, [
736
+ %w{ https://goo.gl/abcDE6 https://i.imgur.com/abcDEFY.png },
737
+ %w{ https://i.imgur.com/abcDEFY.png },
738
+ ] ],
739
+ ],
740
+ _500px: [
741
+ [%w{ https://500px.com/photo/123456789/milky-way https://500px.com/photo/123456789/milky-way }, [
742
+ %w{ https://500px.com/photo/123456789/milky-way },
743
+ ] ],
744
+ ],
745
+ flickr: [
746
+ ["https://www.flickr.com/photos/12345678@N07/12345678901/in/dateposted-public/", [
747
+ ["https://www.flickr.com/photos/12345678@N07/12345678901/in/dateposted-public/"],
748
+ ] ],
749
+ [["https://flic.kr/p/abcDEF", "https://www.flickr.com/photos/lopez/12345678901/"], [
750
+ ["https://flic.kr/p/abcDEF", "https://www.flickr.com/photo.gne?short=abcDEF"],
751
+ ["https://www.flickr.com/photo.gne?short=abcDEF", "/photo.gne?rb=1&short=abcDEF"],
752
+ ["https://www.flickr.com/photo.gne?rb=1&short=abcDEF", "/photos/lopez/12345678901/"],
753
+ ["https://www.flickr.com/photos/lopez/12345678901/"],
754
+ ] ],
755
+ ],
756
+ wiki: [
757
+ ["https://en.wikipedia.org/wiki/Third_Party_System#/media/File:United_States_presidential_election_results,_1876-1892.svg", [
758
+ %w{ https://en.wikipedia.org/wiki/Third_Party_System },
759
+ ] ],
760
+ [%w{ http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg https://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg }, [
761
+ %w{ http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg https://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg },
762
+ %w{ https://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg }
763
+ ] ],
764
+ ],
765
+ reddit: [
766
+ [%w{ https://www.reddit.com/r/abcdef/comments/abcde6/c/ }, [
767
+ %w{ https://www.reddit.com/r/abcdef/comments/abcde6/c/ },
768
+ ] ],
769
+ [%w{ http://redd.it/abcde6 https://www.reddit.com/comments/abcde6 }, [
770
+ [*%w{ http://redd.it/abcde6 https://redd.it/abcde6 }, [301, "Moved Permanently"]],
771
+ [*%w{ https://redd.it/abcde6 https://www.reddit.com/comments/abcde6 }, [302, "Temporarily Moved"]],
772
+ %w{ https://www.reddit.com/comments/abcde6 },
773
+ ] ],
774
+ [%w{ https://reddit.com/123456 https://www.reddit.com/r/funny/comments/123456/im_thinking/ }, [
775
+ [*%w{ https://reddit.com/123456 https://www.reddit.com/123456 }, [301, "Moved Permanently"]],
776
+ [*%w{ https://www.reddit.com/123456 https://www.reddit.com/r/funny/comments/123456/im_thinking/ }, [301, "Moved Permanently"]],
777
+ %w{ https://www.reddit.com/r/funny/comments/123456/im_thinking/ },
778
+ ] ],
779
+ [%w{ https://www.reddit.com/r/abcDEF/comments/abcde6/beard_trimmer/ }, [
780
+ %w{ https://www.reddit.com/r/abcDEF/comments/abcde6/beard_trimmer/ }
781
+ ] ], # NSFW causes redirect to /over_18? if the special cookie not provided # TODO: improve this test
782
+ ],
783
+ vk: [
784
+ ["https://vk.com/id12345678?z=photo12345678_456241143", [
785
+ ["https://vk.com/id12345678?z=photo12345678_456241143", nil, [418, ""]],
786
+ ] ],
787
+ ],
788
+ }.each do |method, tests|
789
+ describe "DirectLink() choses a method '#{method}' according to a domain" do
790
+ tests.each_with_index do |((input, expected), stub), i|
791
+ it "##{i + 1}" do
792
+ DirectLink.stub method, ->(link, timeout = nil, **__){
793
+ assert_equal (expected || input), link
794
+ throw :_
795
+ } do
796
+ (stub || []).each do |u, o, code|
797
+ if o
798
+ stub_request(:head, u).to_return status: (code || [302, "Found"]), headers: {location: o}
799
+ else
800
+ if code
801
+ stub_request(:head, u).to_return status: code
802
+ else
803
+ stub_request(:head, u)
804
+ end
805
+ end
806
+ end
807
+ catch :_ do
808
+ DirectLink input
809
+ fail "DirectLink.#{method} was not called"
810
+ end
811
+ end
812
+ end
813
+ end
814
+ end
815
+ end
816
+
817
+ # TODO: make a Reddit describe
818
+ it "retries limited amount of times on error JSON::ParserError" do
819
+ # TODO: the same but with the error expected to be gone after tries
820
+ stub_request(:post, "https://www.reddit.com/api/v1/access_token").to_return body: '{"access_token": "123456789000-17BDFFGGJJKNPVXZlt-2ACKQZfoswz", "token_type": "bearer", "expires_in": 3600, "scope": "*"}'
821
+ stub_request(:get, "https://oauth.reddit.com/by_id/t3_abcde6?api_type=json").to_return body: "invalid JSON"
822
+ tries = 0
823
+ m = JSON.method :load
824
+ e = assert_raises DirectLink::ErrorBadLink do
825
+ JSON.stub :load, ->*_,**__{ tries += 1; m.call *_,**__ } do
826
+ DirectLink.reddit "https://www.reddit.com/r/gifs/comments/abcde6/", 2.5
827
+ end
828
+ end
829
+ assert_instance_of JSON::ParserError, e.cause if Exception.instance_methods.include? :cause # Ruby 2.1
830
+ assert (3 <= tries)
831
+ assert (tries <= 4)
832
+ end
833
+
834
+ it "Reddit correctly parses out id when no token provided" do
835
+ stub_request(:head, "https://www.reddit.com/r/gifs/comments/9ftc8f/")
836
+ m = NetHTTPUtils.method :request_data
837
+ NetHTTPUtils.stub :request_data, lambda{ |u, *args, **kwargs|
838
+ throw :_ if u == "https://www.reddit.com/9ftc8f.json"
839
+ m.call u, *args, **kwargs
840
+ } do
841
+ t = ENV.delete "REDDIT_SECRETS"
842
+ begin
843
+ catch :_ do
844
+ DirectLink "https://www.reddit.com/r/gifs/comments/9ftc8f/"
845
+ fail
846
+ end
847
+ ensure
848
+ ENV["REDDIT_SECRETS"] = t
849
+ end
850
+ end
851
+ end
852
+
853
+ describe "throws ErrorBadLink if method does not match the link" do
854
+ %i{ google imgur flickr _500px wiki reddit vk }.each do |method|
855
+ ["", "test", "http://example.com/"].each_with_index do |url, i|
856
+ it "#{method} ##{i + 1}" do
857
+ assert_raises DirectLink::ErrorBadLink do
858
+ DirectLink.method(method).call url
859
+ end
860
+ end
861
+ end
862
+ end
863
+ end
864
+
865
+ end
866
+
867
+ describe "DirectLink()" do
868
+
869
+ # thanks to gem addressable
870
+ it "does not throw URI::InvalidURIError if there are brackets" do
871
+ stub_request(:head, "https://www.flickr.com/photos/nakilon/12345678900/%2520%5B2048x1152%5D").to_return status: [404, "Not Found"]
872
+ assert_equal 404, (
873
+ assert_raises NetHTTPUtils::Error do
874
+ DirectLink "https://www.flickr.com/photos/nakilon/12345678900/%20[2048x1152]"
875
+ end.code
876
+ )
877
+ end
878
+
879
+ it "throws ErrorNotFound when Reddit gallery is removed" do
880
+ stub_request(:head, "https://www.reddit.com/gallery/abcde6")
881
+ stub_request(:post, "https://www.reddit.com/api/v1/access_token").to_return body: {"access_token"=>"123456789012-abcde6cOO5V20ZD6J8WC6l36gMYRXQ"}.to_json
882
+ stub_request(:get, "https://oauth.reddit.com/by_id/t3_abcde6?api_type=json").to_return body: {
883
+ "data"=>{
884
+ "children"=>[{
885
+ "data"=>{
886
+ "selftext"=>"[удалено]",
887
+ "media_metadata"=>nil,
888
+ "is_self"=>false,
889
+ "permalink"=>"/r/woahdude/comments/abcde6/crystal_light/",
890
+ "url"=>"https://www.reddit.com/gallery/abcde6",
891
+ "media"=>nil
892
+ }
893
+ }]
894
+ }
895
+ }.to_json
896
+ assert_raises DirectLink::ErrorNotFound do
897
+ DirectLink "https://www.reddit.com/gallery/abcde6"
898
+ end
899
+ end
900
+
901
+ it "follows Reddit crosspost" do
902
+ stub_request(:head, "https://www.reddit.com/abcde6").to_return status: [301, "Moved Permanently"], headers: {location: "https://www.reddit.com/r/wallpapers/comments/abcde6/new_wallpaper/"}
903
+ stub_request(:head, "https://www.reddit.com/r/wallpapers/comments/abcde6/new_wallpaper/")
904
+ stub_request(:post, "https://www.reddit.com/api/v1/access_token").to_return body: {"access_token"=>"123456789012-abcde6WGKuIupoi5M3NtPVdI7bk1jg"}.to_json
905
+ stub_request(:get, "https://oauth.reddit.com/by_id/t3_abcde6?api_type=json").to_return body: {
906
+ "data"=>{
907
+ "children"=>[{
908
+ "data"=>{
909
+ "selftext"=>"",
910
+ "is_self"=>false,
911
+ "crosspost_parent"=>"t3_abcde7",
912
+ "permalink"=>"/r/wallpapers/comments/abcde6/new_wallpaper/",
913
+ "url"=>"/r/Firewatch/comments/abcde7/new_wallpaper/",
914
+ "media"=>nil,
915
+ }
916
+ }]
917
+ }
918
+ }.to_json
919
+ stub_request(:head, "https://www.reddit.com/r/Firewatch/comments/abcde7/new_wallpaper/")
920
+ stub_request(:get, "https://oauth.reddit.com/by_id/t3_abcde7?api_type=json").to_return body: {
921
+ "data"=>{
922
+ "children"=>[{
923
+ "data"=>{
924
+ "selftext"=>"I",
925
+ "media_metadata"=>{
926
+ "abcde62zhek51"=>{
927
+ "status"=>"valid",
928
+ "m"=>"image/png",
929
+ "s"=>{"y"=>1920, "x"=>4920, "u"=>"asd"},
930
+ },
931
+ "abcde72zhek51"=>{
932
+ "status"=>"valid",
933
+ "m"=>"image/png",
934
+ "s"=>{"y"=>1920, "x"=>4920, "u"=>"asd"},
935
+ }
936
+ },
937
+ "is_self"=>true,
938
+ "media"=>nil,
939
+ "permalink"=>"/r/Firewatch/comments/abcde7/new_wallpaper/",
940
+ "url"=>"https://www.reddit.com/r/Firewatch/comments/abcde7/new_wallpaper/",
941
+ }
942
+ }],
943
+ }
944
+ }.to_json
945
+ assert_equal %w{ image/png image/png }, DirectLink("https://www.reddit.com/abcde6").map(&:type)
946
+ end
947
+
948
+ it "throws ErrorBadLink if link is invalid" do
949
+ assert_equal "test".inspect, (
950
+ assert_raises DirectLink::ErrorBadLink do
951
+ DirectLink "test"
952
+ end
953
+ ).message
954
+ end
955
+
956
+ describe "does not shadow the internal exception" do
957
+ [
958
+ SocketError,
959
+ Errno::ECONNRESET,
960
+ ].each do |exception|
961
+ it "raises #{exception} from the redirect resolving stage" do
962
+ assert_raises exception do
963
+ NetHTTPUtils.stub :request_data, ->*{ raise exception.new } do
964
+ DirectLink "http://example.com/404"
965
+ end
966
+ end
967
+ end
968
+ end
969
+ it "raises Net::OpenTimeout -- server side issues can happen (not related to User Agent)" do
970
+ assert_raises Net::OpenTimeout do
971
+ NetHTTPUtils.stub :request_data, ->*{ raise Net::OpenTimeout.new } do
972
+ DirectLink "http://example.com/404"
973
+ end
974
+ end
975
+ end
976
+ it "HEAD Errno::ETIMEDOUT and GET FastImage::ImageFetchFailure are handled by DirectLink::NORMAL_EXCEPTIONS" do
977
+ assert_includes DirectLink::NORMAL_EXCEPTIONS, FastImage::ImageFetchFailure
978
+ assert_raises FastImage::ImageFetchFailure do
979
+ NetHTTPUtils.stub :request_data, ->_,mtd,*{ fail unless mtd == :HEAD; raise Errno::ETIMEDOUT } do
980
+ FastImage.stub :new, ->*{ raise FastImage::ImageFetchFailure } do
981
+ DirectLink "http://www.mesacc.edu/~hello12345/hello/images/photos/aerial/a1234.jpg"
982
+ end
983
+ end
984
+ end
985
+ end
986
+ [ # TODO this URLs may be reused from tests that check that this method calls internal method
987
+ [:google, "//lh3.googleusercontent.com/proxy/578BDGJKLLNPTZceiikqtww_Wi2UTaQH0AC_h2kuURiu0AbwyI2ywOk2XgdAjL7ceg=w530-h354-n"],
988
+ [:imgur, "http://imgur.com/ABCDEFG"],
989
+ [:flickr, "https://www.flickr.com/photos/12345678@N00/00013355778/"],
990
+ [:_500px, "https://500px.com/photo/123456789/milky-way"],
991
+ [:wiki, "http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg"],
992
+ [:reddit, "https://www.reddit.com/123456"],
993
+ [:vk, "https://vk.com/"],
994
+ ].each do |method, link|
995
+ it "can otherwise raise DirectLink::ErrorBadLink #{method}" do
996
+ stub_request(:head, link)
997
+ e = assert_raises(DirectLink::ErrorBadLink, link) do
998
+ DirectLink.stub method, ->*{ raise DirectLink::ErrorBadLink.new "test" } do
999
+ DirectLink link
1000
+ end
1001
+ end
1002
+ assert_equal "\"test\" -- if you think this link is valid, please report the issue", e.message
1003
+ end
1004
+ end
1005
+ end
1006
+
1007
+ describe "other domains tests" do
1008
+ [
1009
+ ["http://www.cute.org/wp-content/uploads/2010/10/Niagara.jpg", SocketError, /nodename nor servname provided, or not known|Name or service not known|getaddrinfo: Name does not resolve/, 0],
1010
+ ].each_with_index do |(input, expectation, message_string_or_regex, max_redirect_resolving_retry_delay), i|
1011
+ it "##{i + 1}" do
1012
+ if expectation.is_a? Class
1013
+ # TODO: move the stub to the array above
1014
+ stub_request(:head, input).to_raise expectation.new("Failed to open TCP connection to www.cute.org:80 (getaddrinfo: nodename nor servname provided, or not known)")
1015
+ e = assert_raises expectation, "for #{input}" do
1016
+ DirectLink input, max_redirect_resolving_retry_delay
1017
+ end
1018
+ if message_string_or_regex.is_a? String
1019
+ assert_equal message_string_or_regex, e.message, "for #{input}"
1020
+ else
1021
+ assert_match message_string_or_regex, e.message, "for #{input}"
1022
+ end
1023
+ else
1024
+ u, w, h, t = expectation
1025
+ result = DirectLink input, max_redirect_resolving_retry_delay
1026
+ assert_equal DirectLink.class_variable_get(:@@directlink).new(u, w, h, t), result, "for #{input}"
1027
+ end
1028
+ end
1029
+ end
1030
+ end
1031
+
1032
+ describe "giving up" do
1033
+ [
1034
+ [
1035
+ ["http://example.com", FastImage::UnknownImageType],
1036
+ [
1037
+ [:get, "http://example.com", {body: "<!doctype html><html><head><title>Example Domain</title><meta charset='utf-8'/></head><body><div><h1>Example Domain</h1><p>Lorem ipsum.</p></div></body></html>"}],
1038
+ ],
1039
+ ],
1040
+ [
1041
+ ["http://imgur.com/QWERTYU", FastImage::UnknownImageType, true],
1042
+ [
1043
+ [:get, "http://imgur.com/QWERTYU", {status: [301, "Moved Permanently"], headers: {location: "https://imgur.com/QWERTYU"}}],
1044
+ [:get, "https://imgur.com/QWERTYU", {body: "<html><body></body></html>"}],
1045
+ ],
1046
+ ],
1047
+ [
1048
+ ["http://imgur.com/QWERTYU", "https://i.imgur.com/QWERTYU.jpeg?fb"], # .at_css("meta[@property='og:image']")
1049
+ [
1050
+ [:head, "http://imgur.com/QWERTYU", {status: [301, "Moved Permanently"], headers: {Location: "https://imgur.com/QWERTYU"}}],
1051
+ [:get, "http://imgur.com/QWERTYU", {status: [301, "Moved Permanently"], headers: {Location: "https://imgur.com/QWERTYU"}}],
1052
+ [:head, "https://imgur.com/QWERTYU", {headers: {"Content-Type"=>"text/html"}}],
1053
+ [:get, "https://imgur.com/QWERTYU", {body: <<~HEREDOC
1054
+ <html>
1055
+ <head>
1056
+ <meta property="og:url" data-react-helmet="true" content="https://imgur.com/QWERTYU">
1057
+ <meta name="twitter:image" data-react-helmet="true" content="https://i.imgur.com/QWERTYUh.jpg">
1058
+ <meta property="og:image:width" data-react-helmet="true" content="1">
1059
+ <meta property="og:image:height" data-react-helmet="true" content="2">
1060
+ <meta property="og:image" data-react-helmet="true" content="https://i.imgur.com/QWERTYU.jpeg?fb">
1061
+ <meta property="og:type" data-react-helmet="true" content="article">
1062
+ </head>
1063
+ <body></body>
1064
+ </html>
1065
+ HEREDOC
1066
+ } ],
1067
+ [:head, "https://i.imgur.com/QWERTYU.jpeg?fb"],
1068
+ [:get, "https://i.imgur.com/QWERTYU.jpeg?fb", {body: "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xFF\xDB\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\b\x06\x06\x05\x06\t\b\n\n\t\b\t\t\n\f\x0F\f\n\v\x0E\v\t\t\r\x11\r\x0E\x0F\x10\x10\x11\x10\n\f\x12\x13\x12\x10\x13\x0F\x10\x10\x10\xFF\xC9\x00\v\b\x00\x01\x00\x01\x01\x01\x11\x00\xFF\xCC\x00\x06\x00\x10\x10\x05\xFF\xDA\x00\b\x01\x01\x00\x00?\x00\xD2\xCF \xFF\xD9"}],
1069
+ ],
1070
+ ],
1071
+ [
1072
+ ["https://www.deviantart.com/nakilon/art/Nakilon-123456789", FastImage::UnknownImageType, true],
1073
+ [
1074
+ [:get, "https://www.deviantart.com/nakilon/art/Nakilon-123456789", {body: <<~HEREDOC
1075
+ <html>
1076
+ <head>
1077
+ <meta data-rh="true" property="og:image" content="https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01"/>
1078
+ </head>
1079
+ <body class="theme-dark">
1080
+ <div id="root">
1081
+ <main role="main" class="_1TdPo">
1082
+ <div class="_3tEjH">
1083
+ <div><img alt="Nakilon" aria-hidden="true" class="_1izoQ" src="https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01"/></div>
1084
+ </div>
1085
+ </main>
1086
+ </div>
1087
+ </body>
1088
+ </html>
1089
+ HEREDOC
1090
+ } ],
1091
+ ],
1092
+ ],
1093
+ [
1094
+ ["https://www.deviantart.com/nakilon/art/Nakilon-123456789", "https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01"],
1095
+ [
1096
+ [:head, "https://www.deviantart.com/nakilon/art/Nakilon-123456789", {headers: {"Content-Type"=>"text/html; charset=utf-8"}}],
1097
+ [:get, "https://www.deviantart.com/nakilon/art/Nakilon-123456789", {body: <<~HEREDOC
1098
+ <html>
1099
+ <head>
1100
+ <meta data-rh="true" property="og:image" content="https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01"/>
1101
+ </head>
1102
+ <body class="theme-dark">
1103
+ <div id="root">
1104
+ <main role="main" class="_1TdPo">
1105
+ <div class="_3tEjH">
1106
+ <div><img alt="Nakilon" aria-hidden="true" class="_1izoQ" src="https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01"/></div>
1107
+ </div>
1108
+ </main>
1109
+ </div>
1110
+ </body>
1111
+ </html>
1112
+ HEREDOC
1113
+ } ],
1114
+ [:head, "https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01"],
1115
+ [:get, "https://images-wixmp-abc456.wixmp.com/f/abc456-abc456/abc456-abc456.jpg/v1/fill/w_1024,h_732,q_75,strp/nakilon.jpg?token=abCD01.abCD01.abCD01_abCD01_abCD01", {body: "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xFF\xDB\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\b\x06\x06\x05\x06\t\b\n\n\t\b\t\t\n\f\x0F\f\n\v\x0E\v\t\t\r\x11\r\x0E\x0F\x10\x10\x11\x10\n\f\x12\x13\x12\x10\x13\x0F\x10\x10\x10\xFF\xC9\x00\v\b\x00\x01\x00\x01\x01\x01\x11\x00\xFF\xCC\x00\x06\x00\x10\x10\x05\xFF\xDA\x00\b\x01\x01\x00\x00?\x00\xD2\xCF \xFF\xD9"}],
1116
+ ],
1117
+ ],
1118
+ # TODO: og:image without scheme
1119
+ [
1120
+ ["https://www.reddit.com/r/darksouls3/comments/qwe123/hand/", DirectLink::ErrorBadLink, true],
1121
+ [
1122
+ [:head, "https://www.reddit.com/qwe123.json", {status: [301, "Moved Permanently"], headers: {location: "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json"}}],
1123
+ [:head, "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json"],
1124
+ [:get, "https://www.reddit.com/qwe123.json", {status: [301, "Moved Permanently"], headers: {"Location"=>"https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json"}}],
1125
+ [:get, "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json", {body: <<~HEREDOC
1126
+ [
1127
+ {
1128
+ "data" : {
1129
+ "before" : null,
1130
+ "children" : [
1131
+ {
1132
+ "data" : {
1133
+ "selftext" : "[A](https://imgur.com/a/abcDEF1). I \\n [Here](https://imgur.com/a/abcDEF2)\\n\\nMobile: https://imgur.com/a/abcDEF3",
1134
+ "id" : "qwe123",
1135
+ "name" : "t3_qwe123",
1136
+ "is_self" : true,
1137
+ "media" : null,
1138
+ "permalink" : "/r/darksouls3/comments/qwe123/hand/",
1139
+ "over_18" : false,
1140
+ "url" : "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/"
1141
+ },
1142
+ "kind" : "t3"
1143
+ }
1144
+ ]
1145
+ }
1146
+ }, {}
1147
+ ]
1148
+ HEREDOC
1149
+ } ],
1150
+ ],
1151
+ ],
1152
+ [
1153
+ ["https://www.reddit.com/r/darksouls3/comments/qwe123/hand/", 6], # TODO: should see the 3rd album too
1154
+ [
1155
+ [:head, "https://www.reddit.com/qwe123.json", {status: [301, "Moved Permanently"], headers: {location: "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json"}}],
1156
+ [:head, "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json"],
1157
+ [:get, "https://www.reddit.com/qwe123.json", {status: [301, "Moved Permanently"], headers: {Location: "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json"}}],
1158
+ [:get, "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/.json", {body: <<~HEREDOC
1159
+ [
1160
+ {
1161
+ "data" : {
1162
+ "before" : null,
1163
+ "children" : [
1164
+ {
1165
+ "data" : {
1166
+ "selftext" : "[A](https://imgur.com/a/abcDEF1). I \\n [Here](https://imgur.com/a/abcDEF2)\\n\\nMobile: https://imgur.com/a/abcDEF3",
1167
+ "id" : "qwe123",
1168
+ "name" : "t3_qwe123",
1169
+ "is_self" : true,
1170
+ "media" : null,
1171
+ "permalink" : "/r/darksouls3/comments/qwe123/hand/",
1172
+ "over_18" : false,
1173
+ "url" : "https://www.reddit.com/r/darksouls3/comments/qwe123/hand/"
1174
+ },
1175
+ "kind" : "t3"
1176
+ }
1177
+ ]
1178
+ }
1179
+ }, {}
1180
+ ]
1181
+ HEREDOC
1182
+ } ],
1183
+ [:head, "https://imgur.com/a/abcDEF1"],
1184
+ [:head, "https://api.imgur.com/3/album/abcDEF1/0.json"],
1185
+ [:get, "https://api.imgur.com/3/album/abcDEF1/0.json", {body: <<~HEREDOC
1186
+ {
1187
+ "status" : 200,
1188
+ "success" : true,
1189
+ "data" : {
1190
+ "id" : "abcDEF1",
1191
+ "link" : "https://imgur.com/a/abcDEF1",
1192
+ "images" : [
1193
+ {
1194
+ "link" : "https://i.imgur.com/abcDEF4.png",
1195
+ "type" : "image/png",
1196
+ "height" : 1080,
1197
+ "width" : 1920
1198
+ },
1199
+ {
1200
+ "link" : "https://i.imgur.com/abcDEF5.png",
1201
+ "type" : "image/png",
1202
+ "height" : 1080,
1203
+ "width" : 1920
1204
+ }
1205
+ ]
1206
+ }
1207
+ }
1208
+ HEREDOC
1209
+ } ],
1210
+ [:head, "https://imgur.com/a/abcDEF2"],
1211
+ [:head, "https://api.imgur.com/3/album/abcDEF2/0.json"],
1212
+ [:get, "https://api.imgur.com/3/album/abcDEF2/0.json", {body: <<~HEREDOC
1213
+ {
1214
+ "status" : 200,
1215
+ "success" : true,
1216
+ "data" : {
1217
+ "id" : "abcDEF2",
1218
+ "link" : "https://imgur.com/a/abcDEF2",
1219
+ "images" : [
1220
+ {
1221
+ "link" : "https://i.imgur.com/abcDEF6.png",
1222
+ "type" : "image/png",
1223
+ "height" : 1080,
1224
+ "width" : 1920
1225
+ },
1226
+ {
1227
+ "link" : "https://i.imgur.com/abcDEF7.jpg",
1228
+ "type" : "image/jpeg",
1229
+ "height" : 1080,
1230
+ "width" : 1920
1231
+ },
1232
+ {
1233
+ "link" : "https://i.imgur.com/abcDEF8.jpg",
1234
+ "type" : "image/jpeg",
1235
+ "height" : 1080,
1236
+ "width" : 1920
1237
+ },
1238
+ {
1239
+ "link" : "https://i.imgur.com/abcDEF9.jpg",
1240
+ "type" : "image/jpeg",
1241
+ "height" : 1080,
1242
+ "width" : 1920
1243
+ }
1244
+ ]
1245
+ }
1246
+ }
1247
+ HEREDOC
1248
+ } ],
1249
+ ],
1250
+ ],
1251
+ ].each do |(input, expectation, giveup), stubs|
1252
+ it "#{URI(input).host} giveup=#{!!giveup}" do
1253
+ stub_request(:head, input)
1254
+ stubs.each do |mtd, link, response|
1255
+ if response
1256
+ stub_request(mtd, link).to_return **response
1257
+ else
1258
+ stub_request(mtd, link)
1259
+ end
1260
+ end
1261
+ ti = ENV.delete "IMGUR_CLIENT_ID" if %w{ imgur com } == URI(input).host.split(?.).last(2)
1262
+ tr = ENV.delete "REDDIT_SECRETS" if %w{ reddit com } == URI(input).host.split(?.).last(2)
1263
+ begin
1264
+ case expectation
1265
+ when Class
1266
+ e = assert_raises expectation, "for #{input} (giveup = #{giveup})" do
1267
+ DirectLink input, 5, giveup: giveup
1268
+ end
1269
+ assert_equal expectation.to_s, e.class.to_s, "for #{input} (giveup = #{giveup})"
1270
+ when String
1271
+ result = DirectLink input, 5, giveup: giveup
1272
+ assert_equal expectation, result.url, "for #{input} (giveup = #{giveup})"
1273
+ else
1274
+ result = DirectLink input, 5, giveup: giveup
1275
+ result = [result] unless result.is_a? Array # we can't do `Array(<Struct>)` because it splats by elements
1276
+ assert_equal expectation, result.size, ->{
1277
+ "for #{input} (giveup = #{giveup}): #{result.map &:url}"
1278
+ }
1279
+ end
1280
+ ensure
1281
+ ENV["IMGUR_CLIENT_ID"] = ti if ti
1282
+ ENV["REDDIT_SECRETS"] = tr if tr
1283
+ end
1284
+ end
1285
+ end
1286
+ end
1287
+
1288
+ end
1289
+
1290
+ describe "./bin" do
1291
+ require "open3"
1292
+
1293
+ describe "shows usage help if misused" do
1294
+ [
1295
+ [/\Ausage: directlink \[--debug\] \[--json\] \[--github\] \[--ignore-meta\] <link1> <link2> <link3> \.\.\.\ndirectlink version \d\.\d\.\d\d?\.\d\d? \(https:\/\/github\.com\/nakilon\/directlink\)\n\z/, nil],
1296
+ [/\Ausage: directlink \[--debug\] \[--json\] \[--github\] \[--ignore-meta\] <link1> <link2> <link3> \.\.\.\ndirectlink version \d\.\d\.\d\d?\.\d\d? \(https:\/\/github\.com\/nakilon\/directlink\)\n\z/, "-h"],
1297
+ [/\Ausage: directlink \[--debug\] \[--json\] \[--github\] \[--ignore-meta\] <link1> <link2> <link3> \.\.\.\ndirectlink version \d\.\d\.\d\d?\.\d\d? \(https:\/\/github\.com\/nakilon\/directlink\)\n\z/, "--help"],
1298
+ [/\Ausage: directlink \[--debug\] \[--json\] \[--github\] \[--ignore-meta\] <link1> <link2> <link3> \.\.\.\ndirectlink version \d\.\d\.\d\d?\.\d\d? \(https:\/\/github\.com\/nakilon\/directlink\)\n\z/, "-v"],
1299
+ [/\Ausage: directlink \[--debug\] \[--json\] \[--github\] \[--ignore-meta\] <link1> <link2> <link3> \.\.\.\ndirectlink version \d\.\d\.\d\d?\.\d\d? \(https:\/\/github\.com\/nakilon\/directlink\)\n\z/, "--version"],
1300
+ ["DirectLink::ErrorBadLink: \"--\"\n", "--"],
1301
+ ["DirectLink::ErrorBadLink: \"-\"\n", "-"],
1302
+ ["DirectLink::ErrorBadLink: \"-\"\n", "- -"],
1303
+ ["DirectLink::ErrorBadLink: \"asd\"\n", "asd"],
1304
+ ].each_with_index do |(expectation, param), i|
1305
+ it "##{i + 1}" do
1306
+ string, status = Open3.capture2e "RUBYOPT='-rbundler/setup' ./bin/directlink #{param}"
1307
+ if expectation.is_a? String
1308
+ assert_equal expectation, string
1309
+ else
1310
+ assert_match expectation, string
1311
+ end
1312
+ assert_equal 1, status.exitstatus
1313
+ end
1314
+ end
1315
+ end
1316
+
1317
+ # that hack around `export` is for crossplatform Travis test, since Ubuntu uses `sh` that can't `source`
1318
+ # TODO: but maybe I could make Open3 use the shell I need, since `source` works fine for `.travis.yaml`'s `:script`
1319
+
1320
+ popen = lambda do |input, stubs, export = nil, param = nil|
1321
+ file = Tempfile.new %w{ directlink_webmock .rb }
1322
+ file.write <<~HEREDOC
1323
+ require "webmock"
1324
+ include WebMock::API
1325
+ WebMock.enable!
1326
+ require "#{__dir__}/webmock_patch"
1327
+ #{stubs.inspect}.each do |mtd, input, response|
1328
+ if response
1329
+ stub_request(mtd, input).to_return **response
1330
+ else
1331
+ stub_request(mtd, input)
1332
+ end
1333
+ end
1334
+ HEREDOC
1335
+ file.flush
1336
+ begin
1337
+ Open3.capture2e "#{"export #{File.read("api_tokens.sh").scan(/(?<=^export )\S+=\S+/).join(" ")} && " if export}RUBYOPT='-r bundler/setup #{$-I.map{ |i| "-I #{i}" }.join " "} -r #{file.path}' ./bin/directlink#{" --#{param}" if param} #{input}"
1338
+ ensure
1339
+ file.close
1340
+ file.unlink
1341
+ end
1342
+ end
1343
+
1344
+ [
1345
+ [1, "http://example.com/", /\AFastImage::UnknownImageType\n\z/, [
1346
+ [:head, "http://example.com/"],
1347
+ [:get, "http://example.com/", {body: "<html></html>"}],
1348
+ ] ],
1349
+ [1, "http://example.com/404", /\ANetHTTPUtils::Error: HTTP error #404 \n\z/, [
1350
+ [:head, "http://example.com/404", {status: [404, "Not Found"]}],
1351
+ ] ],
1352
+
1353
+ # TODO: a test when the giveup=false fails and reraises the DirectLink::ErrorMissingEnvVar
1354
+ # maybe put it to ./lib tests
1355
+
1356
+ # by design it should be impossible to write a test for DirectLink::ErrorAssert
1357
+ [1, "https://flic.kr/p/DirectLinkErrorNotFound", /\ANetHTTPUtils::Error: HTTP error #404 \n\z/, [
1358
+ [:head, "https://flic.kr/p/DirectLinkErrorNotFound", {status: [302, "Found"], headers: {"Location"=>"https://www.flickr.com/photo.gne?short=DirectLinkErrorNotFound"}}],
1359
+ [:head, "https://www.flickr.com/photo.gne?short=DirectLinkErrorNotFound", {status: [302, "Found"], headers: {"Location"=>"/photo.gne?rb=1&short=DirectLinkErrorNotFound"}}],
1360
+ [:head, "https://www.flickr.com/photo.gne?rb=1&short=DirectLinkErrorNotFound", {status: [404, "Not Found"]}],
1361
+ ] ],
1362
+
1363
+ [1, "https://imgur.com/a/badlinkpattern", /\ANetHTTPUtils::Error: HTTP error #404 \n\z/, [
1364
+ [:head, "https://imgur.com/a/badlinkpattern", {status: [404, "Not Found"]}],
1365
+ ] ],
1366
+ # TODO: a test that it appends the `exception.cause`
1367
+ ].each do |expected_exit_code, input, expected_output, stubs|
1368
+ it "fails" do
1369
+ string, status = popen.call(input, stubs, true)
1370
+ assert_equal expected_exit_code, status.exitstatus, "for #{input}"
1371
+ assert string[expected_output], "for #{input}: string=#{string.inspect}"
1372
+ end
1373
+ end
1374
+
1375
+ valid_imgur_image_url1 = "https://goo.gl/ySqUb5"
1376
+ valid_imgur_image_url2 = "https://imgur.com/a/oacI3gl"
1377
+ stubs = [
1378
+ [:head, "https://goo.gl/ySqUb5", {status: [302, "Found"], headers: {"Location"=>"https://i.imgur.com/QpOBvRY.png"}}],
1379
+ [:head, "https://i.imgur.com/QpOBvRY.png"],
1380
+ [:head, "https://api.imgur.com/3/image/QpOBvRY/0.json"],
1381
+ [:get, "https://api.imgur.com/3/image/QpOBvRY/0.json", {body: <<~HEREDOC
1382
+ {
1383
+ "status" : 200,
1384
+ "success" : true,
1385
+ "data" : {
1386
+ "height" : 460,
1387
+ "link" : "https://i.imgur.com/QpOBvRY.png",
1388
+ "type" : "image/png",
1389
+ "width" : 460
1390
+ }
1391
+ }
1392
+ HEREDOC
1393
+ } ],
1394
+ [:head, "https://imgur.com/a/oacI3gl"],
1395
+ [:head, "https://api.imgur.com/3/album/oacI3gl/0.json"],
1396
+ [:get, "https://api.imgur.com/3/album/oacI3gl/0.json", {body: <<~HEREDOC
1397
+ {
1398
+ "status" : 200,
1399
+ "success" : true,
1400
+ "data" : {
1401
+ "id" : "oacI3gl",
1402
+ "link" : "https://imgur.com/a/oacI3gl",
1403
+ "images" : [
1404
+ {
1405
+ "height" : 100,
1406
+ "link" : "https://i.imgur.com/9j4KdkJ.png",
1407
+ "type" : "image/png",
1408
+ "width" : 100
1409
+ },
1410
+ {
1411
+ "height" : 460,
1412
+ "link" : "https://i.imgur.com/QpOBvRY.png",
1413
+ "type" : "image/png",
1414
+ "width" : 460
1415
+ }
1416
+ ]
1417
+ }
1418
+ }
1419
+ HEREDOC
1420
+ } ]
1421
+ ]
1422
+ [
1423
+ [stubs, <<~HEREDOC],
1424
+ <= #{valid_imgur_image_url1}
1425
+ => https://i.imgur.com/QpOBvRY.png
1426
+ image/png 460x460
1427
+ <= #{valid_imgur_image_url2}
1428
+ => https://i.imgur.com/QpOBvRY.png
1429
+ image/png 460x460
1430
+ => https://i.imgur.com/9j4KdkJ.png
1431
+ image/png 100x100
1432
+ HEREDOC
1433
+ [stubs, <<~HEREDOC, "json"]
1434
+ [
1435
+ {
1436
+ "url": "https://i.imgur.com/QpOBvRY.png",
1437
+ "width": 460,
1438
+ "height": 460,
1439
+ "type": "image/png"
1440
+ },
1441
+ [
1442
+ {
1443
+ "url": "https://i.imgur.com/QpOBvRY.png",
1444
+ "width": 460,
1445
+ "height": 460,
1446
+ "type": "image/png"
1447
+ },
1448
+ {
1449
+ "url": "https://i.imgur.com/9j4KdkJ.png",
1450
+ "width": 100,
1451
+ "height": 100,
1452
+ "type": "image/png"
1453
+ }
1454
+ ]
1455
+ ]
1456
+ HEREDOC
1457
+ ].each do |stubs, expected_output, param|
1458
+ it "#{param || "default"} output format" do
1459
+ string, status = popen.call("#{valid_imgur_image_url1} #{valid_imgur_image_url2}", stubs, true, param)
1460
+ assert_equal [0, expected_output], [status.exitstatus, string]
1461
+ end
1462
+ end
1463
+
1464
+ it "reddit_bot gem logger does not flood STDOUT" do
1465
+ string, status = popen.call("http://redd.it/123abc", [
1466
+ [:head, "http://redd.it/123abc", {status: [301, "Moved Permanently"], headers: {"Location"=>"https://redd.it/123abc"}}],
1467
+ [:head, "https://redd.it/123abc", {status: [302, "Temporarily Moved"], headers: {"Location"=>"https://www.reddit.com/comments/123abc"}}],
1468
+ [:head, "https://www.reddit.com/comments/123abc"],
1469
+ [:post, "https://www.reddit.com/api/v1/access_token", {body: {"access_token": "123456789268-NxVk35tst8DttE5uCSD8pPY00XeAXQ"}.to_json}],
1470
+ [:get, "https://oauth.reddit.com/by_id/t3_123abc?api_type=json", {body: <<~HEREDOC
1471
+ {
1472
+ "data" : {
1473
+ "children" : [
1474
+ {
1475
+ "data" : {
1476
+ "id" : "123abc",
1477
+ "is_self" : true,
1478
+ "media" : null,
1479
+ "name" : "t3_123abc",
1480
+ "permalink" : "/r/test___________/comments/123abc/for_the_httpsgithubcomnakilondirectlink_unit_tests/",
1481
+ "selftext" : "https://i.imgur.com/ABCabcd.jpg [test](https://goo.gl/ySqUb5)",
1482
+ "url" : "https://www.reddit.com/r/test___________/comments/123abc/for_the_httpsgithubcomnakilondirectlink_unit_tests/"
1483
+ }
1484
+ }
1485
+ ]
1486
+ }
1487
+ }
1488
+ HEREDOC
1489
+ } ],
1490
+ [:head, "https://goo.gl/ySqUb5", {status: [302, "Found"], headers: {"Location"=>"https://i.imgur.com/QpOBvRY.png"}}],
1491
+ [:head, "https://i.imgur.com/QpOBvRY.png"],
1492
+ [:head, "https://api.imgur.com/3/image/QpOBvRY/0.json"],
1493
+ [:get, "https://api.imgur.com/3/image/QpOBvRY/0.json", {body: <<~HEREDOC
1494
+ {
1495
+ "status" : 200,
1496
+ "success" : true,
1497
+ "data" : {
1498
+ "height" : 460,
1499
+ "width" : 460,
1500
+ "link" : "https://i.imgur.com/QpOBvRY.png",
1501
+ "type" : "image/png"
1502
+ }
1503
+ }
1504
+ HEREDOC
1505
+ } ],
1506
+ ] )
1507
+ assert_equal "<= http://redd.it/123abc\n=> https://i.imgur.com/QpOBvRY.png\n image/png 460x460\n", string
1508
+ end
1509
+
1510
+ # TODO: test about --json
1511
+ it "does not give up" do
1512
+ string, status = popen.call("https://www.kp.ru/daily/123/", [
1513
+ [:head, "https://www.kp.ru/daily/123/", {headers: {"Content-Type"=>"text/html"}}],
1514
+ [:get, "https://www.kp.ru/daily/123/", {body: <<~HEREDOC
1515
+ <!DOCTYPE html>
1516
+ <html xmlns="http://www.w3.org/1999/xhtml">
1517
+ <head>
1518
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
1519
+ <link rel="icon" type="image/png" href="/favicon-16.png" sizes="16x16" />
1520
+ <link rel="icon" type="image/png" href="/favicon-32.png" sizes="32x32" />
1521
+ <link rel="icon" type="image/png" href="/favicon-96.png" sizes="96x96" />
1522
+ <link rel="icon" type="image/png" href="/favicon-128.png" sizes="128x128" />
1523
+ <meta data-react-helmet="true" property="og:image" content="https://s11.stc.all.kpcdn.net/share/20.jpg" />
1524
+ <meta data-react-helmet="true" name="twitter:image:src" content="https://s11.stc.all.kpcdn.net/share/20.jpg" />
1525
+ <link data-react-helmet="true" rel="image_src" href="https://s11.stc.all.kpcdn.net/share/20.jpg" />
1526
+ </head>
1527
+ <body>
1528
+ <div id="app">
1529
+ <div>
1530
+ <a href="/daily/author/1234">
1531
+ <img alt="Александра" src="https://s14.stc.all.kpcdn.net/share/i/3/1234567/avatar.jpg" data-show-on-desktop="false" data-show-on-mobile="false" />
1532
+ Ы
1533
+ </a>
1534
+ </div>
1535
+ </div>
1536
+ </body>
1537
+ </html>
1538
+ HEREDOC
1539
+ } ],
1540
+ [:head, "https://s11.stc.all.kpcdn.net/share/20.jpg"],
1541
+ [:get, "https://s11.stc.all.kpcdn.net/share/20.jpg", {body: "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xFF\xDB\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\b\x06\x06\x05\x06\t\b\n\n\t\b\t\t\n\f\x0F\f\n\v\x0E\v\t\t\r\x11\r\x0E\x0F\x10\x10\x11\x10\n\f\x12\x13\x12\x10\x13\x0F\x10\x10\x10\xFF\xC9\x00\v\b\x00\x01\x00\x01\x01\x01\x11\x00\xFF\xCC\x00\x06\x00\x10\x10\x05\xFF\xDA\x00\b\x01\x01\x00\x00?\x00\xD2\xCF \xFF\xD9"}],
1542
+ ], false, "json")
1543
+ assert_equal [0, "https://s11.stc.all.kpcdn.net/share/20.jpg"], [status.exitstatus, JSON.load(string).fetch("url")]
1544
+ end
1545
+
1546
+ end
1547
+
1548
+ end