jekyll-subscriber_only 1.0.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/lib/jekyll/subscriber_only/feed.xml +123 -0
- data/lib/jekyll/subscriber_only/jekyll_feed_patch.rb +20 -0
- data/lib/jekyll/subscriber_only/version.rb +10 -0
- data/lib/jekyll/subscriber_only.rb +137 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eaa1a515b0026b7274bbaf7b2ff047cc8aa24f0910851758badab865248eb1cc
|
4
|
+
data.tar.gz: 69bd7488dc9c1eac6cc3e286318f944e9d71067e07aa3b2c22ca353533142ade
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07e0d6cf897d0f483c9b2f31f492f365411c16bcbb32de0c7ea7a7440944bc7d5ddee88eced2985ef08f11467a5ca373fdd16b4305c933daf461a14bfe6b6535
|
7
|
+
data.tar.gz: 88e4b8c0e72d847c1b71e9781d13bb8cde480f1a70cd1f76f601f5ee4d55028929b13b9bf670596d4db08ea380701dad1942afd589cd32dda971221fccb9277a
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Vitor Manuel de Sousa Pereira
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Jekyll Subscriber Only
|
2
|
+
|
3
|
+
Monetize your Jekyll site with paid subscriber-only content.
|
4
|
+
|
5
|
+
Subscriber Only allows you to monetize your Jekyll site with paid subscriptions. Make selected content available only to paid subscribers by adding a single line to the post's front matter. Leave subscriptions, payment processing and access control to us and focus solely on making great content. Go to https://subscriber-only.com for more details.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
First, you need to [sign up on Subscriber Only](https://app.subscriber-only.com/site/new) -- it's easy, it takes 10 minutes!
|
10
|
+
|
11
|
+
Then, add the `jekyll-subscriber_only` gem to your application's Gemfile, in the `jekyll_plugins` group. Make sure it comes after `jekyll-feed`, if you're using it:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
group :jekyll_plugins do
|
15
|
+
# gem "jekyll-feed", "~> 0.12"
|
16
|
+
gem "jekyll-subscriber_only"
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
And then execute:
|
21
|
+
|
22
|
+
$ bundle install
|
23
|
+
|
24
|
+
Finally, copy your tokens to your site's `_config.yml`:
|
25
|
+
|
26
|
+
```yaml
|
27
|
+
subscriber_only:
|
28
|
+
public_token: MY_PUBLIC_TOKEN
|
29
|
+
secret_token: MY_SECRET_TOKEN
|
30
|
+
```
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
If you want to make a particular post subscriber-only, add `subscriber_only: true` to the post's front matter:
|
35
|
+
|
36
|
+
```yml
|
37
|
+
layout: post
|
38
|
+
title: My premium post
|
39
|
+
subscriber_only: true
|
40
|
+
```
|
41
|
+
|
42
|
+
**That's it!**
|
43
|
+
|
44
|
+
Note that your posts will only be paywalled when building with `JEKYLL_ENV=production`. I.e.
|
45
|
+
|
46
|
+
$ JEKYLL_ENV=production bundle exec jekyll build
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/subscriber-only/jekyll-subscriber_only.
|
51
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
{% comment %}
|
2
|
+
# Copyright (c) 2023 Vitor Manuel de Sousa Pereira
|
3
|
+
# Copyright (c) 2015-present Ben Balter and jekyll-feed contributors
|
4
|
+
# SPDX-License-Identifier: MIT
|
5
|
+
{% endcomment %}
|
6
|
+
|
7
|
+
<?xml version="1.0" encoding="utf-8"?>
|
8
|
+
{% if page.xsl %}
|
9
|
+
<?xml-stylesheet type="text/xml" href="{{ '/feed.xslt.xml' | absolute_url }}"?>
|
10
|
+
{% endif %}
|
11
|
+
<feed xmlns="http://www.w3.org/2005/Atom" {% if site.lang %}xml:lang="{{ site.lang }}"{% endif %}>
|
12
|
+
<generator uri="https://jekyllrb.com/" version="{{ jekyll.version }}">Jekyll</generator>
|
13
|
+
<link href="{{ page.url | absolute_url }}" rel="self" type="application/atom+xml" />
|
14
|
+
<link href="{{ '/' | absolute_url }}" rel="alternate" type="text/html" {% if site.lang %}hreflang="{{ site.lang }}" {% endif %}/>
|
15
|
+
<updated>{{ site.time | date_to_xmlschema }}</updated>
|
16
|
+
<id>{{ page.url | absolute_url | xml_escape }}</id>
|
17
|
+
|
18
|
+
{% assign title = site.title | default: site.name %}
|
19
|
+
{% if page.collection != "posts" %}
|
20
|
+
{% assign collection = page.collection | capitalize %}
|
21
|
+
{% assign title = title | append: " | " | append: collection %}
|
22
|
+
{% endif %}
|
23
|
+
{% if page.category %}
|
24
|
+
{% assign category = page.category | capitalize %}
|
25
|
+
{% assign title = title | append: " | " | append: category %}
|
26
|
+
{% endif %}
|
27
|
+
|
28
|
+
{% if title %}
|
29
|
+
<title type="html">{{ title | smartify | xml_escape }}</title>
|
30
|
+
{% endif %}
|
31
|
+
|
32
|
+
{% if site.description %}
|
33
|
+
<subtitle>{{ site.description | xml_escape }}</subtitle>
|
34
|
+
{% endif %}
|
35
|
+
|
36
|
+
{% if site.author %}
|
37
|
+
<author>
|
38
|
+
<name>{{ site.author.name | default: site.author | xml_escape }}</name>
|
39
|
+
{% if site.author.email %}
|
40
|
+
<email>{{ site.author.email | xml_escape }}</email>
|
41
|
+
{% endif %}
|
42
|
+
{% if site.author.uri %}
|
43
|
+
<uri>{{ site.author.uri | xml_escape }}</uri>
|
44
|
+
{% endif %}
|
45
|
+
</author>
|
46
|
+
{% endif %}
|
47
|
+
|
48
|
+
{% if page.tags %}
|
49
|
+
{% assign posts = site.tags[page.tags] %}
|
50
|
+
{% else %}
|
51
|
+
{% assign posts = site[page.collection] %}
|
52
|
+
{% endif %}
|
53
|
+
{% if page.category %}
|
54
|
+
{% assign posts = posts | where: "categories", page.category %}
|
55
|
+
{% endif %}
|
56
|
+
{% unless site.show_drafts %}
|
57
|
+
{% assign posts = posts | where_exp: "post", "post.draft != true" %}
|
58
|
+
{% endunless %}
|
59
|
+
{% assign posts = posts | sort: "date" | reverse %}
|
60
|
+
{% assign posts_limit = site.feed.posts_limit | default: 10 %}
|
61
|
+
{% for post in posts limit: posts_limit %}
|
62
|
+
<entry{% if post.lang %}{{" "}}xml:lang="{{ post.lang }}"{% endif %}>
|
63
|
+
{% assign post_title = post.title | smartify | strip_html | normalize_whitespace | xml_escape %}
|
64
|
+
|
65
|
+
<title type="html">{{ post_title }}</title>
|
66
|
+
<link href="{{ post.url | absolute_url }}" rel="alternate" type="text/html" title="{{ post_title }}" />
|
67
|
+
<published>{{ post.date | date_to_xmlschema }}</published>
|
68
|
+
<updated>{{ post.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
|
69
|
+
<id>{{ post.id | absolute_url | xml_escape }}</id>
|
70
|
+
{% assign excerpt_only = post.feed.excerpt_only | default: site.feed.excerpt_only %}
|
71
|
+
{% unless excerpt_only %}
|
72
|
+
{% if post["subscriber_only"] == true %}
|
73
|
+
{% assign body = "This post is for paying subscribers only." %}
|
74
|
+
{% else %}
|
75
|
+
{% assign body = post.content %}
|
76
|
+
{% endif %}
|
77
|
+
<content type="html" xml:base="{{ post.url | absolute_url | xml_escape }}"><![CDATA[{{ body | strip }}]]></content>
|
78
|
+
{% endunless %}
|
79
|
+
|
80
|
+
{% assign post_author = post.author | default: post.authors[0] | default: site.author %}
|
81
|
+
{% assign post_author = site.data.authors[post_author] | default: post_author %}
|
82
|
+
{% assign post_author_email = post_author.email | default: nil %}
|
83
|
+
{% assign post_author_uri = post_author.uri | default: nil %}
|
84
|
+
{% assign post_author_name = post_author.name | default: post_author %}
|
85
|
+
|
86
|
+
<author>
|
87
|
+
<name>{{ post_author_name | default: "" | xml_escape }}</name>
|
88
|
+
{% if post_author_email %}
|
89
|
+
<email>{{ post_author_email | xml_escape }}</email>
|
90
|
+
{% endif %}
|
91
|
+
{% if post_author_uri %}
|
92
|
+
<uri>{{ post_author_uri | xml_escape }}</uri>
|
93
|
+
{% endif %}
|
94
|
+
</author>
|
95
|
+
|
96
|
+
{% if post.category %}
|
97
|
+
<category term="{{ post.category | xml_escape }}" />
|
98
|
+
{% elsif post.categories %}
|
99
|
+
{% for category in post.categories %}
|
100
|
+
<category term="{{ category | xml_escape }}" />
|
101
|
+
{% endfor %}
|
102
|
+
{% endif %}
|
103
|
+
|
104
|
+
{% for tag in post.tags %}
|
105
|
+
<category term="{{ tag | xml_escape }}" />
|
106
|
+
{% endfor %}
|
107
|
+
|
108
|
+
{% assign post_summary = post.description | default: post.excerpt %}
|
109
|
+
{% if post_summary and post_summary != empty %}
|
110
|
+
<summary type="html"><![CDATA[{{ post_summary | strip_html | normalize_whitespace }}]]></summary>
|
111
|
+
{% endif %}
|
112
|
+
|
113
|
+
{% assign post_image = post.image.path | default: post.image %}
|
114
|
+
{% if post_image %}
|
115
|
+
{% unless post_image contains "://" %}
|
116
|
+
{% assign post_image = post_image | absolute_url %}
|
117
|
+
{% endunless %}
|
118
|
+
<media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="{{ post_image | xml_escape }}" />
|
119
|
+
<media:content medium="image" url="{{ post_image | xml_escape }}" xmlns:media="http://search.yahoo.com/mrss/" />
|
120
|
+
{% endif %}
|
121
|
+
</entry>
|
122
|
+
{% endfor %}
|
123
|
+
</feed>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Copyright (c) 2023 Vitor Manuel de Sousa Pereira
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
# frozen_string_literal: true
|
5
|
+
|
6
|
+
module Jekyll
|
7
|
+
module SubscriberOnly
|
8
|
+
module JekyllFeedPatch
|
9
|
+
private
|
10
|
+
|
11
|
+
def feed_source_path
|
12
|
+
@feed_source_path ||= File.expand_path("feed.xml", __dir__)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if Object.const_defined?("JekyllFeed::Generator")
|
19
|
+
JekyllFeed::Generator.prepend(Jekyll::SubscriberOnly::JekyllFeedPatch)
|
20
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# Copyright (c) 2023 Vitor Manuel de Sousa Pereira
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
# frozen_string_literal: true
|
5
|
+
|
6
|
+
require "net/http"
|
7
|
+
require "uri"
|
8
|
+
|
9
|
+
require "jekyll"
|
10
|
+
|
11
|
+
require "jekyll/subscriber_only/jekyll_feed_patch"
|
12
|
+
require "jekyll/subscriber_only/version"
|
13
|
+
|
14
|
+
module Jekyll
|
15
|
+
module SubscriberOnly
|
16
|
+
extend self
|
17
|
+
|
18
|
+
Conf = Struct.new(
|
19
|
+
:env, :public_token, :secret_token, :base_url, :script_url, :upload_url,
|
20
|
+
keyword_init: true
|
21
|
+
)
|
22
|
+
|
23
|
+
def configure(site)
|
24
|
+
jekyll_conf = site.config["subscriber_only"]
|
25
|
+
base_url = "https://app.subscriber-only.com"
|
26
|
+
|
27
|
+
if jekyll_conf.nil?
|
28
|
+
die("The site must be configured before you're able to make your " \
|
29
|
+
"posts subscriber-only. Go to #{new_site_url} to configure the " \
|
30
|
+
"site.")
|
31
|
+
end
|
32
|
+
|
33
|
+
env = jekyll_conf["env"]
|
34
|
+
base_url = "http://localhost:3000" if env == "development"
|
35
|
+
|
36
|
+
@conf = Conf.new(
|
37
|
+
env: env,
|
38
|
+
public_token: jekyll_conf["public_token"],
|
39
|
+
secret_token: jekyll_conf["secret_token"],
|
40
|
+
base_url: base_url,
|
41
|
+
script_url: "#{base_url}/so.js",
|
42
|
+
upload_url: URI("#{base_url}/api/v1/posts"),
|
43
|
+
)
|
44
|
+
|
45
|
+
return unless @conf.public_token.nil? || @conf.secret_token.nil?
|
46
|
+
|
47
|
+
die("Public and secret tokens must be configured to enable subscriber-" \
|
48
|
+
"only posts. Go to #{edit_site_url} to get the tokens.")
|
49
|
+
end
|
50
|
+
|
51
|
+
def paywall(doc)
|
52
|
+
return unless subscriber_only?(doc)
|
53
|
+
|
54
|
+
info("Paywalling post \"#{doc['title']}\"")
|
55
|
+
upload_post(doc)
|
56
|
+
replace_content(doc)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def upload_post(doc)
|
62
|
+
res = Net::HTTP.post(
|
63
|
+
@conf.upload_url,
|
64
|
+
{ path: doc.url, title: doc["title"], content: doc.content }.to_json,
|
65
|
+
{
|
66
|
+
Authorization: "Bearer #{@conf.secret_token}",
|
67
|
+
Accept: "application/json",
|
68
|
+
"Content-Type": "application/json",
|
69
|
+
},
|
70
|
+
)
|
71
|
+
|
72
|
+
handle_api_error(res)
|
73
|
+
rescue Errno::ECONNREFUSED
|
74
|
+
die("Failed to connect to #{@conf.base_url}. Please contact us if this " \
|
75
|
+
"issue persists.")
|
76
|
+
rescue JSON::ParserError
|
77
|
+
File.write("parser_error.html", res.body) if @conf.env == "development"
|
78
|
+
die("Failed to get a correct response.")
|
79
|
+
end
|
80
|
+
|
81
|
+
def handle_api_error(res)
|
82
|
+
return if res.is_a?(Net::HTTPSuccess)
|
83
|
+
|
84
|
+
error_details = JSON.parse(res.body)
|
85
|
+
die("Failed to upload \"#{doc['title']}\".\n#{error_details['message']}")
|
86
|
+
end
|
87
|
+
|
88
|
+
def replace_content(doc)
|
89
|
+
doc.output.sub!("</head>", "#{head_html}</head>")
|
90
|
+
doc.output.sub!(doc.content, content_html)
|
91
|
+
end
|
92
|
+
|
93
|
+
def head_html
|
94
|
+
"<script type=\"module\" src=\"#{@conf.script_url}\"></script>"
|
95
|
+
end
|
96
|
+
|
97
|
+
def content_html
|
98
|
+
"<div data-subscriber-only data-public-token=\"#{@conf.public_token}\">" \
|
99
|
+
"</div>"
|
100
|
+
end
|
101
|
+
|
102
|
+
def new_site_url
|
103
|
+
"#{@conf.base_url}/site/edit"
|
104
|
+
end
|
105
|
+
|
106
|
+
def edit_site_url
|
107
|
+
"#{@conf.base_url}/site/edit"
|
108
|
+
end
|
109
|
+
|
110
|
+
def subscriber_only?(doc)
|
111
|
+
Jekyll.env == "production" &&
|
112
|
+
!doc.draft? &&
|
113
|
+
doc.published? &&
|
114
|
+
doc.write? &&
|
115
|
+
doc["subscriber_only"] == true
|
116
|
+
end
|
117
|
+
|
118
|
+
def info(message)
|
119
|
+
Jekyll.logger.info("Subscriber-Only:", message)
|
120
|
+
end
|
121
|
+
|
122
|
+
def die(message)
|
123
|
+
Jekyll.logger.error("Subscriber-Only:", message)
|
124
|
+
# Jekyll's command handler catches all Exceptions so we must be more brusque
|
125
|
+
# when aborting or the user will be drowning in stack traces.
|
126
|
+
exit!
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
Jekyll::Hooks.register(:site, :after_init) do |site|
|
132
|
+
Jekyll::SubscriberOnly.configure(site)
|
133
|
+
end
|
134
|
+
|
135
|
+
Jekyll::Hooks.register(:posts, :post_render) do |doc|
|
136
|
+
Jekyll::SubscriberOnly.paywall(doc)
|
137
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jekyll-subscriber_only
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vitor Manuel de Sousa Pereira
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-04-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jekyll
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.7'
|
27
|
+
description: |2
|
28
|
+
Subscriber Only allows you to monetize your Jekyll site with paid
|
29
|
+
subscriptions. Make selected content available only to paid subscribers by
|
30
|
+
adding a single line to the post's front matter. Leave subscriptions,
|
31
|
+
payment processing and access control to us and focus solely on making great
|
32
|
+
content. Go to https://subscriber-only.com for more details.
|
33
|
+
email: vmsousapereira@gmail.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- LICENSE.txt
|
39
|
+
- README.md
|
40
|
+
- lib/jekyll/subscriber_only.rb
|
41
|
+
- lib/jekyll/subscriber_only/feed.xml
|
42
|
+
- lib/jekyll/subscriber_only/jekyll_feed_patch.rb
|
43
|
+
- lib/jekyll/subscriber_only/version.rb
|
44
|
+
homepage: https://subscriber-only.com
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata:
|
48
|
+
homepage_uri: https://subscriber-only.com
|
49
|
+
rubygems_mfa_required: 'true'
|
50
|
+
source_code_uri: https://github.com/subscriber-only/jekyll-subscriber_only
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 2.7.0
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubygems_version: 3.1.6
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: Monetize your Jekyll site with paid subscriber-only content.
|
70
|
+
test_files: []
|