ZMediumToMarkdown 2.3.8 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6aa04febbe53fb63849ed9bc0fdd83c1e30b67e808f42899e9ec2d4772db57bd
4
- data.tar.gz: '09fb6a627ecb9c320b114113713464871d60fe1e2c32810ecba4c77ea11e5381'
3
+ metadata.gz: d8a5ca887a7781219ae534d50ebe022c796d21ddc6eb5fab977559fd65ce4cb2
4
+ data.tar.gz: 4e43067b4502adc9a11cf3347c68091980c273e567d22811e6d4d02b233ef5a8
5
5
  SHA512:
6
- metadata.gz: 3e58ac4d9c2f00113e9446594f75067c2f9a48a3c8c35f2ef752da024c19e18c77eb3f03f816b53a7c0c8fd6603b2790d08cd5986e6de7d858c555f0d17c9e91
7
- data.tar.gz: eab3c050b0fdc2cfc0c85518cabdc4d9d3a1eda6de220046639fb76ccdb133540e9408640a1dec9b48574933f465d130b988fe072af6b40e79a78cd719b065db
6
+ metadata.gz: 8e7ecbc20c8038097d213bfce93f6129b8451f7a990ad97c04f07533b6b9c0be8c0a913d4599d19b8aa6ca8e8d95724da4190f6f55c5678c5e944078d022a390
7
+ data.tar.gz: 7ea56ec01d108d51a1f56087e28a5596a7e865dbc937507ccf2e029ed8ac1f536225a940437d30a5e36ab3a74e7c1fef38b410f0adc7b6081144baafdcbd3e4b
@@ -8,69 +8,106 @@ require "ZMediumFetcher"
8
8
  require "Helper"
9
9
  require "optparse"
10
10
 
11
+ $cookie_sid = nil
12
+ $cookie_uid = nil
13
+
11
14
  class Main
12
15
  def initialize
13
16
  fetcher = ZMediumFetcher.new
14
17
  ARGV << '-h' if ARGV.empty?
15
18
 
19
+ options = {}
16
20
  filePath = ENV['PWD'] || ::Dir.pwd
17
-
21
+
18
22
  OptionParser.new do |opts|
19
23
  opts.banner = "Usage: ZMediumFetcher [options]"
20
-
21
- opts.on('-uUSERNAME', '--username=USERNAME', 'Downloading all posts from user') do |username|
22
- outputFilePath = PathPolicy.new("#{filePath}/Output", "Output")
23
- fetcher.downloadPostsByUsername(username, outputFilePath)
24
-
25
- Helper.printNewVersionMessageIfExists()
24
+
25
+ opts.on('-s', '--cookie_sid COOKIESID', 'Your logged-in Medium cookie sid value') do |cookie_sid|
26
+ $cookie_sid = cookie_sid
26
27
  end
27
-
28
- opts.on('-pPOST_URL', '--postURL=POST_URL', 'Downloading single post') do |postURL|
29
- outputFilePath = PathPolicy.new("#{filePath}/Output", "Output")
30
- fetcher.downloadPost(postURL, outputFilePath, nil)
31
28
 
32
- Helper.printNewVersionMessageIfExists()
29
+ opts.on('-d', '--cookie_uid COOKIEUID', 'Your logged-in Medium cookie uid value') do |cookie_uid|
30
+ $cookie_uid = cookie_uid
33
31
  end
34
32
 
35
- opts.on('-jUSERNAME', '--jekyllUsername=USERNAME', 'Downloading all posts from user with Jekyll friendly') do |username|
36
- outputFilePath = PathPolicy.new(filePath, "")
37
- fetcher.isForJekyll = true
38
- fetcher.downloadPostsByUsername(username, outputFilePath)
39
-
40
- Helper.printNewVersionMessageIfExists()
33
+ opts.on('-u', '--username USERNAME', 'Downloading all posts from user') do |username|
34
+ options[:u] = username
41
35
  end
42
36
 
