directlink 0.0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/directlink.rb ADDED
@@ -0,0 +1,219 @@
1
+ module DirectLink
2
+
3
+ class << self
4
+ attr_accessor :silent
5
+ end
6
+ self.silent = false
7
+ class << self
8
+ attr_accessor :logger
9
+ end
10
+ self.logger = Object.new
11
+ self.logger.define_singleton_method :error do |str|
12
+ puts str unless Module.nesting.first.silent
13
+ end
14
+
15
+ class ErrorMissingEnvVar < RuntimeError ; end
16
+ class ErrorAssert < RuntimeError
17
+ def initialize msg
18
+ super "#{msg} -- consider reporting this issue to GitHub"
19
+ end
20
+ end
21
+ @@LoggingError = Class.new RuntimeError do
22
+ def initialize msg
23
+ Module.nesting.first.logger.error msg
24
+ super msg
25
+ end
26
+ end
27
+ class ErrorNotFound < @@LoggingError ; end
28
+ class ErrorBadLink < @@LoggingError
29
+ def initialize link, sure = false
30
+ super "#{link.inspect}#{" -- if you think this link is valid, please report the issue" unless sure}"
31
+ end
32
+ end
33
+
34
+
35
+ def self.google src, width = 0
36
+ case src
37
+ when /\A(https:\/\/lh3\.googleusercontent\.com\/-[a-zA-Z0-9_-]{11}\/W[n-r][a-zA-Z0-9_-]{8}I\/AAAAAAAA[bA-V][a-zA-Z0-9]{2}\/[a-zA-Z0-9_-]{32}[gwAQ]CJoC\/)w[1-4]\d\d-h(318|353|727)-n(\/[^\/]+)\z/,
38
+ /\A(https:\/\/lh3\.googleusercontent\.com\/-[a-zA-Z0-9_-]{11}\/W[a-zA-Z0-9_-]{9}I\/AAAAAAA[a-zA-Z0-9_-]{4}\/[a-zA-Z0-9_-]{33}(?:CJoC|CL0B(?:GAs)?)\/)w530(?:-d)?-h[2-9]\d\d-n(\/[^\/]+)\z/
39
+ "#{$1}s#{width}#{$2}"
40
+ when /\A(\/\/lh3\.googleusercontent\.com\/proxy\/[a-zA-Z0-9_-]{66,523}=)(?:w(?:464|504|530)-h[2-7]\d\d-[np]|s530-p)\z/
41
+ "https:#{$1}s#{width}"
42
+ when /\A(\/\/lh3\.googleusercontent\.com\/-[a-zA-Z0-9-]{11}\/W[a-zA-Z0-9_-]{9}I\/AAAAAAA[AC][a-zA-Z0-9]{3}\/[a-zA-Z0-9_-]{32}[gw]CJoC\/)w530-h3\d\d-p(\/[^\/]+)\z/,
43
+ /\A(\/\/[14]\.bp\.blogspot\.com\/-[a-zA-Z0-9_-]{11}\/W[np][a-zA-Z0-9_-]{8}I\/AAAAAAAA[DE][a-zA-Z0-9_-]{2}\/[a-zA-Z0-9_-]{33}C(?:Lc|Kg)BGAs\/)w530-h278-p(\/[^\/]+)\z/
44
+ "https:#{$1}s#{width}#{$2}"
45
+ else
46
+ raise ErrorBadLink.new src
47
+ end
48
+ end
49
+
50
+ require "json"
51
+ require "nethttputils"
52
+
53
+ def self.imgur link
54
+ raise ErrorMissingEnvVar.new "define IMGUR_CLIENT_ID env var" unless ENV["IMGUR_CLIENT_ID"]
55
+
56
+ case link
57
+ when /\Ahttps?:\/\/(?:(?:i|m|www)\.)?imgur\.com\/(a|gallery)\/([a-zA-Z0-9]{5}(?:[a-zA-Z0-9]{2})?)\z/,
58
+ /\Ahttps?:\/\/imgur\.com\/(gallery)\/([a-zA-Z0-9]{5}(?:[a-zA-Z0-9]{2})?)\/new\z/
59
+ timeout = 1
60
+ json = begin
61
+ NetHTTPUtils.request_data "https://api.imgur.com/3/#{
62
+ $1 == "gallery" ? "gallery" : "album"
63
+ }/#{$2}/0.json", header: { Authorization: "Client-ID #{ENV["IMGUR_CLIENT_ID"]}" }
64
+ rescue NetHTTPUtils::Error => e
65
+ case e.code
66
+ when 500
67
+ logger.error "retrying in #{timeout} seconds because of Imgur HTTP ERROR 500"
68
+ sleep timeout
69
+ timeout *= 2
70
+ retry
71
+ when 404 ; raise ErrorNotFound.new link.inspect
72
+ else ; raise ErrorAssert.new "unexpected http error for #{link}"
73
+ end
74
+ end
75
+ data = JSON.load(json)["data"]
76
+ if data["error"]
77
+ raise ErrorAssert.new "unexpected error #{data.inspect} for #{link}"
78
+ elsif data["images"]
79
+ data["images"]
80
+ elsif data["type"] && data["type"].start_with?("image/")
81
+ [ data ]
82
+ # elsif data["comment"]
83
+ # fi["https://imgur.com/" + data["image_id"]]
84
+ else
85
+ # one day single-video item should hit this but somehow it didn't yet
86
+ raise ErrorAssert.new "unknown data format #{data.inspect} for #{link}"
87
+ end
88
+ when /\Ahttps?:\/\/(?:(?:i|m|www)\.)?imgur\.com\/([a-zA-Z0-9]{7})(?:\.(?:gifv|jpg))?\z/,
89
+ /\Ahttps?:\/\/(?:(?:i|m|www)\.)?imgur\.com\/([a-zA-Z0-9]{5})\.mp4\z/,
90
+ /\Ahttps?:\/\/imgur\.com\/([a-zA-Z0-9]{5}(?:[a-zA-Z0-9]{2})?)\z/,
91
+ /\Ahttps?:\/\/imgur\.com\/([a-zA-Z0-9]{7})(?:\?\S+)?\z/,
92
+ /\Ahttps?:\/\/imgur\.com\/r\/[0-9_a-z]+\/([a-zA-Z0-9]{7})\z/
93
+ json = begin
94
+ NetHTTPUtils.request_data "https://api.imgur.com/3/image/#{$1}/0.json", header: { Authorization: "Client-ID #{ENV["IMGUR_CLIENT_ID"]}" }
95
+ rescue NetHTTPUtils::Error => e
96
+ raise ErrorNotFound.new link.inspect if e.code == 404
97
+ raise ErrorAssert.new "unexpected http error for #{link}"
98
+ end
99
+ [ JSON.load(json)["data"] ]
100
+ else
101
+ raise ErrorBadLink.new link
102
+ end.map do |image|
103
+ case image["type"]
104
+ when "image/jpeg", "image/png", "image/gif", "video/mp4"
105
+ image.values_at "link", "width", "height", "type"
106
+ else
107
+ raise ErrorAssert.new "unknown type of #{link}: #{image}"
108
+ end
109
+ end
110
+ end
111
+
112
+ def self._500px link
113
+ raise ErrorBadLink.new link unless %r{\Ahttps://500px\.com/photo/(?<id>[^/]+)/[^/]+\z} =~ link
114
+ raise ErrorMissingEnvVar.new "define _500PX_CONSUMER_KEY env var !!! WARNING: the 500px.com deprecates their API in summer of 2018 !!!" unless ENV["_500PX_CONSUMER_KEY"]
115
+ JSON.load( NetHTTPUtils.request_data "https://api.500px.com/v1/photos/#{id}", form: {
116
+ image_size: 2048,
117
+ consumer_key: ENV["_500PX_CONSUMER_KEY"],
118
+ } )["photo"].values_at "width", "height", "image_url", "image_format" # could also get "camera" and "taken_at"
119
+ end
120
+
121
+ def self.flickr link
122
+ raise ErrorBadLink.new link unless %r{\Ahttps://www\.flickr\.com/photos/[^/]+/(?<id>[^/]+)} =~ link ||
123
+ %r{\Ahttps://flic\.kr/p/(?<id>[^/]+)\z} =~ link
124
+ raise ErrorMissingEnvVar.new "define FLICKR_API_KEY env var" unless ENV["FLICKR_API_KEY"]
125
+
126
+ flickr = lambda do |id, method|
127
+ JSON.load NetHTTPUtils.request_data "https://api.flickr.com/services/rest/", form: {
128
+ api_key: ENV["FLICKR_API_KEY"],
129
+ format: "json",
130
+ nojsoncallback: 1,
131
+ photo_id: id,
132
+ method: "flickr.photos.#{method}",
133
+ }
134
+ end
135
+ json = flickr.call id, "getSizes"
136
+ raise ErrorNotFound.new link.inspect if json == {"stat"=>"fail", "code"=>1, "message"=>"Photo not found"}
137
+ raise ErrorAssert.new "unhandled API response stat for #{link}: #{json}" unless json["stat"] == "ok"
138
+ json["sizes"]["size"].map do |_|
139
+ w, h, u = _.values_at("width", "height", "source")
140
+ [w.to_i, h.to_i, u]
141
+ end.max_by{ |w, h, u| w * h }
142
+ end
143
+
144
+ def self.wiki link
145
+ raise ErrorBadLink.new link unless %r{\Ahttps?://([a-z]{2}\.wikipedia|commons.wikimedia)\.org/wiki(/[^/]+)*/(?<id>File:.+)} =~ link
146
+ t = JSON.load json = NetHTTPUtils.request_data( "https://commons.wikimedia.org/w/api.php", form: {
147
+ format: "json",
148
+ action: "query",
149
+ prop: "imageinfo",
150
+ iiprop: "url",
151
+ titles: id,
152
+ } )
153
+ imageinfo = t["query"]["pages"].values.first["imageinfo"]
154
+ raise ErrorAssert.new "unexpected format of API response about #{link}: #{json}" unless imageinfo
155
+ imageinfo.first["url"]
156
+ end
157
+
158
+
159
+ class_variable_set :@@directlink, Struct.new(:url, :width, :height, :type)
160
+ end
161
+
162
+
163
+ require "fastimage"
164
+
165
+ def DirectLink link
166
+ begin
167
+ URI link
168
+ rescue URI::InvalidURIError
169
+ link = URI.escape link
170
+ end
171
+ raise DirectLink::ErrorBadLink.new link, true unless URI(link).host
172
+
173
+ struct = Module.const_get(__callee__).class_variable_get :@@directlink
174
+
175
+
176
+ if %w{ lh3 googleusercontent com } == URI(link).host.split(?.).last(3) ||
177
+ %w{ bp blogspot com } == URI(link).host.split(?.).last(3)
178
+ u = DirectLink.google link
179
+ f = FastImage.new(u, raise_on_failure: true, http_header: {"User-Agent" => "Mozilla"})
180
+ w, h = f.size
181
+ return struct.new u, w, h, f.type
182
+ end
183
+
184
+ if %w{ imgur com } == URI(link).host.split(?.).last(2) ||
185
+ %w{ i imgur com } == URI(link).host.split(?.).last(3) ||
186
+ %w{ m imgur com } == URI(link).host.split(?.).last(3) ||
187
+ %w{ www imgur com } == URI(link).host.split(?.).last(3)
188
+ imgur = DirectLink.imgur(link).sort_by{ |u, w, h, t| - w * h }.map do |u, w, h, t|
189
+ struct.new u, w, h, t
190
+ end
191
+ # `DirectLink.imgur` return value is always an Array
192
+ return imgur.size == 1 ? imgur.first : imgur
193
+ end
194
+
195
+ if %w{ 500px com } == URI(link).host.split(?.).last(2)
196
+ w, h, u, t = DirectLink._500px(link)
197
+ return struct.new u, w, h, t
198
+ end
199
+
200
+ if %w{ www flickr com } == URI(link).host.split(?.).last(3) ||
201
+ %w{ flic kr } == URI(link).host.split(?.).last(2)
202
+ w, h, u = DirectLink.flickr(link)
203
+ f = FastImage.new(u, raise_on_failure: true, http_header: {"User-Agent" => "Mozilla"})
204
+ return struct.new u, w, h, f.type
205
+ end
206
+
207
+ if %w{ wikipedia org } == URI(link).host.split(?.).last(2) ||
208
+ %w{ commons wikimedia org } == URI(link).host.split(?.).last(3)
209
+ u = DirectLink.wiki link
210
+ f = FastImage.new(u, raise_on_failure: true, http_header: {"User-Agent" => "Mozilla"})
211
+ w, h = f.size
212
+ return struct.new u, w, h, f.type
213
+ end
214
+
215
+ f = FastImage.new(link, raise_on_failure: true, http_header: {"User-Agent" => "Mozilla"})
216
+ w, h = f.size
217
+ struct.new link, w, h, f.type
218
+
219
+ end
data/test.rb ADDED
@@ -0,0 +1,374 @@
1
+ STDOUT.sync = true
2
+
3
+ require "minitest/autorun"
4
+ require "minitest/mock"
5
+
6
+ require_relative "lib/directlink"
7
+ DirectLink.silent = true
8
+ describe DirectLink do
9
+
10
+ describe "methods" do
11
+
12
+ describe "google" do
13
+ "
14
+ 1 //1.bp.blogspot.com/-rYk_u-qROQc/WngolJ8M0LI/AAAAAAAAD-w/woivnaIVzasBPG5C2T1t-VrWKRd_U6lMgCLcBGAs/w530-h278-p/i-469.jpg
15
+ 2 //4.bp.blogspot.com/-1RlHbRShONs/Wngp9WfxbfI/AAAAAAAAD-8/vnBBXpSZdvUMz4VjJPBpprLBrJ5QpBaqACLcBGAs/w530-h278-p/i-468.jpg
16
+ 3 //4.bp.blogspot.com/-5kP8ndL0kuM/Wpt82UCqvmI/AAAAAAAAEjI/ZbbZWs0-kgwRXEJ9JEGioR0bm6U8MOkvQCKgBGAs/w530-h278-p/IMG_20171223_093922.jpg
17
+ 4 //4.bp.blogspot.com/-bS1SZxFkvek/Wnjz3D5Il4I/AAAAAAAAD_I/bKYvRLGB7r4NGJ0aR1F-ugsbVm4HUjf5ACLcBGAs/w530-h278-p/i-466.jpg
18
+ 5 //lh3.googleusercontent.com/-1CF2jge1MYI/Wmr_kfCQQUI/AAAAAAAClKs/N1F2tz4v0q0i5N7rgso6G8SrhJNzsHEagCJoC/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
19
+ 6 //lh3.googleusercontent.com/-h1siEcRFqJ8/Wiv1iDQhNVI/AAAAAAAAQV4/9gGepSmYkDMLyVTG_1EBCMmj-UhmclXWwCJoC/w530-h353-p/001
20
+ 7 //lh3.googleusercontent.com/-qnZygRb-Tn8/Wlyj4UuKbaI/AAAAAAAATqE/iKWSto-XBZwsh843xJ3cSd0JPt1LedU9gCJoC/w530-h330-p/001
21
+ 8 //lh3.googleusercontent.com/-rXO9PzbjNmk/WlNfjJkJBmI/AAAAAAAATNY/4dHLtYtWBzUKWufNXWkK82etw3RKA6jVgCJoC/w530-h361-p/001
22
+
23
+ 13 //lh3.googleusercontent.com/proxy/2fsWfEOeeCMNEP8MGvHiJPkBlg47Ifaf8DwpgDPJSPSYsh9iomTrfOA9OuwB60laZimtNr4-1GKA5J2csuVB6-1DLNO30K-fngB8p56ivICWRFAUuOwzizaztBx5gQ=w530-h707-p
24
+ 14 //lh3.googleusercontent.com/proxy/2qy3gOwhBT77Ie_qa9VgGz6kgN14tnOh1AMmV9FB65oiXOSAIll1_qmOZdupL3QCcJQL4701YskEgeDUnxJTnFGq4mFEzNFvjpNC-QMX9Jg7mdar3e89u_siA4AD-j1SJ6mrkl2MwSJSu1s2guYn0NR5ND1985lK34_iGiwzjoGHG2a65IbxKDSEzgSGbPQ_CQiQ6rBIvfcgvAxtS6E=s530-p
25
+ 28 //lh3.googleusercontent.com/proxy/7N0QCidzIFysO6OhmrG8twRJ74-sboNenI3Nhy3mIWP8C0SSa8J6xFQU3iyBTw4k3QeleiAgXOME3abYutPvBrHGx-0TBUcI-_LsgFa59Zaw6PAGdLndkxp5ReRJnsg=w530-h298-p
26
+ 31 //lh3.googleusercontent.com/proxy/7pN3aTE159nEXXQb_KajU1vcnDDzJVpGOv9vlWWPd3CbVuCKu-v4ndr423Mmhx-Zn5vqkVf6-TfZgPA5NyHECsr4w7kaw1jTmQDJbWO-9jU-Cs7ywrzcIRhyYTTZT-hPGiazwI1oEdbooIk=w464-h640-p
27
+ 48 //lh3.googleusercontent.com/proxy/DZtTi5KL7PqiBwJc8weNGLk_Wi2UTaQH0AC_h2kuURiu0AbwyI2ywOk2XgdAjL7ceg=w530-h354-n
28
+ 74 //lh3.googleusercontent.com/proxy/P4FQdTYwWN8cU_PWmFja1cm3oYu57L_flea7LmKvFjx0Ibdxl3glxcp-KFWsdRxMFTQ3gQkWsWzlrkyCm9QrSjCUcGIgnbsYBCXewnhEwSn0ZgT4XDg485GdXMoGWpNETrLYa8IGvG20_iw=w504-h597-p
29
+ 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-4H12RdH7MyiHBQt04uRnb58GA4vl5VBQ=w530-h398-p
30
+
31
+ 245 https://lh3.googleusercontent.com/-2JzV0ErXvv8/Wsdr-m5Q_aI/AAAAAAAA_-I/Lw_e6qEMPUck4yW9yWhkC-nBNd5em8c3QCJoC/w530-h795-n/888052_800n.jpg
32
+ 346 https://lh3.googleusercontent.com/-8YmBt-SUaJo/War0Un_7YTI/AAAAAAAB9xs/kgXp9QrHZX8mpDfyLwuS9cvUSsXIoH6NgCL0BGAs/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
33
+ 1317 https://lh3.googleusercontent.com/-tGAyJv4S6gM/WSLy4A7AFaI/AAAAAAABxk4/eq0Aprp2koM6GEti9xEIn-0PLh87bRRdQCL0B/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
34
+
35
+ 178 https://lh3.googleusercontent.com/--Ggbfs9HJZM/Wog1DA2WcFI/AAAAAAAAbdQ/SZKPcnXFIr8MFfl9pddfiLPQnOiJ37muQCJoC/w409-h727-n/11a1c54f37c07d3c04588b40d108b4c2.jpg
36
+ 260 https://lh3.googleusercontent.com/-3Fe6CL9RH4o/WnipWY4889I/AAAAAAAAI4I/CPqdKpjqdlwkaBlCqGQ3RdZ9bLJ4nzBwwCJoC/w239-h318-n/gplus1855963830.jpg
37
+ 303 https://lh3.googleusercontent.com/-5vPhLnUILsA/WqFhJhu7VlI/AAAAAAAAAhQ/wWSrC8dqZTA7OuzeU3UoXJkNG19tqtAGwCJoC/w265-h353-n/DSC08260.JPG
38
+ 668 https://lh3.googleusercontent.com/-NLW68hNxEzU/WrSNfBxFX2I/AAAAAAAAAT8/7cvRM-DUZLoIdlb6CS8pL5-eJ3wbwohlQCJoC/w179-h318-n/23.03.2018%2B-%2B3
39
+ 669 https://lh3.googleusercontent.com/-NVJgqmI_2Is/WqMM2OMYg-I/AAAAAAAALrk/5-p3JL3iZt0Ho9dOf_p3gpddzqwr3Wp0ACJoC/w424-h318-n/001
40
+ 783 https://lh3.googleusercontent.com/-SqAvt_F__bg/Wq0huHcX2NI/AAAAAAAAVj0/XfnwCU7JwhUh0Knw8rcXA-bhpKYkI4hdwCJoC/w358-h318-n/IMG_20180317_120218-01-01.jpeg
41
+ 830 https://lh3.googleusercontent.com/-VB3YlLVL_tQ/Wo21yTbKl_I/AAAAAAAAApU/3DURmsYBklcv0kif2ZRjhoLG4mTHYf8OwCJoC/w254-h318-n/180218_00001.jpg
42
+ 1002 https://lh3.googleusercontent.com/-d8FkfLWq-_4/WpQXzEfFgBI/AAAAAAAADww/J32xKSAYUkgIc3pyrZnNmRec6kjdnJBHgCJoC/w239-h318-n/gplus-485739203.jpg
43
+ ".scan(/(\S+) (\S+)/).each do |i, url|
44
+ it "##{i}" do
45
+ assert_kind_of String, DirectLink.google(url)
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "imgur" do
51
+ %w{
52
+ https://imgur.com/a/badlinkpattern
53
+ http://example.com/
54
+ https://imgur.com/gallery/YO49F.
55
+ }.each_with_index do |url, i|
56
+ it "ErrorBadLink_##{i + 1}" do
57
+ assert_raises DirectLink::ErrorBadLink do
58
+ DirectLink.imgur url
59
+ end
60
+ end
61
+ end
62
+
63
+ valid_imgur_image_url = "https://i.imgur.com/BLCesav.jpg"
64
+ it 200 do
65
+ assert_equal [["https://i.imgur.com/BLCesav.jpg", 1000, 1500, "image/jpeg"]],
66
+ DirectLink.imgur(valid_imgur_image_url)
67
+ end
68
+ it 404 do
69
+ assert_raises DirectLink::ErrorNotFound do
70
+ NetHTTPUtils.stub :request_data, ->*{ raise NetHTTPUtils::Error.new "", 404 } do
71
+ DirectLink.imgur valid_imgur_image_url
72
+ end
73
+ end
74
+ end
75
+ it 400 do
76
+ e = assert_raises DirectLink::ErrorAssert do
77
+ NetHTTPUtils.stub :request_data, ->*{ raise NetHTTPUtils::Error.new "", 400 } do
78
+ DirectLink.imgur valid_imgur_image_url
79
+ end
80
+ end
81
+ assert_equal 400, e.cause.code if Exception.instance_methods.include? :cause # Ruby 2.1
82
+ end
83
+
84
+ [ # TODO: move these end line comments to test names; and do the same in other tests
85
+ ["https://imgur.com/a/Aoh6l", "https://i.imgur.com/BLCesav.jpg", 1000, 1500, "image/jpeg"],
86
+ ["http://i.imgur.com/7xcxxkR.gifv", "http://i.imgur.com/7xcxxkRh.gif", 718, 404, "image/gif"],
87
+ ["https://imgur.com/9yaMdJq", "https://i.imgur.com/9yaMdJq.mp4", 720, 404, "video/mp4"],
88
+ ["http://imgur.com/gallery/dCQprEq/new", "https://i.imgur.com/dCQprEq.jpg"],
89
+ ["http://imgur.com/HQHBBBD", "https://i.imgur.com/HQHBBBD.jpg"], # http
90
+ ["https://imgur.com/BGDh6eu", "https://i.imgur.com/BGDh6eu.jpg"], # https
91
+ ["https://imgur.com/a/qNCOo", 6, "https://i.imgur.com/vwqfi3s.jpg", "https://i.imgur.com/CnSMWvo.jpg"], # https album
92
+ ["http://imgur.com/a/0MiUo", 49, "https://i.imgur.com/kJ1jrjO.jpg", "https://i.imgur.com/TMQJARX.jpg"], # album, zoomable
93
+ ["http://imgur.com/a/WswMg", 20, "https://i.imgur.com/Bt3RWV7.png", "https://i.imgur.com/sRc2lqN.png"], # album image, not zoomable
94
+ ["http://imgur.com/a/AdJUK", 3, "https://i.imgur.com/Yunpxnx.jpg", "https://i.imgur.com/2epn2nT.jpg"], # needs https because of authorship # WAT?
95
+ ["http://imgur.com/gallery/vR4Am", 7, "https://i.imgur.com/yuUQI25.jpg", "https://i.imgur.com/RdxyAMQ.jpg"],
96
+ ["http://imgur.com/gallery/qP2RQtL", "https://i.imgur.com/qP2RQtL.png"], # single image gallery?
97
+ ["http://imgur.com/gallery/nKAwE/new", 28, "https://i.imgur.com/VQhR8hB.jpg", "https://i.imgur.com/axlzNRL.jpg"],
98
+ ["http://m.imgur.com/rarOohr", "https://i.imgur.com/rarOohr.jpg"],
99
+ ["http://imgur.com/r/wallpaper/j39dKMi", "https://i.imgur.com/j39dKMi.jpg"],
100
+ ["http://imgur.com/gallery/jm0OKQM", DirectLink::ErrorNotFound], # this id does not belong to gallery but to an image, that is probably irrelevant
101
+ ["http://imgur.com/gallery/oZXfZ", DirectLink::ErrorNotFound], # this id does not belong to gallery but to an album, that is probably irrelevant
102
+ ["http://imgur.com/gallery/dCQprEq/new", "https://i.imgur.com/dCQprEq.jpg", 5760, 3840, "image/jpeg"],
103
+ ["https://imgur.com/S5u2xRB?third_party=1#_=_", "https://i.imgur.com/S5u2xRB.jpg", 2448, 2448, "image/jpeg"],
104
+ ["https://imgur.com/3eThW", "https://i.imgur.com/3eThW.jpg", 2560, 1600, "image/jpeg"],
105
+ ["https://i.imgur.com/RGO6i.mp4", "https://i.imgur.com/RGO6i.gif", 339, 397, "image/gif"],
106
+ ["https://imgur.com/a/oacI3gl", 2, "https://i.imgur.com/9j4KdkJ.png", "https://i.imgur.com/QpOBvRY.png"],
107
+ # some other tests not sure we need them
108
+ ["http://i.imgur.com/7xcxxkR.gifv", "http://i.imgur.com/7xcxxkRh.gif"],
109
+ ["http://imgur.com/HQHBBBD", "https://i.imgur.com/HQHBBBD.jpg", 1024, 768, "image/jpeg"],
110
+ ["http://imgur.com/a/AdJUK", 3, "https://i.imgur.com/Yunpxnx.jpg", "https://i.imgur.com/2epn2nT.jpg", "https://i.imgur.com/3afw2aF.jpg"],
111
+ ["https://imgur.com/9yaMdJq", "https://i.imgur.com/9yaMdJq.mp4", 720, 404, "video/mp4"],
112
+ ["http://imgur.com/gallery/dCQprEq/new", "https://i.imgur.com/dCQprEq.jpg", 5760, 3840, "image/jpeg"],
113
+ ].each_with_index do |t, i|
114
+ url, n, first, last, type = t
115
+ it "##{i + 1}" do
116
+ case last
117
+ when NilClass
118
+ if n.is_a? Class
119
+ assert_raises n do
120
+ DirectLink.imgur url
121
+ end
122
+ else
123
+ real = DirectLink.imgur url
124
+ assert_equal 1, real.size
125
+ assert_equal n, real.first.first
126
+ end
127
+ when Numeric
128
+ real = DirectLink.imgur url
129
+ assert_equal 1, real.size
130
+ assert_equal [n, first, last, type], real.first
131
+ when String
132
+ real = DirectLink.imgur url
133
+ assert_equal n, real.size
134
+ assert_equal first, real.first.first
135
+ assert_equal last, real.last.first
136
+ else
137
+ fail "bug in tests"
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ [
144
+ [ :_500px, [
145
+ ["https://500px.com/photo/112134597/milky-way-by-tom-hall", [4928, 2888, "https://drscdn.500px.org/photo/112134597/m%3D2048_k%3D1_a%3D1/v2?client_application_id=48351&webp=true&sig=6b9f6ac5aed6d051aa66911555657ef57262e7d998f406e40f50e6f61515808f", "jpeg"]],
146
+ ] ],
147
+ [ :flickr, [
148
+ ["https://www.flickr.com/photos/tomas-/17220613278/", DirectLink::ErrorNotFound],
149
+ ["https://www.flickr.com/photos/16936123@N07/18835195572", DirectLink::ErrorNotFound],
150
+ ["https://www.flickr.com/photos/44133687@N00/17380073505/", [3000, 2000, "https://farm8.staticflickr.com/7757/17380073505_ed5178cc6a_o.jpg"]], # trailing slash
151
+ ["https://www.flickr.com/photos/jacob_schmidt/18414267018/in/album-72157654235845651/", DirectLink::ErrorNotFound], # username in-album
152
+ ["https://www.flickr.com/photos/tommygi/5291099420/in/dateposted-public/", [1600, 1062, "https://farm6.staticflickr.com/5249/5291099420_3bf8f43326_o.jpg"]], # username in-public
153
+ ["https://www.flickr.com/photos/132249412@N02/18593786659/in/album-72157654521569061/", DirectLink::ErrorNotFound],
154
+ ["https://www.flickr.com/photos/130019700@N03/18848891351/in/dateposted-public/", [4621, 3081, "https://farm4.staticflickr.com/3796/18848891351_f751b35aeb_o.jpg"]], # userid in-public
155
+ ["https://www.flickr.com/photos/frank3/3778768209/in/photolist-6KVb92-eCDTCr-ur8K-7qbL5z-c71afh-c6YvXW-7mHG2L-c71ak9-c71aTq-c71azf-c71aq5-ur8Q-6F6YkR-eCDZsD-eCEakg-eCE6DK-4ymYku-7ubEt-51rUuc-buujQE-ur8x-9fuNu7-6uVeiK-qrmcC6-ur8D-eCEbei-eCDY9P-eCEhCk-eCE5a2-eCH457-eCHrcq-eCEdZ4-eCH6Sd-c71b5o-c71auE-eCHa8m-eCDSbz-eCH1dC-eCEg3v-7JZ4rh-9KwxYL-6KV9yR-9tUSbU-p4UKp7-eCHfwS-6KVbAH-5FrdbP-eeQ39v-eeQ1UR-4jHAGN", [1024, 681, "https://farm3.staticflickr.com/2499/3778768209_280f82abab_b.jpg"]],
156
+ ["https://www.flickr.com/photos/patricksloan/18230541413/sizes/l", [2048, 491, "https://farm6.staticflickr.com/5572/18230541413_fec4783d79_k.jpg"]],
157
+ ["https://flic.kr/p/vPvCWJ", [2048, 1365, "https://farm1.staticflickr.com/507/19572004110_d44d1b4ead_k.jpg"]],
158
+ ] ],
159
+ [ :wiki, [
160
+ ["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"],
161
+ ["https://en.wikipedia.org/wiki/Third_Party_System#/media/File:United_States_presidential_election_results,_1876-1892.svg", DirectLink::ErrorAssert],
162
+ ["http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg", "https://upload.wikimedia.org/wikipedia/commons/0/0d/Eduard_Bohlen_anagoria.jpg"],
163
+ ] ],
164
+ ].each do |method, tests|
165
+ describe method do
166
+ tests.each_with_index do |(input, expectation), i|
167
+ it "##{i + 1}" do
168
+ if expectation.is_a? Class
169
+ assert_raises expectation do
170
+ DirectLink.method(method).call input
171
+ end
172
+ else
173
+ result = DirectLink.method(method).call input
174
+ assert_equal expectation, result, "#{input} :: #{result.inspect} != #{expectation.inspect}"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ {
182
+ google: %w{
183
+ https://lh3.googleusercontent.com/-NVJgqmI_2Is/WqMM2OMYg-I/AAAAAAAALrk/5-p3JL3iZt0Ho9dOf_p3gpddzqwr3Wp0ACJoC/w424-h318-n/001
184
+ https://lh3.googleusercontent.com/-2JzV0ErXvv8/Wsdr-m5Q_aI/AAAAAAAA_-I/Lw_e6qEMPUck4yW9yWhkC-nBNd5em8c3QCJoC/w530-h795-n/888052_800n.jpg
185
+ //lh3.googleusercontent.com/proxy/DZtTi5KL7PqiBwJc8weNGLk_Wi2UTaQH0AC_h2kuURiu0AbwyI2ywOk2XgdAjL7ceg=w530-h354-n
186
+ //lh3.googleusercontent.com/-h1siEcRFqJ8/Wiv1iDQhNVI/AAAAAAAAQV4/9gGepSmYkDMLyVTG_1EBCMmj-UhmclXWwCJoC/w530-h353-p/001
187
+ //1.bp.blogspot.com/-rYk_u-qROQc/WngolJ8M0LI/AAAAAAAAD-w/woivnaIVzasBPG5C2T1t-VrWKRd_U6lMgCLcBGAs/w530-h278-p/i-469.jpg
188
+ },
189
+ imgur: %w{
190
+ https://imgur.com/3eThW
191
+ https://i.imgur.com/3eThW
192
+ https://m.imgur.com/3eThW
193
+ https://www.imgur.com/3eThW
194
+ },
195
+ _500px: %w{
196
+ https://500px.com/photo/112134597/milky-way-by-tom-hall
197
+ },
198
+ flickr: %w{
199
+ https://www.flickr.com/photos/tomas-/17220613278/
200
+ https://flic.kr/p/vPvCWJ
201
+ },
202
+ wiki: %w{
203
+ https://en.wikipedia.org/wiki/Third_Party_System#/media/File:United_States_presidential_election_results,_1876-1892.svg
204
+ http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg
205
+ },
206
+ }.each do |method, tests|
207
+ describe "DirectLink() calls #{method}" do
208
+ tests.each_with_index do |input, i|
209
+ it "##{i + 1}" do
210
+ called = false
211
+ m = DirectLink.method method
212
+ DirectLink.stub method, ->link{ called = true } do
213
+ DirectLink.method(method).call input
214
+ assert called, "DirectLink.#{method} was not called"
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ describe "throws ErrorBadLink if method does not match the link" do
222
+ %i{ google imgur flickr _500px wiki }.each do |method|
223
+ it method do
224
+ assert_raises DirectLink::ErrorBadLink do
225
+ DirectLink.method(method).call ""
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ end
232
+
233
+ describe "DirectLink()" do
234
+
235
+ it "throws ErrorBadLink if link is invalid" do
236
+ e = assert_raises DirectLink::ErrorBadLink do
237
+ DirectLink "test"
238
+ end
239
+ assert_equal "test".inspect, e.message
240
+ end
241
+
242
+ describe "does not shadow the internal exception" do
243
+ [
244
+ [:google, "//lh3.googleusercontent.com/proxy/DZtTi5KL7PqiBwJc8weNGLk_Wi2UTaQH0AC_h2kuURiu0AbwyI2ywOk2XgdAjL7ceg=w530-h354-n"],
245
+ [:imgur, "http://imgur.com/HQHBBBD"],
246
+ [:flickr, "https://www.flickr.com/photos/tomas-/17220613278/"],
247
+ [:_500px, "https://500px.com/photo/112134597/milky-way-by-tom-hall"],
248
+ [:wiki, "http://commons.wikimedia.org/wiki/File:Eduard_Bohlen_anagoria.jpg"],
249
+ ].each do |method, link|
250
+ it method do
251
+ e = assert_raises DirectLink::ErrorBadLink do
252
+ DirectLink.stub method, ->*{ raise DirectLink::ErrorBadLink.new "test" } do
253
+ DirectLink link
254
+ end
255
+ end
256
+ assert_equal "\"test\" -- if you think this link is valid, please report the issue", e.message
257
+ end
258
+ end
259
+ it "raises FastImage::ImageFetchFailure on Net::HTTP failure" do
260
+ assert_raises FastImage::ImageFetchFailure do
261
+ NetHTTPUtils.stub :get_response, ->*{ raise SocketError.new } do
262
+ DirectLink "http://example.com/404"
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "some other tests" do
269
+ [
270
+ ["http://www.aeronautica.difesa.it/organizzazione/REPARTI/divolo/PublishingImages/6%C2%B0%20Stormo/2013-decollo%20al%20tramonto%20REX%201280.jpg", ["http://www.aeronautica.difesa.it/organizzazione/REPARTI/divolo/PublishingImages/6%C2%B0%20Stormo/2013-decollo%20al%20tramonto%20REX%201280.jpg", 1280, 853, :jpeg]],
271
+ ["http://example.com", FastImage::UnknownImageType], # was URL2Dimensions::ErrorUnknown
272
+ ["http://minus.com/lkP3hgRJd9npi", FastImage::ImageFetchFailure], # was URL2Dimensions::Error404
273
+ ["https://i.redd.it/si758zk7r5xz.jpg", FastImage::ImageFetchFailure], # was URL2Dimensions::Error404
274
+ ["http://www.cutehalloweencostumeideas.org/wp-content/uploads/2017/10/Niagara-Falls_04.jpg", FastImage::ImageFetchFailure], # was URL2Dimensions::Error404
275
+ ].each_with_index do |(input, expectation), i|
276
+ it "##{i + 1}" do
277
+ if expectation.is_a? Class
278
+ assert_raises expectation do
279
+ DirectLink input
280
+ end
281
+ else
282
+ u, w, h, t = expectation
283
+ result = DirectLink input
284
+ assert_equal DirectLink.class_variable_get(:@@directlink).new(u, w, h, t),
285
+ result, "#{input} :: #{result.inspect} != #{expectation.inspect}"
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ end
292
+
293
+ describe "./bin" do
294
+ require "open3"
295
+
296
+ describe "shows usage help if misused" do
297
+ [
298
+ ["usage: directlink [--debug] [--json] <link1> <link2> <link3> ...\n", nil],
299
+ ["usage: directlink [--debug] [--json] <link1> <link2> <link3> ...\n", "--help"],
300
+ ["usage: directlink [--debug] [--json] <link1> <link2> <link3> ...\n", "-h"],
301
+ ["DirectLink::ErrorBadLink: \"--\"\n", "--"],
302
+ ["DirectLink::ErrorBadLink: \"-\"\n", "-"],
303
+ ["DirectLink::ErrorBadLink: \"-\"\n", "- -"],
304
+ ["DirectLink::ErrorBadLink: \"asd\"\n", "asd"],
305
+ ].each_with_index do |(expected_output, param), i|
306
+ it "##{i + 1}" do
307
+ string, status = Open3.capture2e "ruby -Ilib bin/directlink #{param}"
308
+ assert_equal [1, expected_output], [status.exitstatus, string]
309
+ end
310
+ end
311
+ end
312
+
313
+ # that hack around `export` is for crossplatform Travis test, since Ubuntu uses `sh` that can't `source`
314
+ # TODO: but maybe I could make Open3 use the shell I need, since `source` works fine for `.travis.yaml`'s `:script`
315
+
316
+ describe "fails" do
317
+ [
318
+ [1, "http://example.com/", "FastImage::UnknownImageType"], # TODO: is it possible to obtain it from `.cause`?
319
+ [1, "http://example.com/404", "FastImage::ImageFetchFailure"],
320
+ [1, "http://imgur.com/HQHBBBD", "DirectLink::ErrorMissingEnvVar: define IMGUR_CLIENT_ID env var", " && unset IMGUR_CLIENT_ID"], # TODO: make similar test for ./lib
321
+ # by design it should be impossible to write a test for DirectLink::ErrorAssert
322
+ [1, "https://flic.kr/p/DirectLinkErrorNotFound", "DirectLink::ErrorNotFound: \"https://flic.kr/p/DirectLinkErrorNotFound\""],
323
+ [1, "https://imgur.com/a/badlinkpattern", "DirectLink::ErrorBadLink: \"https://imgur.com/a/badlinkpattern\" -- if you think this link is valid, please report the issue"],
324
+ ].each_with_index do |(expected_exit_code, link, expected_output, unset), i|
325
+ it "##{i + 1}" do
326
+ string, status = Open3.capture2e "export #{File.read("api_tokens_for_travis.sh").gsub(/\n?export/, ?\s).strip}#{unset} && ruby -Ilib bin/directlink #{link}"
327
+ assert_equal [expected_exit_code, "#{expected_output}\n"], [status.exitstatus, string]
328
+ end
329
+ end
330
+ end
331
+
332
+ valid_imgur_image_urls = "https://avatars1.githubusercontent.com/u/2870363?100 https://imgur.com/a/oacI3gl"
333
+ [
334
+ ['https://avatars1.githubusercontent.com/u/2870363?100
335
+ jpeg 460x460
336
+
337
+ https://i.imgur.com/QpOBvRY.png
338
+ image/png 460x460
339
+ https://i.imgur.com/9j4KdkJ.png
340
+ image/png 100x100
341
+ '.gsub(/^ {8}/, "")],
342
+ ['[
343
+ {
344
+ "url": "https://avatars1.githubusercontent.com/u/2870363?100",
345
+ "width": 460,
346
+ "height": 460,
347
+ "type": "jpeg"
348
+ },
349
+ [
350
+ {
351
+ "url": "https://i.imgur.com/QpOBvRY.png",
352
+ "width": 460,
353
+ "height": 460,
354
+ "type": "image/png"
355
+ },
356
+ {
357
+ "url": "https://i.imgur.com/9j4KdkJ.png",
358
+ "width": 100,
359
+ "height": 100,
360
+ "type": "image/png"
361
+ }
362
+ ]
363
+ ]
364
+ '.gsub(/^ {8}/, ""), "json"],
365
+ ].each do |expected_output, param|
366
+ it "#{param || "default"} succeeds" do
367
+ string, status = Open3.capture2e "export #{File.read("api_tokens_for_travis.sh").gsub(/\n?export/, ?\s).strip} && ruby -Ilib bin/directlink#{" --#{param}" if param} #{valid_imgur_image_urls}"
368
+ assert_equal [0, expected_output], [status.exitstatus, string]
369
+ end
370
+ end
371
+
372
+ end
373
+
374
+ end