jekyll-subscriber_only 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|