43
- opts.on('-kPOST_URL', '--jekyllPostURL=POST_URL', 'Downloading single post with Jekyll friendly') do |postURL|
44
- outputFilePath = PathPolicy.new(filePath, "")
45
- fetcher.isForJekyll = true
46
- fetcher.downloadPost(postURL, outputFilePath, nil)
37
+ opts.on('-p', '--postURL POST_URL', 'Downloading single post') do |postURL|
38
+ options[:p] = postURL
39
+ end
47
40
 
48
- Helper.printNewVersionMessageIfExists()
41
+ opts.on('-j', '--jekyllUsername USERNAME', 'Downloading all posts from user with Jekyll friendly') do |username|
42
+ options[:j] = username
43
+ end
44
+
45
+ opts.on('-k', '--jekyllPostURL POST_URL', 'Downloading single post with Jekyll friendly') do |postURL|
46
+ options[:k] = postURL
49
47
  end
50
48
 
51
49
  opts.on('-n', '--new', 'Update to latest version') do
52
- if Helper.getRemoteVersionFromGithub() > Helper.getLocalVersion()
53
- Helper.downloadLatestVersion()
54
- else
55
- puts "You're using the latest version :)"
56
- end
50
+ options[:n] = true
57
51
  end
58
52
 
59
53
  opts.on('-c', '--clean', 'Remove all downloaded posts data') do
60
- outputFilePath = PathPolicy.new(filePath, "")
61
- FileUtils.rm_rf(Dir[outputFilePath.getAbsolutePath(nil)])
62
- puts "All downloaded posts data has been removed."
63
-
64
- Helper.printNewVersionMessageIfExists()
54
+ options[:c] = true
65
55
  end
66
56
 
67
57
  opts.on('-v', '--version', 'Print current ZMediumToMarkdown Version & Output Path') do
68
- puts "Version:#{Helper.getLocalVersion().to_s}"
69
-
70
- Helper.printNewVersionMessageIfExists()
58
+ options[:v] = true
71
59
  end
72
-
73
60
  end.parse!
61
+
62
+ #
63
+ if !options[:v].nil? && options[:v] == true
64
+ puts "Version:#{Helper.getLocalVersion().to_s}"
65
+
66
+ Helper.printNewVersionMessageIfExists()
67
+ elsif !options[:c].nil? && options[:c] == true
68
+ outputFilePath = PathPolicy.new(filePath, "")
69
+ FileUtils.rm_rf(Dir[outputFilePath.getAbsolutePath(nil)])
70
+ puts "All downloaded posts data has been removed."
71
+
72
+ Helper.printNewVersionMessageIfExists()
73
+ elsif !options[:n].nil? && options[:n] == true
74
+ if Helper.getRemoteVersionFromGithub() > Helper.getLocalVersion()
75
+ Helper.downloadLatestVersion()
76
+ else
77
+ puts "You're using the latest version :)"
78
+ end
79
+ elsif !options[:k].nil?
80
+ postURL = options[:k]
81
+
82
+ outputFilePath = PathPolicy.new(filePath, "")
83
+ fetcher.isForJekyll = true
84
+ fetcher.downloadPost(postURL, outputFilePath, nil)
85
+
86
+ Helper.printNewVersionMessageIfExists()
87
+ elsif !options[:j].nil?
88
+ username = options[:j]
89
+
90
+ outputFilePath = PathPolicy.new(filePath, "")
91
+ fetcher.isForJekyll = true
92
+ fetcher.downloadPostsByUsername(username, outputFilePath)
93
+
94
+ Helper.printNewVersionMessageIfExists()
95
+ elsif !options[:p].nil?
96
+ postURL = options[:p]
97
+
98
+ outputFilePath = PathPolicy.new("#{filePath}/Output", "Output")
99
+ fetcher.downloadPost(postURL, outputFilePath, nil)
100
+
101
+ Helper.printNewVersionMessageIfExists()
102
+ elsif !options[:u].nil?
103
+ username = options[:u]
104
+
105
+ outputFilePath = PathPolicy.new("#{filePath}/Output", "Output")
106
+ fetcher.downloadPostsByUsername(username, outputFilePath)
107
+
108
+ Helper.printNewVersionMessageIfExists()
109
+ end
110
+
74
111
  end
