middleman-medium_export 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 70a0f8d944340107139756124aa63973a3f71c686fcee927ea63a830e8971f22
4
+ data.tar.gz: 8cca4d27765a84ffaedb871012d3f46fe1b2ec33f4d8874155a6c4a593829c17
5
+ SHA512:
6
+ metadata.gz: c6884c14ae7550558aa91a0ce8d80d548f1a37c5305b6d291ccae6910cc79a7ece792218b36cdd3a730d1f419163edf0f9743a3930997a95134947c1c6b64570
7
+ data.tar.gz: 7178bd55aab8c1524da4464caf0a91189f3a50eaa9044b0a922050536959afeafe2e1a30757850199ee7c53605c48b3ab16cac75bc136005eb762e1767382a8d
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,77 @@
1
+ # Middleman Medium Export
2
+
3
+ A Middleman extension that adds a cli command to export your articles to medium
4
+
5
+ # Getting Started
6
+
7
+ Add the gem to your gemfile
8
+
9
+ ```ruby
10
+ gem "middleman-medium_export"
11
+ ```
12
+
13
+ Activate it in your `config.rb`
14
+
15
+ ```ruby
16
+ activate :medium_export do |export|
17
+ export.api_token = 'your_api_token'
18
+
19
+ # export.template_path = 'path_to_template'
20
+ # export.template_position = :top
21
+ # export.publish_status = :draft
22
+ end
23
+ ```
24
+
25
+ ## Options
26
+
27
+ Extension provides several options
28
+
29
+ `api_token` - required, self issued api token to post your articles
30
+
31
+ `template_path` - optinal, path to partial to add to your article
32
+ `template_position` - optional. Position of your template. Possible values are: `:top` and `:bottom`. Default is `:bottom`
33
+ `publish_status` - optional. Status of your articles, when they are exported. Possible values are: `public`, `draft`, or `unlisted`. Default is `:draft`
34
+
35
+ ## Export
36
+
37
+ ### Interactivly
38
+
39
+ To chose article to export interactivlt
40
+
41
+ ```ruby
42
+ be middleman medium_export -m interactive
43
+ ```
44
+
45
+ ### Only last article
46
+
47
+ ```ruby
48
+ be middleman medium_export
49
+ ```
50
+
51
+ or
52
+
53
+ ```ruby
54
+ be middleman medium_export -m last
55
+ ```
56
+
57
+ ### All articles
58
+
59
+ ```ruby
60
+ be middleman medium_export -m all
61
+ ```
62
+
63
+ ## Obtaining Api Token
64
+
65
+ You used to be able to create an `api_token` yourself, but due to increased spam activity from API, this feature is now restricted. You need to email support with request for an API Token. It usually takes from a couple of hours to a day. Visit official [documentaion](https://github.com/Medium/medium-api-docs#22-self-issued-access-tokens) for more info.
66
+
67
+ # Contribution
68
+
69
+ Pull requests and issues are very welcome
70
+
71
+ To create a pull request
72
+
73
+ * Clone repo
74
+ * Run tests to see, that all is fine `be rake test`
75
+ * Implement feature
76
+ * Add tests
77
+
@@ -0,0 +1,8 @@
1
+ require "middleman-core"
2
+
3
+ Middleman::Extensions.register :medium_export do
4
+ require "middleman-medium_export/extension"
5
+ require "middleman-medium_export/commands/export"
6
+
7
+ MediumExport
8
+ end
@@ -0,0 +1,53 @@
1
+ require 'httparty'
2
+
3
+ class MediumExport::ApiClient
4
+ include HTTParty
5
+
6
+ attr_reader :auth, :publish_status
7
+
8
+ base_uri 'https://api.medium.com/v1/'
9
+
10
+ headers 'Content-Type': 'application/json'
11
+ headers 'Accept': 'application/json'
12
+
13
+ # raise_on (400..599).to_a
14
+
15
+ def initialize(api_token:, publish_status:)
16
+ @auth = { Authorization: "Bearer #{api_token}" }
17
+ @publish_status = publish_status
18
+ end
19
+
20
+ def publish(content:, title:, tags:)
21
+ response = self.class.post(
22
+ "/users/#{id}/posts",
23
+ headers: auth,
24
+ body: {
25
+ "title": title,
26
+ "contentFormat": "html",
27
+ "content": content,
28
+ "tags": tags,
29
+ "publishStatus": publish_status
30
+ }.to_json
31
+ )
32
+ end
33
+
34
+ def upload_image(image:)
35
+ response = self.class.post(
36
+ "https://api.medium.com/v1/images",
37
+ headers: auth,
38
+ body: {
39
+ "image": image
40
+ }
41
+ )
42
+ end
43
+
44
+ def me
45
+ self.class.get('/me', headers: auth)
46
+ end
47
+
48
+ private
49
+
50
+ def id
51
+ @id ||= me['data']['id']
52
+ end
53
+ end
@@ -0,0 +1,36 @@
1
+ class MediumExport::ArticlesFilter
2
+ attr_reader :articles, :shell
3
+
4
+ def initialize(articles:, shell:)
5
+ @articles = articles
6
+ @shell = shell
7
+ end
8
+
9
+ def last
10
+ [articles.first]
11
+ end
12
+
13
+ def all
14
+ articles
15
+ end
16
+
17
+ def interactive
18
+ articles.each_with_object([]) do |article, acc|
19
+ case ask_to_include(article.title)
20
+ when 'Y' then acc.push(article)
21
+ when 'S' then acc.push(article) and break(acc)
22
+ when 'Q' then break(acc)
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def ask_to_include(title)
30
+ shell.say(%Q(Do you want to publish "#{title}"\n).freeze)
31
+ shell.say(" Y - yes and continue\n S - yes and quit".freeze, :green)
32
+ shell.say(" N - no and continue\n Q - no and quit\n".freeze, :red)
33
+
34
+ shell.ask("\n ", :magenta, limited_to: %w(Y S N Q).freeze)
35
+ end
36
+ end
@@ -0,0 +1,74 @@
1
+ require 'middleman-core/cli'
2
+ require 'middleman-blog/uri_templates'
3
+ require 'middleman-medium_export/articles_filter'
4
+ require 'middleman-medium_export/content'
5
+ require 'middleman-medium_export/publisher'
6
+
7
+ module Middleman
8
+
9
+ module Cli
10
+
11
+ ##
12
+ # This class provides an "medium_export" command for the middleman CLI.
13
+ #
14
+ # @usage bundle exec middleman medium_export
15
+ # @usage bundle exec middleman medium_export --help
16
+ # @usage bundle exec middleman medium_export --mode last
17
+ # @usage bundle exec middleman medium_export --mode interactive
18
+ #
19
+ ##
20
+ class Export < ::Thor::Group
21
+ include Thor::Actions
22
+ include Blog::UriTemplates
23
+
24
+ check_unknown_options!
25
+
26
+ class_option "blog",
27
+ aliases: "-b",
28
+ desc: "The name of the blog to create the post inside (for multi-blog apps, defaults to the only blog in single-blog apps)"
29
+
30
+ class_option "mode",
31
+ aliases: "-m",
32
+ enum: %w[last all interactive],
33
+ default: 'last',
34
+ desc: "Chose which articles should be published"
35
+
36
+
37
+ def medium_export
38
+ articles = blog.data.articles.sort_by { |a| -a.date.to_i }
39
+
40
+ filtered_articles = MediumExport::ArticlesFilter.
41
+ new(articles: articles, shell: shell).
42
+ public_send(options.mode)
43
+
44
+ content = filtered_articles.map do |article|
45
+ MediumExport::Content.new(article: article, template: export_extension.template)
46
+ end
47
+
48
+ MediumExport::Publisher.new(
49
+ api_client: export_extension.api_client, content: content, shell: shell).call
50
+ end
51
+
52
+ private
53
+
54
+ def export_extension
55
+ @export_extension ||= app.extensions[:medium_export]
56
+ end
57
+
58
+ def app
59
+ @app ||= ::Middleman::Application.new
60
+ end
61
+
62
+ def blog
63
+ @blog ||= if options[:blog]
64
+ app.extensions[:blog].find { | k, v | v.options[:name] == options[ :blog ] }.last
65
+ else
66
+ app.extensions[:blog].values.first
67
+ end
68
+ end
69
+
70
+ # Add to CLI
71
+ Base.register(self, 'medium_export', 'medium_export [options]', 'Export one or more articles to middleman')
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,58 @@
1
+ require 'nokogiri'
2
+
3
+ class MediumExport::Content
4
+ Image = Struct.new(:src, :location)
5
+
6
+ attr_reader :article, :template
7
+
8
+ def initialize(article:, template: nil)
9
+ @article = article
10
+ @template = template
11
+ end
12
+
13
+ def html
14
+ @content ||= begin
15
+ template ? body_with_template : article.body
16
+ end
17
+ end
18
+
19
+ def markdown
20
+ @markdown ||= File.read(article.source_file)
21
+ end
22
+
23
+ def title
24
+ article.title
25
+ end
26
+
27
+ def tags
28
+ article.tags
29
+ end
30
+
31
+ def local_images
32
+ @images ||= Nokogiri::HTML(html).search('img').map do |img|
33
+ src = img.attributes['src'].value
34
+ next if src.start_with?('http')
35
+
36
+ Image.new(src, File.join(source_dir, src))
37
+ end.compact
38
+ end
39
+
40
+ private
41
+
42
+ def body_with_template
43
+ template_html = template_context.render(nil, template.path, { locals: {}})
44
+ template.position == :top ? "#{template_html}#{article.body}" : "#{article.body}#{template_html}"
45
+ end
46
+
47
+ def source_dir
48
+ @source_dir ||= app.source_dir
49
+ end
50
+
51
+ def template_context
52
+ @template_context ||= app.generic_template_context
53
+ end
54
+
55
+ def app
56
+ @app ||= article.blog_data.controller.app
57
+ end
58
+ end
@@ -0,0 +1,66 @@
1
+ # Require core library
2
+ require 'middleman-core'
3
+
4
+ # Extension namespace
5
+ class MediumExport < ::Middleman::Extension
6
+ require 'middleman-medium_export/api_client'
7
+ require 'middleman-medium_export/template'
8
+
9
+ ApiTokenMissing = Class.new(ArgumentError)
10
+ InvalidTemplatePosition = Class.new(ArgumentError)
11
+ InvalidPublishStatus = Class.new(ArgumentError)
12
+
13
+ TEMPLATES_POSSITIONS = %w[bottom top].freeze
14
+ PUBLISH_STATUSES = %i[public draft unlisted].freeze
15
+
16
+ option :api_token, nil, 'Medium API Token'
17
+ option :template_path, nil, 'HTML template to append to the end of each article'
18
+ option :template_position, :bottom
19
+ option :publish_status, :draft
20
+ option :tags_field, :tags
21
+
22
+ def initialize(app, options_hash={}, &block)
23
+ super
24
+
25
+ check_api_token!
26
+ check_template_positions!
27
+ check_publish_statuses!
28
+ end
29
+
30
+ def api_client
31
+ @api_client ||= ApiClient.new(options.to_h.slice(:api_token, :publish_status))
32
+ end
33
+
34
+ def template
35
+ return unless options.template_path
36
+
37
+ Template.new(options.to_h.slice(:template_path, :template_position))
38
+ end
39
+
40
+ private
41
+
42
+ def check_api_token!
43
+ return unless options.api_token.to_s.empty?
44
+
45
+ error_msg = "Please, provide an api_token option. To obtain api_token refer to\n" \
46
+ "https://help.medium.com/hc/en-us/articles/213480228-Get-integration-token\n\n"
47
+ raise ApiTokenMissing, error_msg
48
+ end
49
+
50
+ def check_template_positions!
51
+ position = options.template_position.to_s
52
+ return if position.empty? || TEMPLATES_POSSITIONS.include?(position)
53
+
54
+ error_msg = "Invalid template_position: #{options.template_position}.\n" \
55
+ "Possible template positions are: #{TEMPLATES_POSSITIONS.join(", ")}\n\n"
56
+ raise InvalidTemplatePosition, error_msg
57
+ end
58
+
59
+ def check_publish_statuses!
60
+ return if PUBLISH_STATUSES.include?(options.publish_status.to_sym)
61
+
62
+ error_msg = "Invalid publish_status: #{options.publish_status}.\n" \
63
+ "Possible publish statuses are: #{PUBLISH_STATUSES.join(", ")}\n\n"
64
+ raise InvalidPublishStatus, error_msg
65
+ end
66
+ end
@@ -0,0 +1,30 @@
1
+ class MediumExport::Publisher
2
+ attr_reader :content, :shell, :api_client
3
+
4
+ def initialize(content:, shell:, api_client:)
5
+ @content = content
6
+ @shell = shell
7
+ @api_client = api_client
8
+ end
9
+
10
+ def call
11
+ shell.say("Preparing to publish #{content.size} articles") if content.size > 1
12
+
13
+ content.each do |article|
14
+ publish(article)
15
+ shell.say(%Q(Published: "#{article.title}"), :green)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def publish(article)
22
+ updated_html = article.local_images.inject(article.html) do |html, image|
23
+ response = api_client.upload_image(image: File.open(image.location))
24
+ url = response['data']['url'] # it may contain errors {"errors"=>[{"message"=>"Token was invalid.", "code"=>6003}]}
25
+ html.gsub(image.src, url)
26
+ end
27
+
28
+ api_client.publish(content: updated_html, tags: article.tags, title: article.title)
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ class MediumExport::Template
2
+
3
+ attr_reader :path, :position
4
+
5
+ def initialize(template_path:, template_position:)
6
+ @path = template_path
7
+ @position = template_position.to_sym
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: middleman-medium_export
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - TheSmartnik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: middleman-core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: middleman-blog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: httparty
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.16.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.16.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: middleman-cli
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: |
98
+ An extension to export your articles created with
99
+ middleman-blog to medium from a command line.
100
+ email:
101
+ - misharinn@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - MIT-LICENSE
107
+ - README.md
108
+ - lib/middleman-medium_export.rb
109
+ - lib/middleman-medium_export/api_client.rb
110
+ - lib/middleman-medium_export/articles_filter.rb
111
+ - lib/middleman-medium_export/commands/export.rb
112
+ - lib/middleman-medium_export/content.rb
113
+ - lib/middleman-medium_export/extension.rb
114
+ - lib/middleman-medium_export/publisher.rb
115
+ - lib/middleman-medium_export/template.rb
116
+ homepage: https://github.com/TheSmartnik/middleman-medium_export
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.7.6
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: A Middleman extension to export your blogposts to medium
140
+ test_files: []