jekyll-webmention_io 3.3.7 → 4.1.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/JekyllWebmentionIO.js +2 -2
- data/lib/jekyll/assets/_utils.js +2 -2
- data/lib/jekyll/commands/webmention.rb +16 -8
- data/lib/jekyll/generators/compile_js.rb +1 -2
- data/lib/jekyll/generators/queue_webmentions.rb +161 -17
- data/lib/jekyll/tags/count.rb +1 -1
- data/lib/jekyll/tags/webmention.rb +16 -8
- data/lib/jekyll/tags/webmention_type.rb +2 -2
- data/lib/jekyll/templates/bookmarks.html +54 -15
- data/lib/jekyll/templates/likes.html +38 -9
- data/lib/jekyll/templates/links.html +54 -15
- data/lib/jekyll/templates/posts.html +55 -8
- data/lib/jekyll/templates/replies.html +52 -18
- data/lib/jekyll/templates/reposts.html +37 -8
- data/lib/jekyll/templates/rsvps.html +37 -10
- data/lib/jekyll/templates/webmentions.html +36 -17
- data/lib/jekyll/webmention_io/version.rb +1 -1
- data/lib/jekyll/webmention_io/webmention_item.rb +5 -21
- data/lib/jekyll/webmention_io.rb +209 -19
- metadata +28 -8
data/lib/jekyll/webmention_io.rb
CHANGED
@@ -15,16 +15,30 @@ require "json"
|
|
15
15
|
require "net/http"
|
16
16
|
require "uri"
|
17
17
|
require "openssl"
|
18
|
-
require "
|
18
|
+
require "active_support"
|
19
19
|
require "indieweb/endpoints"
|
20
20
|
require "webmention"
|
21
21
|
|
22
22
|
module Jekyll
|
23
23
|
module WebmentionIO
|
24
|
+
module UriState
|
25
|
+
UNSUPPORTED = "unsupported"
|
26
|
+
ERROR = "error"
|
27
|
+
FAILURE = "failure"
|
28
|
+
SUCCESS = "success"
|
29
|
+
end
|
30
|
+
|
31
|
+
module UriPolicy
|
32
|
+
BAN = "ban"
|
33
|
+
IGNORE = "ignore"
|
34
|
+
RETRY = "retry"
|
35
|
+
end
|
36
|
+
|
24
37
|
class << self
|
25
38
|
# define simple getters and setters
|
26
39
|
attr_reader :config, :jekyll_config, :cache_files, :cache_folder,
|
27
|
-
:file_prefix, :types, :supported_templates, :js_handler
|
40
|
+
:file_prefix, :types, :supported_templates, :js_handler,
|
41
|
+
:uri_whitelist, :uri_blacklist
|
28
42
|
attr_writer :api_suffix
|
29
43
|
end
|
30
44
|
|
@@ -69,6 +83,24 @@ module Jekyll
|
|
69
83
|
end
|
70
84
|
|
71
85
|
@js_handler = WebmentionIO::JSHandler.new(site)
|
86
|
+
|
87
|
+
@uri_whitelist = @config
|
88
|
+
.fetch("bad_uri_policy", {})
|
89
|
+
.fetch("whitelist", [])
|
90
|
+
.clone
|
91
|
+
.insert(-1, "^https?://webmention.io/")
|
92
|
+
.map { |expr| Regexp.new(expr) }
|
93
|
+
|
94
|
+
@uri_blacklist = @config
|
95
|
+
.fetch("bad_uri_policy", {})
|
96
|
+
.fetch("blacklist", [])
|
97
|
+
.map { |expr| Regexp.new(expr) }
|
98
|
+
|
99
|
+
# Backward compatibility config for html_proofer setting
|
100
|
+
|
101
|
+
if @config['html_proofer'] == true
|
102
|
+
@config['html_proofer_ignore'] = "templates"
|
103
|
+
end
|
72
104
|
end
|
73
105
|
|
74
106
|
# Setter
|
@@ -81,6 +113,10 @@ module Jekyll
|
|
81
113
|
Jekyll.sanitized_path(@cache_folder, "#{@file_prefix}#{filename}")
|
82
114
|
end
|
83
115
|
|
116
|
+
def self.max_attempts()
|
117
|
+
@config.dig("max_attempts")
|
118
|
+
end
|
119
|
+
|
84
120
|
def self.get_cache_file_path(key)
|
85
121
|
@cache_files[key] || false
|
86
122
|
end
|
@@ -213,11 +249,11 @@ module Jekyll
|
|
213
249
|
endpoint = IndieWeb::Endpoints.get(uri)[:webmention]
|
214
250
|
unless endpoint
|
215
251
|
log("info", "Could not find a webmention endpoint at #{uri}")
|
216
|
-
|
252
|
+
update_uri_cache(uri, UriState::UNSUPPORTED)
|
217
253
|
end
|
218
254
|
rescue StandardError => e
|
219
255
|
log "info", "Endpoint lookup failed for #{uri}: #{e.message}"
|
220
|
-
|
256
|
+
update_uri_cache(uri, UriState::FAILURE)
|
221
257
|
endpoint = false
|
222
258
|
end
|
223
259
|
endpoint
|
@@ -231,10 +267,24 @@ module Jekyll
|
|
231
267
|
case response.code
|
232
268
|
when 200, 201, 202
|
233
269
|
log "info", "Webmention successful!"
|
270
|
+
update_uri_cache(target, UriState::SUCCESS)
|
234
271
|
response.body
|
235
272
|
else
|
236
273
|
log "info", response.inspect
|
237
274
|
log "info", "Webmention failed, but will remain queued for next time"
|
275
|
+
|
276
|
+
if response.body
|
277
|
+
begin
|
278
|
+
body = JSON.parse(response.body)
|
279
|
+
|
280
|
+
if body.key? "error"
|
281
|
+
log "msg", "Endpoint returned error: #{body['error']}"
|
282
|
+
end
|
283
|
+
rescue
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
update_uri_cache(target, UriState::ERROR)
|
238
288
|
false
|
239
289
|
end
|
240
290
|
end
|
@@ -260,7 +310,8 @@ module Jekyll
|
|
260
310
|
end
|
261
311
|
|
262
312
|
def self.html_templates
|
263
|
-
|
313
|
+
setting = @config['html_proofer_ignore']
|
314
|
+
proofer = if setting == "all" || setting == "templates"
|
264
315
|
' data-proofer-ignore'
|
265
316
|
else
|
266
317
|
''
|
@@ -291,12 +342,12 @@ module Jekyll
|
|
291
342
|
redirect_to = redirect_to.relative? ? "#{original_uri.scheme}://#{original_uri.host}" + redirect_to.to_s : redirect_to.to_s
|
292
343
|
return get_uri_source(redirect_to, redirect_limit - 1, original_uri)
|
293
344
|
else
|
294
|
-
|
345
|
+
update_uri_cache(uri, UriState::FAILURE)
|
295
346
|
return false
|
296
347
|
end
|
297
348
|
else
|
298
349
|
log("warn", "too many redirects for #{original_uri}") if original_uri
|
299
|
-
|
350
|
+
update_uri_cache(uri, UriState::FAILURE)
|
300
351
|
return false
|
301
352
|
end
|
302
353
|
end
|
@@ -347,37 +398,176 @@ module Jekyll
|
|
347
398
|
return response
|
348
399
|
rescue *EXCEPTIONS => e
|
349
400
|
log "warn", "Got an error checking #{uri}: #{e}"
|
350
|
-
|
401
|
+
update_uri_cache(uri, UriState::FAILURE)
|
351
402
|
return false
|
352
403
|
end
|
353
404
|
end
|
354
405
|
|
355
|
-
#
|
356
|
-
|
406
|
+
# Given the provided state value (see UriState), retrieve the policy
|
407
|
+
# entry. If no entry exists, return a new default entry that
|
408
|
+
# indicates unlimited retries.
|
409
|
+
def self.get_bad_uri_policy_entry(state)
|
410
|
+
settings = @config.fetch("bad_uri_policy", {})
|
411
|
+
|
412
|
+
default_policy = { "policy" => UriPolicy::RETRY }
|
413
|
+
policy_entry = nil
|
414
|
+
|
415
|
+
# Retrieve the policy entry, the default entry, or the canned default
|
416
|
+
policy_entry = settings.fetch(state) {
|
417
|
+
settings.fetch("default", default_policy)
|
418
|
+
}
|
419
|
+
|
420
|
+
# Convert shorthand entry to full policy record
|
421
|
+
if policy_entry.instance_of? String
|
422
|
+
policy_entry = { "policy" => policy_entry }
|
423
|
+
end
|
424
|
+
|
425
|
+
if policy_entry["policy"] == UriPolicy::RETRY and ! policy_entry.key? "retry_delay"
|
426
|
+
# If this is a retry policy and no delay is set, set up the default
|
427
|
+
# delay policy. This inherits from the legacy cache_bad_uris_for
|
428
|
+
# setting to enable backward compatibility with older configurations.
|
429
|
+
#
|
430
|
+
# We do this here to make the rule enforcement logic a little tidier.
|
431
|
+
|
432
|
+
policy_entry["retry_delay"] = [ @config.fetch("cache_bad_uris_for", 1) * 24 ]
|
433
|
+
end
|
434
|
+
|
435
|
+
return policy_entry
|
436
|
+
end
|
437
|
+
|
438
|
+
# Retrieve the bad_uris cache entry for the given URI. This method
|
439
|
+
# takes the cache and a URI instance (i.e. parsing must already be done).
|
440
|
+
#
|
441
|
+
# If the URI has no entry in the cache, returns nil and *not* a default
|
442
|
+
# entry.
|
443
|
+
def self.get_bad_uri_cache_entry(bad_uris, uri)
|
444
|
+
return nil if ! bad_uris.key? uri.host
|
445
|
+
|
446
|
+
entry = bad_uris[uri.host].clone
|
447
|
+
|
448
|
+
if entry.instance_of? String
|
449
|
+
# Older version of the bad URL cache, convert to new format with some
|
450
|
+
# "sensible" defaults.
|
451
|
+
|
452
|
+
entry = {
|
453
|
+
"state" => UriState::UNSUPPORTED,
|
454
|
+
"last_checked" => DateTime.parse(entry).to_time,
|
455
|
+
"attempts" => 1
|
456
|
+
}
|
457
|
+
else
|
458
|
+
# Otherwise, parse the check time into a real Time object before
|
459
|
+
# returning the entry.
|
460
|
+
#
|
461
|
+
# We convert to a Time object so we can do arithmetic on it later.
|
462
|
+
|
463
|
+
entry["last_checked"] = DateTime.parse(entry["last_checked"]).to_time
|
464
|
+
end
|
465
|
+
|
466
|
+
return entry
|
467
|
+
end
|
468
|
+
|
469
|
+
# Update the URI cache for this entry.
|
470
|
+
#
|
471
|
+
# If the state is UriState.SUCCESS or the URI is whitelisted or
|
472
|
+
# blacklisted, we delete any existing entries since no policy will
|
473
|
+
# apply. This ensures we reset the policy state when a webmention
|
474
|
+
# succeeds.
|
475
|
+
#
|
476
|
+
# Otherwise, we either create or update an entry for the URI, recording
|
477
|
+
# the state and the current attempt counter.
|
478
|
+
def self.update_uri_cache(uri, state)
|
357
479
|
uri = URI::Parser.new.parse(uri.to_s)
|
358
|
-
|
359
|
-
return if uri.host == "webmention.io"
|
480
|
+
uri_str = uri.to_s
|
360
481
|
|
361
482
|
cache_file = @cache_files["bad_uris"]
|
362
483
|
bad_uris = load_yaml(cache_file)
|
363
|
-
|
484
|
+
|
485
|
+
if state == UriState::SUCCESS or
|
486
|
+
@uri_whitelist.any? { |expr| expr.match uri_str } or
|
487
|
+
@uri_blacklist.any? { |expr| expr.match uri_str }
|
488
|
+
|
489
|
+
return if bad_uris.delete(uri.host).nil?
|
490
|
+
else
|
491
|
+
old_entry = get_bad_uri_cache_entry(bad_uris, uri) || {}
|
492
|
+
|
493
|
+
bad_uris[uri.host] = {
|
494
|
+
"state" => state,
|
495
|
+
"attempts" => old_entry.fetch("attempts", 0) + 1,
|
496
|
+
"last_checked" => Time.now.to_s
|
497
|
+
}
|
498
|
+
end
|
499
|
+
|
364
500
|
dump_yaml(cache_file, bad_uris)
|
365
501
|
end
|
366
502
|
|
503
|
+
# Check if we should attempt to send a webmention to the given URI based
|
504
|
+
# on the error handling policy and the last attempt.
|
367
505
|
def self.uri_ok?(uri)
|
368
506
|
uri = URI::Parser.new.parse(uri.to_s)
|
369
507
|
now = Time.now.to_s
|
508
|
+
uri_str = uri.to_s
|
509
|
+
|
510
|
+
# If the URI is whitelisted, it's always ok!
|
511
|
+
return true if @uri_whitelist.any? { |expr| expr.match uri_str }
|
512
|
+
|
513
|
+
# If the URI is blacklisted, it's never ok!
|
514
|
+
return false if @uri_blacklist.any? { |expr| expr.match uri_str }
|
515
|
+
|
370
516
|
bad_uris = load_yaml(@cache_files["bad_uris"])
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
517
|
+
entry = get_bad_uri_cache_entry(bad_uris, uri)
|
518
|
+
|
519
|
+
# If the entry isn't in our cache yet, then it's ok.
|
520
|
+
return true if entry.nil?
|
521
|
+
|
522
|
+
# Okay, the last time we tried to send a webmention to this URI it
|
523
|
+
# failed, so depending on what happened and the policy, we need to
|
524
|
+
# decide what to do.
|
525
|
+
#
|
526
|
+
# First pull the retry policy given the type of the last error for the URI
|
527
|
+
policy_entry = get_bad_uri_policy_entry(entry["state"])
|
528
|
+
policy = policy_entry["policy"]
|
529
|
+
|
530
|
+
if policy == UriPolicy::BAN
|
531
|
+
return false
|
532
|
+
elsif policy == UriPolicy::IGNORE
|
533
|
+
return true
|
534
|
+
elsif policy == UriPolicy::RETRY
|
535
|
+
now = Time.now
|
536
|
+
|
537
|
+
attempts = entry["attempts"]
|
538
|
+
max_attempts = policy_entry["max_attempts"]
|
539
|
+
|
540
|
+
if ! max_attempts.nil? and attempts >= max_attempts
|
541
|
+
# If there's a retry limit and we've hit it, URI is not ok.
|
542
|
+
log "msg", "Skipping #{uri}, attempted #{attempts} times and max is #{max_attempts}"
|
543
|
+
|
544
|
+
return false
|
545
|
+
end
|
546
|
+
|
547
|
+
retry_delay = policy_entry["retry_delay"]
|
548
|
+
|
549
|
+
# Sneaky trick. By clamping to the array length, the last entry in
|
550
|
+
# the retry_delay list is used for all remaining retries.
|
551
|
+
delay = retry_delay[(attempts - 1).clamp(0, retry_delay.length - 1)]
|
552
|
+
|
553
|
+
recheck_at = (entry["last_checked"] + delay * 3600)
|
554
|
+
|
555
|
+
if recheck_at.to_r > now.to_r
|
556
|
+
log "msg", "Skipping #{uri}, next attempt will happen after #{recheck_at}"
|
557
|
+
|
558
|
+
return false
|
559
|
+
end
|
560
|
+
else
|
561
|
+
log "error", "Invalid bad URI policy type: #{policy}"
|
376
562
|
end
|
563
|
+
|
377
564
|
return true
|
378
565
|
end
|
379
566
|
|
380
|
-
private_class_method :get_http_response,
|
567
|
+
private_class_method :get_http_response,
|
568
|
+
:get_bad_uri_policy_entry,
|
569
|
+
:get_bad_uri_cache_entry,
|
570
|
+
:update_uri_cache
|
381
571
|
end
|
382
572
|
end
|
383
573
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-webmention_io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Gustafson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: jekyll
|
@@ -65,19 +65,25 @@ dependencies:
|
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: '4.0'
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
|
-
name:
|
68
|
+
name: activesupport
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
71
|
- - "~>"
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version: '0
|
73
|
+
version: '7.0'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 7.0.4.3
|
74
77
|
type: :runtime
|
75
78
|
prerelease: false
|
76
79
|
version_requirements: !ruby/object:Gem::Requirement
|
77
80
|
requirements:
|
78
81
|
- - "~>"
|
79
82
|
- !ruby/object:Gem::Version
|
80
|
-
version: '0
|
83
|
+
version: '7.0'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 7.0.4.3
|
81
87
|
- !ruby/object:Gem::Dependency
|
82
88
|
name: htmlbeautifier
|
83
89
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,6 +126,20 @@ dependencies:
|
|
120
126
|
- - "~>"
|
121
127
|
- !ruby/object:Gem::Version
|
122
128
|
version: '7.0'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: jsonpath
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 1.0.1
|
136
|
+
type: :runtime
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 1.0.1
|
123
143
|
- !ruby/object:Gem::Dependency
|
124
144
|
name: bundler
|
125
145
|
requirement: !ruby/object:Gem::Requirement
|
@@ -154,14 +174,14 @@ dependencies:
|
|
154
174
|
requirements:
|
155
175
|
- - "~>"
|
156
176
|
- !ruby/object:Gem::Version
|
157
|
-
version: '
|
177
|
+
version: '13.0'
|
158
178
|
type: :development
|
159
179
|
prerelease: false
|
160
180
|
version_requirements: !ruby/object:Gem::Requirement
|
161
181
|
requirements:
|
162
182
|
- - "~>"
|
163
183
|
- !ruby/object:Gem::Version
|
164
|
-
version: '
|
184
|
+
version: '13.0'
|
165
185
|
- !ruby/object:Gem::Dependency
|
166
186
|
name: rspec
|
167
187
|
requirement: !ruby/object:Gem::Requirement
|
@@ -297,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
297
317
|
- !ruby/object:Gem::Version
|
298
318
|
version: '0'
|
299
319
|
requirements: []
|
300
|
-
rubygems_version: 3.
|
320
|
+
rubygems_version: 3.4.22
|
301
321
|
signing_key:
|
302
322
|
specification_version: 4
|
303
323
|
summary: A Jekyll plugin for sending & receiving webmentions via Webmention.io.
|