75
112
  end
76
113
 
data/lib/Helper.rb CHANGED
@@ -99,7 +99,7 @@ class Helper
99
99
  end
100
100
  end
101
101
 
102
- def self.createPostInfo(postInfo, isPin, isForJekyll)
102
+ def self.createPostInfo(postInfo, isPin, isLockedPreviewOnly, isForJekyll)
103
103
  title = postInfo.title&.gsub("[","")
104
104
  title = title&.gsub("]","")
105
105
 
@@ -123,6 +123,9 @@ class Helper
123
123
  if !isPin.nil? && isPin == true
124
124
  result += "pin: true\r\n"
125
125
  end
126
+ if !isLockedPreviewOnly.nil? && isLockedPreviewOnly == true
127
+ result += "lockedPreviewOnly: true\r\n"
128
+ end
126
129
 
127
130
  if isForJekyll
128
131
  result += "render_with_liquid: false\n"
@@ -211,4 +214,17 @@ class Helper
211
214
 
212
215
  text
213
216
  end
217
+
218
+ def self.createViewFullPost(postURL, isForJekyll)
219
+ jekyllOpen = ""
220
+ if isForJekyll
221
+ jekyllOpen = "{:target=\"_blank\"}"
222
+ end
223
+
224
+ text = "\r\n\r\n\r\n"
225
+ text += "**This [post](#{postURL})#{jekyllOpen} is behind Medium's paywall, View the full [post](#{postURL})#{jekyllOpen} on Medium, converted by [ZMediumToMarkdown](https://github.com/ZhgChgLi/ZMediumToMarkdown)#{jekyllOpen}.**"
226
+ text += "\r\n"
227
+
228
+ text
229
+ end
214
230
  end
data/lib/Post.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  $lib = File.expand_path('../lib', File.dirname(__FILE__))
2
2
 
3
3
  require "Request"
4
+ require "net/http"
4
5
  require 'uri'
5
6
  require 'nokogiri'
6
7
  require 'json'
@@ -50,10 +51,22 @@ class Post
50
51
  }
51
52
  ]
52
53
 
53
- body = Request.body(Request.URL("https://medium.com/_/graphql", "POST", query))
54
+ uri = URI("https://medium.com/_/graphql")
55
+ https = Net::HTTP.new(uri.host, uri.port)
56
+ https.use_ssl = true
57
+ request = Net::HTTP::Post.new(uri)
58
+ request['Content-Type'] = 'application/json'
59
+ if !$cookie_sid.nil? && !$cookie_uid.nil?
60
+ request['Cookie'] = "sid=#{$cookie_sid}; uid=#{$cookie_uid}"
61
+ end
62
+ request.body = JSON.dump(query)
63
+ response = https.request(request)
64
+
65
+
66
+ body = Request.body(response)
54
67
  if !body.nil?
55
68
  json = JSON.parse(body)
56
- json&.dig(0, "data", "post", "viewerEdge", "fullContent", "bodyModel", "paragraphs")
69
+ json&.dig(0, "data", "post", "viewerEdge", "fullContent")
57
70
  else
58
71
  nil
59
72
  end
@@ -148,8 +148,16 @@ class ZMediumFetcher
148
148
  end
149
149
 
150
150
  postInfo = Post.parsePostInfoFromPostContent(postContent, postID, imagePathPolicy)
151
+ contentInfo = Post.fetchPostParagraphs(postID)
152
+
153
+ if contentInfo.nil?
154
+ raise "Error: Paragraph Content not found! PostURL: #{postURL}"
155
+ end
156
+
157
+ isLockedPreviewOnly = contentInfo&.dig("isLockedPreviewOnly")
158
+
159
+ sourceParagraphs = contentInfo&.dig("bodyModel", "paragraphs")
151
160
 
