jekyll-webmention_io 4.1.0 → 4.2.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/jekyll/assets/webmention_loader.js +4 -2
- data/lib/jekyll/caches.rb +98 -0
- data/lib/jekyll/commands/webmention.rb +62 -61
- data/lib/jekyll/config.rb +336 -0
- data/lib/jekyll/generators/compile_js.rb +40 -26
- data/lib/jekyll/generators/gather_webmentions.rb +50 -101
- data/lib/jekyll/generators/queue_webmentions.rb +74 -139
- data/lib/jekyll/network_client.rb +109 -0
- data/lib/jekyll/tags/bookmarks.rb +2 -2
- data/lib/jekyll/tags/count.rb +5 -5
- data/lib/jekyll/tags/likes.rb +2 -2
- data/lib/jekyll/tags/links.rb +2 -2
- data/lib/jekyll/tags/posts.rb +2 -2
- data/lib/jekyll/tags/replies.rb +2 -2
- data/lib/jekyll/tags/reposts.rb +2 -2
- data/lib/jekyll/tags/rsvps.rb +2 -2
- data/lib/jekyll/tags/webmention.rb +31 -44
- data/lib/jekyll/tags/webmention_type.rb +1 -1
- data/lib/jekyll/tags/webmentions.rb +2 -2
- data/lib/jekyll/tags/webmentions_head.rb +21 -17
- data/lib/jekyll/tags/webmentions_js.rb +1 -1
- data/lib/jekyll/templates/bookmarks.html +7 -8
- data/lib/jekyll/templates/likes.html +6 -7
- data/lib/jekyll/templates/links.html +7 -8
- data/lib/jekyll/templates/posts.html +7 -8
- data/lib/jekyll/templates/replies.html +7 -8
- data/lib/jekyll/templates/reposts.html +6 -7
- data/lib/jekyll/templates/rsvps.html +6 -7
- data/lib/jekyll/templates/webmentions.html +10 -9
- data/lib/jekyll/templates.rb +59 -0
- data/lib/jekyll/webmention_io/js_handler.rb +8 -47
- data/lib/jekyll/webmention_io/version.rb +1 -1
- data/lib/jekyll/webmention_io/webmention_item.rb +46 -46
- data/lib/jekyll/webmention_io.rb +26 -549
- data/lib/jekyll/webmention_policy.rb +195 -0
- data/lib/jekyll/webmentions.rb +155 -0
- data/lib/jekyll-webmention_io.rb +2 -2
- metadata +130 -53
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eefc9862bc9a4e40c060cc0cbf7bd549e834e427b710b7340a8bb0b3bf6e20ad
|
|
4
|
+
data.tar.gz: 68506fdb42fb4e79639baedbfabc44c3aeefd2b2e9bbd5db7824a3948b7d04f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4badbaedab8e97b0d310586104c8179a8b0f8952214bc58de83c1df1ff97603ceba6321e18932be494eb5956b3457a197d76092dbe041c1d0ea674d50f6c3264
|
|
7
|
+
data.tar.gz: 505e06d78be628a3962a7885ac731c4f7aaab0b0079a4b40fc3a9c19ed77039c0460e468d8c5ceb3254c84f1b9eba6d30ca39e06743dd18db56fdca4b75de642
|
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
redirects = false;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// Load up any unpublished webmentions on load
|
|
27
|
+
// Load up any unpublished webmentions on load. The API base is injected at
|
|
28
|
+
// build time (see CompileJS#add_config); fall back to webmention.io.
|
|
28
29
|
$script = document.createElement('script');
|
|
29
30
|
$script.async = true;
|
|
30
|
-
$script.src = 'https://webmention.io/api
|
|
31
|
+
$script.src = ( window.JekyllWebmentionIO.api_base || 'https://webmention.io/api' ) +
|
|
32
|
+
'/mentions?' +
|
|
31
33
|
'jsonp=window.JekyllWebmentionIO.processWebmentions&target[]=' +
|
|
32
34
|
targets.join( '&target[]=' );
|
|
33
35
|
document.querySelector('head').appendChild( $script );
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module WebmentionIO
|
|
8
|
+
# The Caches class is a utility service that provides access to the cache files used
|
|
9
|
+
# by this plugin.
|
|
10
|
+
#
|
|
11
|
+
# It is initialized with a config object and creates a folder in the configured cache folder
|
|
12
|
+
# to store the cache files.
|
|
13
|
+
#
|
|
14
|
+
# The class is a singleton and the instance is accessed via the WebmentionIO.caches method.
|
|
15
|
+
class Caches
|
|
16
|
+
def initialize(config)
|
|
17
|
+
@config = config
|
|
18
|
+
|
|
19
|
+
FileUtils.makedirs(@config.cache_folder)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def incoming_webmentions
|
|
23
|
+
@@incoming_webmentions ||= Cache.new(cache_file_path('incoming'))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def outgoing_webmentions
|
|
27
|
+
@@outgoing_webmentions ||= Cache.new(cache_file_path('outgoing'))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def bad_uris
|
|
31
|
+
@@bad_uris ||= Cache.new(cache_file_path('bad_uris'))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def site_lookups
|
|
35
|
+
@@site_lookups ||= Cache.new(cache_file_path('lookups'))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Resets all singleton cache instances by clearing the class variables.
|
|
39
|
+
# This is useful for testing to ensure clean state between tests.
|
|
40
|
+
def self.reset
|
|
41
|
+
@@incoming_webmentions = nil
|
|
42
|
+
@@outgoing_webmentions = nil
|
|
43
|
+
@@bad_uris = nil
|
|
44
|
+
@@site_lookups = nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def cache_file_path(name)
|
|
50
|
+
Jekyll.sanitized_path(@config.cache_folder, "webmention_io_#{name}.yml")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A class that represents a single cache file. The initalizer takes a full path
|
|
54
|
+
# where the cache data will be stored and retrieved.
|
|
55
|
+
#
|
|
56
|
+
# Upon initialization the current contents of the cache are loaded. Writes are not
|
|
57
|
+
# saved until the `write` method is called.
|
|
58
|
+
class Cache
|
|
59
|
+
extend Forwardable
|
|
60
|
+
|
|
61
|
+
attr_reader :path
|
|
62
|
+
|
|
63
|
+
def_delegator :@data, :each
|
|
64
|
+
def_delegator :@data, :key?
|
|
65
|
+
def_delegator :@data, :delete
|
|
66
|
+
def_delegator :@data, :dig
|
|
67
|
+
def_delegator :@data, :[]
|
|
68
|
+
def_delegator :@data, :[]=
|
|
69
|
+
def_delegator :@data, :empty?
|
|
70
|
+
def_delegator :@data, :map
|
|
71
|
+
|
|
72
|
+
def initialize(path)
|
|
73
|
+
# NOTE: This is a deviation from the old code! Previously if the configured
|
|
74
|
+
# cache folder had the word 'webmention' in it, the 'webmention_io' prefix
|
|
75
|
+
# would be removed, but the extra complexity wasn't worth replicating.
|
|
76
|
+
@path = path
|
|
77
|
+
|
|
78
|
+
begin
|
|
79
|
+
@data = SafeYAML.load_file(path)
|
|
80
|
+
rescue StandardError
|
|
81
|
+
@data = {}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def write
|
|
86
|
+
File.open(@path, 'wb') { |f| f.puts YAML.dump(@data) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def clear
|
|
90
|
+
@data = {}
|
|
91
|
+
FileUtils.rm_f(@path)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private_constant :Cache
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'json'
|
|
4
4
|
|
|
5
5
|
module Jekyll
|
|
6
6
|
module WebmentionIO
|
|
@@ -8,8 +8,8 @@ module Jekyll
|
|
|
8
8
|
class WebmentionCommand < Command
|
|
9
9
|
def self.init_with_program(prog)
|
|
10
10
|
prog.command(:webmention) do |c|
|
|
11
|
-
c.syntax
|
|
12
|
-
c.description
|
|
11
|
+
c.syntax 'webmention'
|
|
12
|
+
c.description 'Sends queued webmentions'
|
|
13
13
|
|
|
14
14
|
c.action { |args, options| process args, options }
|
|
15
15
|
end
|
|
@@ -17,68 +17,69 @@ module Jekyll
|
|
|
17
17
|
|
|
18
18
|
def self.process(_args = [], options = {})
|
|
19
19
|
options = configuration_from_options(options)
|
|
20
|
-
|
|
20
|
+
site = Jekyll::Site.new(options)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
WebmentionIO.bootstrap(site)
|
|
23
|
+
|
|
24
|
+
send_webmentions
|
|
25
|
+
end
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
def self.send_webmentions
|
|
28
|
+
WebmentionIO.log 'msg', 'Getting ready to send webmentions (this may take a while).'
|
|
27
29
|
|
|
28
30
|
count = 0
|
|
29
|
-
max_attempts = WebmentionIO.max_attempts
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# get the endpoint
|
|
60
|
-
endpoint = WebmentionIO.get_webmention_endpoint(escaped)
|
|
61
|
-
next unless endpoint
|
|
62
|
-
|
|
63
|
-
# get the response
|
|
64
|
-
response = WebmentionIO.webmention(source, target)
|
|
65
|
-
next unless response
|
|
66
|
-
|
|
67
|
-
# capture JSON responses in case site wants to do anything with them
|
|
68
|
-
begin
|
|
69
|
-
response = JSON.parse response
|
|
70
|
-
rescue JSON::ParserError
|
|
71
|
-
response = ""
|
|
72
|
-
end
|
|
31
|
+
max_attempts = WebmentionIO.config.max_attempts
|
|
32
|
+
outgoing = WebmentionIO.caches.outgoing_webmentions
|
|
33
|
+
|
|
34
|
+
return if outgoing.empty?
|
|
35
|
+
|
|
36
|
+
outgoing.each do |source, targets|
|
|
37
|
+
targets.each do |target, response|
|
|
38
|
+
# skip ones we’ve handled
|
|
39
|
+
next unless response == false || response.instance_of?(Integer)
|
|
40
|
+
|
|
41
|
+
# skip protocol-less links, we'll need to revisit this again later
|
|
42
|
+
idx = target.index('//')
|
|
43
|
+
next if idx.nil? || idx.zero?
|
|
44
|
+
|
|
45
|
+
# produce an escaped version of the target (in case of special
|
|
46
|
+
# characters, etc).
|
|
47
|
+
escaped = URI::Parser.new.escape(target);
|
|
48
|
+
|
|
49
|
+
# skip bad URLs
|
|
50
|
+
next unless WebmentionIO.policy.uri_ok?(escaped)
|
|
51
|
+
|
|
52
|
+
# give up if we've attempted this too many times
|
|
53
|
+
response = (response || 0) + 1
|
|
54
|
+
|
|
55
|
+
if !max_attempts.nil? && response > max_attempts
|
|
56
|
+
outgoing[source][target] = ''
|
|
57
|
+
WebmentionIO.log 'msg', "Giving up sending from #{source} to #{target}."
|
|
58
|
+
next
|
|
59
|
+
else
|
|
73
60
|
outgoing[source][target] = response
|
|
74
|
-
count += 1
|
|
75
61
|
end
|
|
62
|
+
|
|
63
|
+
# get the response
|
|
64
|
+
response = WebmentionIO.webmentions.send_webmention(source, target)
|
|
65
|
+
next unless response
|
|
66
|
+
|
|
67
|
+
# capture JSON responses in case site wants to do anything with them
|
|
68
|
+
begin
|
|
69
|
+
response = JSON.parse response
|
|
70
|
+
rescue JSON::ParserError
|
|
71
|
+
response = ''
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
outgoing[source][target] = response
|
|
75
|
+
count += 1
|
|
76
76
|
end
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
outgoing.write
|
|
80
|
+
WebmentionIO.log 'msg', "#{count} webmentions sent."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module WebmentionIO
|
|
7
|
+
class Config
|
|
8
|
+
module HtmlProofer
|
|
9
|
+
NONE = 'none'
|
|
10
|
+
ALL = 'all'
|
|
11
|
+
TEMPLATES = 'templates'
|
|
12
|
+
|
|
13
|
+
def self.get_const(val)
|
|
14
|
+
constants.find { |sym| const_get(sym) == val }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module UriPolicy
|
|
19
|
+
BAN = 'ban'
|
|
20
|
+
IGNORE = 'ignore'
|
|
21
|
+
RETRY = 'retry'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
TIMEFRAMES = {
|
|
25
|
+
'last_week' => 'weekly',
|
|
26
|
+
'last_month' => 'monthly',
|
|
27
|
+
'last_year' => 'yearly',
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
attr_accessor :html_proofer_ignore, :max_attempts,
|
|
31
|
+
:templates, :bad_uri_policy, :throttle_lookups, :cache_folder,
|
|
32
|
+
:legacy_domains, :pause_lookups, :site_url, :syndication, :js,
|
|
33
|
+
:username, :debug, :api_url
|
|
34
|
+
|
|
35
|
+
# The scheme://host[:port] origin and bare host of the configured API,
|
|
36
|
+
# derived from api_url. Used to build the service links emitted into the
|
|
37
|
+
# page head (and the JS) so they track a custom endpoint instead of being
|
|
38
|
+
# hard-coded to webmention.io.
|
|
39
|
+
attr_reader :api_origin, :api_host
|
|
40
|
+
|
|
41
|
+
# The default base URL for the Webmention.io API. Exposed as a config key
|
|
42
|
+
# so the endpoint can be pointed elsewhere (e.g. a local stand-in during
|
|
43
|
+
# integration testing) instead of being hard-coded in the network layer.
|
|
44
|
+
DEFAULT_API_URL = 'https://webmention.io/api'
|
|
45
|
+
|
|
46
|
+
# Resolves a webmention API URL to a URI, falling back to the default
|
|
47
|
+
# endpoint when the configured value has no host.
|
|
48
|
+
def self.api_uri(url)
|
|
49
|
+
uri = URI.parse(url)
|
|
50
|
+
uri.host ? uri : URI.parse(DEFAULT_API_URL)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The host of a parsed API URI, plus the port when it isn't the scheme's
|
|
54
|
+
# default (so custom/local endpoints still match the full URL).
|
|
55
|
+
def self.authority(uri)
|
|
56
|
+
uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def initialize(site = nil)
|
|
60
|
+
@site = site
|
|
61
|
+
|
|
62
|
+
if site.nil?
|
|
63
|
+
parse
|
|
64
|
+
else
|
|
65
|
+
parse(@site.config['webmentions'], @site.config['url'].to_s, @site.config['baseurl'].to_s)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def parse(config = nil, site_url = '', base_url = '')
|
|
70
|
+
config ||= {}
|
|
71
|
+
|
|
72
|
+
@site_url = site_url
|
|
73
|
+
@username = config['username']
|
|
74
|
+
@debug = config['debug']
|
|
75
|
+
@api_url = config['api_url'] || DEFAULT_API_URL
|
|
76
|
+
api_uri = self.class.api_uri(@api_url)
|
|
77
|
+
@api_host = api_uri.host
|
|
78
|
+
@api_origin = "#{api_uri.scheme}://#{self.class.authority(api_uri)}"
|
|
79
|
+
|
|
80
|
+
@pause_lookups =
|
|
81
|
+
if !@site.nil? && @site.config['serving']
|
|
82
|
+
WebmentionIO.log 'msg', 'Webmentions won’t be gathered when running `jekyll serve`.'
|
|
83
|
+
|
|
84
|
+
true
|
|
85
|
+
elsif !@site.nil? && @site_url.include?('localhost')
|
|
86
|
+
WebmentionIO.log 'msg', 'Webmentions won’t be gathered on localhost.'
|
|
87
|
+
|
|
88
|
+
true
|
|
89
|
+
else
|
|
90
|
+
config['pause_lookups']
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@cache_folder = config['cache_folder'] || '.jekyll-cache'
|
|
94
|
+
@cache_folder = @site.in_source_dir(@cache_folder) if !@site.nil?
|
|
95
|
+
|
|
96
|
+
@pages = config['pages']
|
|
97
|
+
@collections = config['collections'] || {}
|
|
98
|
+
@templates = config['templates'] || {}
|
|
99
|
+
|
|
100
|
+
@js = JsConfig.new(base_url, config['js'] || false)
|
|
101
|
+
|
|
102
|
+
@html_proofer_ignore = HtmlProofer.get_const(
|
|
103
|
+
config['html_proofer_ignore'] ||
|
|
104
|
+
(config['html_proofer'] ? 'templates' : nil) ||
|
|
105
|
+
'none'
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
@max_attempts = config['max_attempts']
|
|
109
|
+
|
|
110
|
+
@bad_uri_policy = BadUriPolicy.new(config)
|
|
111
|
+
|
|
112
|
+
@throttle_lookups = config['throttle_lookups'] || {}
|
|
113
|
+
|
|
114
|
+
@legacy_domains = config['legacy_domains'] || []
|
|
115
|
+
|
|
116
|
+
@syndication = (config['syndication'] || {}).transform_values { |entry| SyndicationRule.new(entry) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The next lookup date has to be before this date to be allowed to
|
|
120
|
+
# request webmentions again.
|
|
121
|
+
def last_lookup_threshold(date)
|
|
122
|
+
age = get_timeframe_from_date(date)
|
|
123
|
+
|
|
124
|
+
throttle = @throttle_lookups[age]
|
|
125
|
+
|
|
126
|
+
throttle.nil? ? nil : get_date_from_string(throttle)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Given a webmention endpoint, find the corresponding syndication rule
|
|
130
|
+
# Yes, this is a kind of reverse lookup so we can figure out of a given
|
|
131
|
+
# queued webmention was a result of a syndication rule.
|
|
132
|
+
def syndication_rule_for_uri(uri)
|
|
133
|
+
@syndication.values.detect { |rule| rule.endpoint == uri }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Based on the specified configuration, return the list of documents for
|
|
137
|
+
# the site that should be processed.
|
|
138
|
+
def documents
|
|
139
|
+
documents = @site.posts.docs.clone
|
|
140
|
+
|
|
141
|
+
if @pages == true
|
|
142
|
+
WebmentionIO.log 'info', 'Including site pages.'
|
|
143
|
+
documents.concat @site.pages.clone
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if @collections.empty?
|
|
147
|
+
WebmentionIO.log 'info', 'Adding collections.'
|
|
148
|
+
|
|
149
|
+
@site.collections.each do |name, collection|
|
|
150
|
+
# skip _posts
|
|
151
|
+
next if name == 'posts'
|
|
152
|
+
|
|
153
|
+
if collections.include?(name)
|
|
154
|
+
documents.concat collection.docs.clone
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
documents
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def collections
|
|
163
|
+
@site.collections
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
class BadUriPolicy
|
|
167
|
+
BadUriPolicyEntry = Struct.new(:policy, :max_attempts, :retry_delay)
|
|
168
|
+
|
|
169
|
+
attr_reader :whitelist, :blacklist
|
|
170
|
+
|
|
171
|
+
def initialize(site_config)
|
|
172
|
+
@bad_uri_policy = site_config['bad_uri_policy'] || {}
|
|
173
|
+
|
|
174
|
+
@bad_uri_policy['whitelist'] ||= []
|
|
175
|
+
@bad_uri_policy['blacklist'] ||= []
|
|
176
|
+
|
|
177
|
+
# We always want to collect webmentions from the configured API host,
|
|
178
|
+
# so we explicitly whitelist it. This way a transient service outage
|
|
179
|
+
# won't get the endpoint banned by the bad-URI policy. Derived from the
|
|
180
|
+
# configured api_url (default webmention.io) so a custom endpoint gets
|
|
181
|
+
# the same protection.
|
|
182
|
+
@bad_uri_policy['whitelist'].insert(-1, api_host_pattern(site_config))
|
|
183
|
+
|
|
184
|
+
@whitelist = @bad_uri_policy['whitelist'].map { |expr| Regexp.new(expr) }
|
|
185
|
+
@blacklist = @bad_uri_policy['blacklist'].map { |expr| Regexp.new(expr) }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def set_policy(state, policy, max_attempts = nil, retry_delay = nil)
|
|
189
|
+
@bad_uri_policy[state] = {
|
|
190
|
+
'policy' => policy,
|
|
191
|
+
'max_attempts' => max_attempts,
|
|
192
|
+
'retry_delay' => retry_delay
|
|
193
|
+
}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Given the provided state value (see WebmentionPolicy::State),
|
|
197
|
+
# retrieve the policy entry. If no entry exists, return a new default
|
|
198
|
+
# entry that indicates unlimited retries.
|
|
199
|
+
def for_state(state)
|
|
200
|
+
default_policy = { 'policy' => UriPolicy::RETRY }
|
|
201
|
+
|
|
202
|
+
# Retrieve the policy entry, the default entry, or the canned default
|
|
203
|
+
policy_entry = @bad_uri_policy[state] || @bad_uri_policy['default'] || default_policy
|
|
204
|
+
|
|
205
|
+
# Convert shorthand entry to full policy record
|
|
206
|
+
if policy_entry.instance_of? String
|
|
207
|
+
policy_entry = { 'policy' => policy_entry }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
if policy_entry['policy'] == UriPolicy::RETRY && !policy_entry.key?('retry_delay')
|
|
211
|
+
# If this is a retry policy and no delay is set, set up the default
|
|
212
|
+
# delay policy. This inherits from the legacy cache_bad_uris_for
|
|
213
|
+
# setting to enable backward compatibility with older configurations.
|
|
214
|
+
#
|
|
215
|
+
# We do this here to make the rule enforcement logic a little tidier.
|
|
216
|
+
|
|
217
|
+
policy_entry['retry_delay'] = [(@bad_uri_policy['cache_bad_uris_for'] || 1) * 24]
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Now finally convert into a proper policy entry structure
|
|
221
|
+
BadUriPolicyEntry.new(
|
|
222
|
+
policy_entry['policy'],
|
|
223
|
+
policy_entry['max_attempts'],
|
|
224
|
+
policy_entry['retry_delay']
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
private
|
|
229
|
+
|
|
230
|
+
# Builds an anchored host pattern for the configured webmention API so it
|
|
231
|
+
# is always exempt from the bad-URI policy, mirroring the api_url read in
|
|
232
|
+
# Config#parse. A non-default port is included so custom/local endpoints
|
|
233
|
+
# still match the full URL the network layer checks.
|
|
234
|
+
def api_host_pattern(site_config)
|
|
235
|
+
uri = Config.api_uri(site_config['api_url'] || DEFAULT_API_URL)
|
|
236
|
+
"^https?://#{Regexp.escape(Config.authority(uri))}/"
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
class SyndicationRule
|
|
241
|
+
attr_reader :endpoint, :response_mapping, :shorturl, :fragment
|
|
242
|
+
|
|
243
|
+
def initialize(entry)
|
|
244
|
+
@endpoint = entry['endpoint']
|
|
245
|
+
@shorturl = entry['shorturl']
|
|
246
|
+
@fragment = entry['fragment']
|
|
247
|
+
@response_mapping = {}
|
|
248
|
+
|
|
249
|
+
return unless entry.key?('response_mapping')
|
|
250
|
+
|
|
251
|
+
entry['response_mapping'].each do |key, pattern|
|
|
252
|
+
@response_mapping[key] = JsonPath.new(pattern)
|
|
253
|
+
rescue StandardError => e
|
|
254
|
+
WebmentionIO.log 'error', "Ignoring invalid JsonPath expression #{pattern}: #{e}"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
class JsConfig
|
|
260
|
+
attr_reader :destination, :resource_name, :resource_url
|
|
261
|
+
|
|
262
|
+
def initialize(base_url, js_config)
|
|
263
|
+
if js_config == false
|
|
264
|
+
@disabled = true
|
|
265
|
+
return
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
@disabled = false
|
|
269
|
+
@destination = js_config['destination'] || 'js'
|
|
270
|
+
|
|
271
|
+
# rubocop:disable Style/RedundantCondition
|
|
272
|
+
# Apparently this cop is broken...
|
|
273
|
+
@deploy = js_config['deploy'].nil? ? true : js_config['deploy']
|
|
274
|
+
@source = js_config['source'].nil? ? true : js_config['source']
|
|
275
|
+
@uglify = js_config['uglify'].nil? ? true : js_config['uglify']
|
|
276
|
+
# rubocop:enable Style/RedundantCondition
|
|
277
|
+
|
|
278
|
+
@resource_name = 'JekyllWebmentionIO.js'
|
|
279
|
+
@resource_url = File.join('', base_url, @destination, @resource_name)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def disabled?; @disabled; end
|
|
283
|
+
|
|
284
|
+
def source?; @source; end
|
|
285
|
+
|
|
286
|
+
def deploy?; @deploy; end
|
|
287
|
+
|
|
288
|
+
def uglify?; @uglify; end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private
|
|
292
|
+
|
|
293
|
+
def get_timeframe_from_date(time)
|
|
294
|
+
date = time.to_date
|
|
295
|
+
|
|
296
|
+
timeframe = nil
|
|
297
|
+
|
|
298
|
+
TIMEFRAMES.each do |key, value|
|
|
299
|
+
if date.to_date > get_date_from_string(value)
|
|
300
|
+
timeframe = key
|
|
301
|
+
break
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
timeframe ||= 'older'
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def get_date_from_string(text)
|
|
309
|
+
today = Date.today
|
|
310
|
+
pattern = /every\s(?:(\d+)\s)?(day|week|month|year)s?/
|
|
311
|
+
matches = text.match(pattern)
|
|
312
|
+
|
|
313
|
+
unless matches
|
|
314
|
+
text = if text == 'daily'
|
|
315
|
+
'every 1 day'
|
|
316
|
+
else
|
|
317
|
+
"every 1 #{text.sub('ly', '')}"
|
|
318
|
+
end
|
|
319
|
+
matches = text.match(pattern)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
n = matches[1] ? matches[1].to_i : 1
|
|
323
|
+
unit = matches[2]
|
|
324
|
+
|
|
325
|
+
# weeks aren't natively supported in Ruby
|
|
326
|
+
if unit == 'week'
|
|
327
|
+
n *= 7
|
|
328
|
+
unit = 'day'
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# dynamic method call
|
|
332
|
+
today.send "prev_#{unit}", n
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|