ZMediumToMarkdown 3.4.0 → 3.5.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 +4 -4
- data/lib/CLI.rb +5 -19
- data/lib/Parsers/IMGParser.rb +2 -1
- data/lib/Post.rb +1 -1
- data/lib/Request.rb +30 -0
- data/lib/ZMediumFetcher.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 047cd0234c7a856da3d912b93d419ddcd7ad1396079ba49d3c494807ca37a8de
|
|
4
|
+
data.tar.gz: c4a96904909f9885c48bff408200a886191858be9c9f8746a01db065e66aaa8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 07f6681e620d463a849ccff8e8d497c10114073d8aad3792f0d2beb6cf730145dfb36ac6e304a3ad805de56aad5f24e3f028c5e2613da4f38228657bbc9f70f7
|
|
7
|
+
data.tar.gz: 6e8056232851f1f2656a5b6340b6972bdc603f8ff4aedd18e101c9ec8eb5fe10d7b4ca4d6376086e533daddd180d169a4db7b3c2ead4bd69ba1b175cf73661d9
|
data/lib/CLI.rb
CHANGED
|
@@ -14,7 +14,6 @@ module CLI
|
|
|
14
14
|
COOKIE_SETUP_URL = 'https://github.com/ZhgChgLi/ZMediumToMarkdown/wiki/Setting-Up-Medium-Cookies-and-a-Cloudflare-Worker-Proxy'.freeze
|
|
15
15
|
|
|
16
16
|
DEFAULT_MEDIUM_HOST = 'https://medium.com/_/graphql'.freeze
|
|
17
|
-
DEFAULT_MIRO_MEDIUM_HOST = 'https://miro.medium.com'.freeze
|
|
18
17
|
|
|
19
18
|
module_function
|
|
20
19
|
|
|
@@ -53,10 +52,6 @@ module CLI
|
|
|
53
52
|
ENV['MEDIUM_HOST'] = v
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
opts.on('--miro_medium_host URL', 'Cloudflare Worker proxy URL for Medium image CDN (or set $MIRO_MEDIUM_HOST). Optional companion to --medium_host.') do |v|
|
|
57
|
-
ENV['MIRO_MEDIUM_HOST'] = v
|
|
58
|
-
end
|
|
59
|
-
|
|
60
55
|
opts.on('-u', '--username USERNAME', 'Download all posts from a Medium username') do |v|
|
|
61
56
|
options[:username] = v
|
|
62
57
|
end
|
|
@@ -167,25 +162,17 @@ module CLI
|
|
|
167
162
|
!host.empty? && host != DEFAULT_MEDIUM_HOST
|
|
168
163
|
end
|
|
169
164
|
|
|
170
|
-
def imageProxyConfigured?
|
|
171
|
-
host = ENV['MIRO_MEDIUM_HOST'].to_s
|
|
172
|
-
!host.empty? && host != DEFAULT_MIRO_MEDIUM_HOST
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
|
|
176
165
|
# Only warn when the invocation will actually hit Medium — skip for
|
|
177
166
|
# --version, --clean, --help, --new.
|
|
178
167
|
def warnAboutMissingSetup(options, errput: $stderr)
|
|
179
168
|
return unless willHitMedium?(options)
|
|
180
169
|
|
|
181
|
-
missingCookies
|
|
182
|
-
missingProxy
|
|
183
|
-
|
|
184
|
-
return if !missingCookies && !missingProxy && !missingImageProxy
|
|
170
|
+
missingCookies = !cookiesPresent?
|
|
171
|
+
missingProxy = !proxyConfigured?
|
|
172
|
+
return if !missingCookies && !missingProxy
|
|
185
173
|
|
|
186
174
|
errput.puts buildSetupBanner(missingCookies: missingCookies,
|
|
187
|
-
missingProxy: missingProxy
|
|
188
|
-
missingImageProxy: missingImageProxy)
|
|
175
|
+
missingProxy: missingProxy)
|
|
189
176
|
end
|
|
190
177
|
|
|
191
178
|
def willHitMedium?(options)
|
|
@@ -194,11 +181,10 @@ module CLI
|
|
|
194
181
|
|
|
195
182
|
# One-line warning. The wiki has the actual setup steps; we just
|
|
196
183
|
# nudge the user toward it instead of dumping a wall of guidance.
|
|
197
|
-
def buildSetupBanner(missingCookies:, missingProxy
|
|
184
|
+
def buildSetupBanner(missingCookies:, missingProxy:)
|
|
198
185
|
missing = []
|
|
199
186
|
missing << 'Medium cookies (sid / uid)' if missingCookies
|
|
200
187
|
missing << 'Cloudflare Worker proxy (MEDIUM_HOST)' if missingProxy
|
|
201
|
-
missing << 'Cloudflare image proxy (MIRO_MEDIUM_HOST)' if missingImageProxy
|
|
202
188
|
return '' if missing.empty?
|
|
203
189
|
|
|
204
190
|
"⚠ Missing #{missing.join(' / ')}. Medium / Cloudflare may block the run. Setup guide: #{COOKIE_SETUP_URL}"
|
data/lib/Parsers/IMGParser.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'Models/Paragraph'
|
|
|
3
3
|
|
|
4
4
|
require 'ImageDownloader'
|
|
5
5
|
require 'PathPolicy'
|
|
6
|
+
require 'Request'
|
|
6
7
|
|
|
7
8
|
class IMGParser < Parser
|
|
8
9
|
attr_accessor :nextParser, :pathPolicy, :isForJekyll
|
|
@@ -20,7 +21,7 @@ class IMGParser < Parser
|
|
|
20
21
|
|
|
21
22
|
fileName = paragraph.metadata.id #d*fsafwfe.jpg
|
|
22
23
|
|
|
23
|
-
miro_host =
|
|
24
|
+
miro_host = Request.miroHost
|
|
24
25
|
imageURL = "#{miro_host}/#{fileName}"
|
|
25
26
|
|
|
26
27
|
result = ""
|
data/lib/Post.rb
CHANGED
|
@@ -67,7 +67,7 @@ class Post
|
|
|
67
67
|
imagePathPolicy = PathPolicy.new(pathPolicy.getAbsolutePath(postID), pathPolicy.getRelativePath(postID))
|
|
68
68
|
absolutePath = imagePathPolicy.getAbsolutePath(previewImageFileName)
|
|
69
69
|
|
|
70
|
-
miro_host =
|
|
70
|
+
miro_host = Request.miroHost
|
|
71
71
|
imageURL = "#{miro_host}/#{previewImageFileName}"
|
|
72
72
|
|
|
73
73
|
if ImageDownloader.download(absolutePath, imageURL)
|
data/lib/Request.rb
CHANGED
|
@@ -227,6 +227,14 @@ class Request
|
|
|
227
227
|
request['Cookie'] = cookiesString;
|
|
228
228
|
end
|
|
229
229
|
|
|
230
|
+
# When the request is going to a configured Worker proxy (and only
|
|
231
|
+
# then), attach the user's MEDIUM_HOST_SECRET as a header so the
|
|
232
|
+
# Worker can authenticate the caller. Skipped for upstream
|
|
233
|
+
# medium.com / miro.medium.com so the secret never leaks to Medium.
|
|
234
|
+
if proxyURI?(uri) && (proxySecret = ENV['MEDIUM_HOST_SECRET'].to_s) && !proxySecret.empty?
|
|
235
|
+
request['X-Medium-Proxy-Secret'] = proxySecret
|
|
236
|
+
end
|
|
237
|
+
|
|
230
238
|
response = https.request(request);
|
|
231
239
|
|
|
232
240
|
setCookieString = response.get_fields('set-cookie');
|
|
@@ -305,6 +313,28 @@ class Request
|
|
|
305
313
|
nil
|
|
306
314
|
end
|
|
307
315
|
|
|
316
|
+
# Resolve the host the gem should use for miro.medium.com image fetches.
|
|
317
|
+
# Single-Worker setups: the same MEDIUM_HOST proxy handles both medium.com
|
|
318
|
+
# and miro.medium.com via path dispatch, so we always derive miro from
|
|
319
|
+
# MEDIUM_HOST's origin. No proxy → upstream miro.medium.com.
|
|
320
|
+
def self.miroHost
|
|
321
|
+
mediumProxyOrigin || 'https://miro.medium.com'
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# True iff `uri` is hosted by the configured Worker proxy — i.e. its
|
|
325
|
+
# host matches MEDIUM_HOST and MEDIUM_HOST is set to something other
|
|
326
|
+
# than upstream medium.com. Used to gate the MEDIUM_HOST_SECRET auth
|
|
327
|
+
# header so the secret only leaves the process when heading to the
|
|
328
|
+
# user's own proxy.
|
|
329
|
+
def self.proxyURI?(uri)
|
|
330
|
+
return false if uri.nil? || uri.host.nil?
|
|
331
|
+
envValue = ENV['MEDIUM_HOST'].to_s
|
|
332
|
+
return false if envValue.empty?
|
|
333
|
+
parsed = URI.parse(envValue) rescue nil
|
|
334
|
+
return false if parsed.nil? || parsed.host.nil?
|
|
335
|
+
parsed.host != 'medium.com' && parsed.host == uri.host
|
|
336
|
+
end
|
|
337
|
+
|
|
308
338
|
# Cloudflare tags blocked responses via either the cf-mitigated header
|
|
309
339
|
# or the standard "Just a moment..." challenge HTML. We check both
|
|
310
340
|
# so we catch challenges even on Cloudflare deployments that don't
|
data/lib/ZMediumFetcher.rb
CHANGED
|
@@ -178,8 +178,8 @@ class ZMediumFetcher
|
|
|
178
178
|
|
|
179
179
|
# Stdout fast path: render markdown directly to `stdoutIO` without
|
|
180
180
|
# touching the filesystem and without downloading any images. Image
|
|
181
|
-
# references stay as remote miro.medium.com
|
|
182
|
-
# proxy
|
|
181
|
+
# references stay as remote URLs on miro.medium.com (or the configured
|
|
182
|
+
# MEDIUM_HOST proxy origin when set).
|
|
183
183
|
def downloadPostToStdout(postURL, isPin)
|
|
184
184
|
postID = Post.getPostIDFromPostURLString(postURL)
|
|
185
185
|
postPath = Post.getPostPathFromPostURLString(postURL)
|