152
- sourceParagraphs = Post.fetchPostParagraphs(postID)
153
161
  if sourceParagraphs.nil?
154
162
  raise "Error: Paragraph not found! PostURL: #{postURL}"
155
163
  end
@@ -236,6 +244,7 @@ class ZMediumFetcher
236
244
 
237
245
  fileLatestPublishedAt = nil
238
246
  filePin = false
247
+ fileLockedPreviewOnly = false
239
248
  if File.file?(absolutePath)
240
249
  lines = File.foreach(absolutePath).first(15)
241
250
  if lines.first&.start_with?("---")
@@ -248,10 +257,15 @@ class ZMediumFetcher
248
257
  if !pinLine.nil?
249
258
  filePin = pinLine[/^(pin:)\s+(\S*)/, 2].downcase == "true"
250
259
  end
260
+
261
+ lockedPreviewOnlyLine = lines.select { |line| line.start_with?("lockedPreviewOnly:") }.first
262
+ if !lockedPreviewOnlyLine.nil?
263
+ fileLockedPreviewOnly = lockedPreviewOnlyLine[/^(lockedPreviewOnly:)\s+(\S*)/, 2].downcase == "true"
264
+ end
251
265
  end
252
266
  end
253
267
 
254
- if (!fileLatestPublishedAt.nil? && fileLatestPublishedAt >= postInfo.latestPublishedAt.to_i) && (!isPin.nil? && isPin == filePin)
268
+ if (!fileLatestPublishedAt.nil? && fileLatestPublishedAt >= postInfo.latestPublishedAt.to_i) && (!isPin.nil? && isPin == filePin) && (!isLockedPreviewOnly.nil? && isLockedPreviewOnly == fileLockedPreviewOnly)
255
269
  # Already downloaded and nothing has changed!, Skip!
256
270
  progress.currentPostParagraphIndex = paragraphs.length
257
271
  progress.message = "Skip, Post already downloaded and nothing has changed!"
@@ -260,7 +274,7 @@ class ZMediumFetcher
260
274
  Helper.createDirIfNotExist(postPathPolicy.getAbsolutePath(nil))
261
275
  File.open(absolutePath, "w+") do |file|
262
276
  # write postInfo into top
263
- postMetaInfo = Helper.createPostInfo(postInfo, isPin, isForJekyll)
277
+ postMetaInfo = Helper.createPostInfo(postInfo, isPin, isLockedPreviewOnly, isForJekyll)
264
278
  if !postMetaInfo.nil?
265
279
  file.puts(postMetaInfo)
266
280
  end
@@ -283,15 +297,29 @@ class ZMediumFetcher
283
297
  progress.message = "Converting Post..."
284
298
  progress.printLog()
285
299
  end
286
-
287
- postWatermark = Helper.createWatermark(postURL, isForJekyll)
288
- if !postWatermark.nil?
289
- file.puts(postWatermark)
300
+
301
+ if isLockedPreviewOnly
302
+ viewFullPost = Helper.createViewFullPost(postURL, isForJekyll)
303
+ if !viewFullPost.nil?
304
+ file.puts(viewFullPost)
305
+ end
306
+ else
307
+ postWatermark = Helper.createWatermark(postURL, isForJekyll)
308
+ if !postWatermark.nil?
309
+ file.puts(postWatermark)
310
+ end
290
311
  end
312
+
313
+
291
314
  end
292
315
  FileUtils.touch absolutePath, :mtime => postInfo.latestPublishedAt
293
316
 
294
- progress.message = "Post Successfully Downloaded!"
317
+ if isLockedPreviewOnly
318
+ progress.message = "This post is behind Medium's paywall. You need to provide valid Medium Member login cookies to download the full post."
319
+ else
320
+ progress.message = "Post Successfully Downloaded!"
321
+ end
322
+
295
323
  progress.printLog()
296
324
  end
297
325
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ZMediumToMarkdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.8
4
+ version: 2.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ZhgChgLi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-26 00:00:00.000000000 Z
11
+ date: 2024-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri