jekyll-activitypub-static 0.7.6 → 0.8.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/README.md +107 -7
- data/lib/jekyll/activitypub_static/generator.rb +96 -11
- data/lib/jekyll/activitypub_static/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '007741693ce63e45f543662e7130a98a1e4ccf9ac738250d6c074d9a50677d48'
|
|
4
|
+
data.tar.gz: 1573599c4e182de63d2072da417d638456716a137529bdd658019a96f83a7584
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eac0999198d787cd640863823f0863b9618e17e6476a0dc3f100774940978f88f9d4f7619f65b5540d4a649112dafe4fbca810506d0b9d9b164bfd9b07d2b037
|
|
7
|
+
data.tar.gz: 23b124c60df634ecaf9be0c507e4d7ab5773ca97d14771a5ae9278e50079c6b226ac4c099aa086745610456d83e5d7c30ecc0446ed5532932efbee77a7a67a85
|
data/README.md
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
A Jekyll plugin that generates a static ActivityPoll feed, the read-only polling subset of ActivityPub defined by FEP-b06c.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this to the `Gemfile` of your Jekyll site:
|
|
8
|
+
|
|
9
|
+
```Gemfile
|
|
10
|
+
gem "jekyll-activitypub-static"
|
|
11
|
+
```
|
|
6
12
|
|
|
7
13
|
To build from source:
|
|
8
14
|
|
|
@@ -13,19 +19,113 @@ gem install ./jekyll-activitypub-static-*.gem
|
|
|
13
19
|
|
|
14
20
|
## Usage
|
|
15
21
|
|
|
16
|
-
Add this to the `
|
|
22
|
+
Add this to the `_config.yml` for your site:
|
|
17
23
|
|
|
18
|
-
```
|
|
19
|
-
|
|
24
|
+
```yaml
|
|
25
|
+
plugins:
|
|
26
|
+
- jekyll-activitypub-static
|
|
20
27
|
```
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
The plugin uses your site's standard `url`, `author`, and `description`
|
|
32
|
+
settings, plus the `activitypub` configuration block.
|
|
23
33
|
|
|
24
34
|
```yaml
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
url: "https://example.com"
|
|
36
|
+
author: "Example Author"
|
|
37
|
+
description: "A short description of the site"
|
|
38
|
+
|
|
39
|
+
activitypub:
|
|
40
|
+
output_path: "activitypub"
|
|
41
|
+
preferred_username: "example"
|
|
42
|
+
summary_property: "description"
|
|
43
|
+
note_max_characters: 500
|
|
44
|
+
update_interval: "P1D"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Available `activitypub` options:
|
|
48
|
+
|
|
49
|
+
- `output_path`: directory for generated ActivityPub files. Defaults to
|
|
50
|
+
`activitypub`.
|
|
51
|
+
- `preferred_username`: username for the generated actor and WebFinger
|
|
52
|
+
document. Defaults to the host name from `url`. The WebFinger account ID is
|
|
53
|
+
`preferred_username` at the domain from `url`, like `example@example.com`.
|
|
54
|
+
- `summary_property`: post front matter property to use for generated Article
|
|
55
|
+
summaries. Defaults to `description`; when the property is missing or empty,
|
|
56
|
+
the plugin uses the rendered post excerpt.
|
|
57
|
+
- `note_max_characters`: maximum plain-text character count for generated
|
|
58
|
+
Note objects. Defaults to `500`.
|
|
59
|
+
- `update_interval`: ActivityPoll polling interval for the generated Actor
|
|
60
|
+
object, as an ISO 8601 duration. Defaults to `P1D`.
|
|
61
|
+
|
|
62
|
+
## Articles and Notes
|
|
63
|
+
|
|
64
|
+
Posts are generated as Activity Streams `Article` objects by default.
|
|
65
|
+
|
|
66
|
+
Short, untitled posts without an explicit summary are generated as
|
|
67
|
+
Activity Streams `Note` objects. `Note` is the native Activity Streams object
|
|
68
|
+
type used by Mastodon for statuses. To make a post eligible for `Note`
|
|
69
|
+
generation, set an explicit blank title:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
---
|
|
73
|
+
title: ""
|
|
74
|
+
---
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Jekyll generates titles from post filenames when `title` is omitted, so an
|
|
78
|
+
omitted title is treated as a normal title. A blank-titled post is generated as
|
|
79
|
+
a `Note` when it has no configured summary property, has one rendered paragraph,
|
|
80
|
+
and its plain-text content is no longer than `activitypub.note_max_characters`.
|
|
81
|
+
|
|
82
|
+
Blank-titled posts may need layout support on home pages, archive pages, or
|
|
83
|
+
post lists. Use the excerpt, date, or another fallback when displaying links to
|
|
84
|
+
untitled posts.
|
|
85
|
+
|
|
86
|
+
## Layouts
|
|
87
|
+
|
|
88
|
+
To link from HTML pages to their ActivityPub representation, add this to the
|
|
89
|
+
`<head>` element of your layouts:
|
|
90
|
+
|
|
91
|
+
```html
|
|
92
|
+
{% if page.activitypub_url %}
|
|
93
|
+
<link rel="alternate" type="application/activity+json" href="{{ page.activitypub_url }}">
|
|
94
|
+
{% endif %}
|
|
27
95
|
```
|
|
28
96
|
|
|
97
|
+
The plugin sets `page.activitypub_url` before rendering posts and the site
|
|
98
|
+
index. For posts, the URL points to the generated JSON-LD Article or Note
|
|
99
|
+
object. For the site index, the URL points to the generated Actor object.
|
|
100
|
+
|
|
101
|
+
## Generated Files
|
|
102
|
+
|
|
103
|
+
The plugin generates these static files:
|
|
104
|
+
|
|
105
|
+
- `actor.jsonld`
|
|
106
|
+
- `.well-known/webfinger`
|
|
107
|
+
- `activitypub/inbox.jsonld`
|
|
108
|
+
- `activitypub/outbox.jsonld`
|
|
109
|
+
- `activitypub/outbox/page-*.jsonld`
|
|
110
|
+
- `activitypub/posts/*.jsonld`
|
|
111
|
+
- `activitypub/activities/create-*.jsonld`
|
|
112
|
+
|
|
113
|
+
The `activitypub` path changes when `activitypub.output_path` is configured.
|
|
114
|
+
|
|
115
|
+
### WebFinger
|
|
116
|
+
|
|
117
|
+
The WebFinger document is generated at `.well-known/webfinger` and uses an
|
|
118
|
+
account ID in the form `preferred_username` at the site domain. For example,
|
|
119
|
+
with `url: "https://example.com"` and `preferred_username: "evan"`, the
|
|
120
|
+
account ID is `evan@example.com`.
|
|
121
|
+
|
|
122
|
+
WebFinger discovery only works when the generated site is served from the root
|
|
123
|
+
of its domain, because clients request `https://example.com/.well-known/webfinger`.
|
|
124
|
+
Sites served from a subdirectory cannot provide a domain-level WebFinger
|
|
125
|
+
endpoint with this plugin alone.
|
|
126
|
+
|
|
127
|
+
## Example Site
|
|
128
|
+
|
|
29
129
|
The `example-site` directory has a minimal example site (thus the name). You can build and run it with these commands:
|
|
30
130
|
|
|
31
131
|
```sh
|
|
@@ -30,21 +30,16 @@ module Jekyll
|
|
|
30
30
|
generate_webfinger(site)
|
|
31
31
|
generate_actor(site)
|
|
32
32
|
generate_inbox(site)
|
|
33
|
-
generate_activities(site)
|
|
34
|
-
generate_outbox_pages(site)
|
|
35
|
-
generate_outbox(site)
|
|
36
33
|
end
|
|
37
34
|
|
|
38
35
|
def generate_webfinger(site)
|
|
39
36
|
Jekyll.logger.info LOG_TAG, "Generating .well-known/webfinger"
|
|
40
37
|
|
|
41
38
|
url = site.config["url"]
|
|
42
|
-
host = URI(url).host
|
|
43
|
-
username = preferred_username(site)
|
|
44
39
|
actor_url = "#{url}/actor.jsonld"
|
|
45
40
|
|
|
46
41
|
webfinger = {
|
|
47
|
-
"subject" => "acct:#{
|
|
42
|
+
"subject" => "acct:#{webfinger_address(site)}",
|
|
48
43
|
"links" => [
|
|
49
44
|
{
|
|
50
45
|
"rel" => "self",
|
|
@@ -103,14 +98,21 @@ module Jekyll
|
|
|
103
98
|
article = {
|
|
104
99
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
|
105
100
|
"id" => article_id,
|
|
106
|
-
"type" =>
|
|
107
|
-
"name" => post.data["title"],
|
|
101
|
+
"type" => post_type(site, post),
|
|
108
102
|
"content" => post.content,
|
|
103
|
+
"summary" => article_summary(site, post),
|
|
109
104
|
"published" => post.date.iso8601,
|
|
110
105
|
"attributedTo" => "#{url}/actor.jsonld",
|
|
106
|
+
"url" => {
|
|
107
|
+
"type" => "Link",
|
|
108
|
+
"mediaType" => "text/html",
|
|
109
|
+
"href" => "#{url}#{post.url}"
|
|
110
|
+
},
|
|
111
111
|
"to" => "as:Public"
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
article["name"] = post.data["title"] unless post.data["title"].to_s.strip.empty?
|
|
115
|
+
|
|
114
116
|
site.static_files << JsonStaticFile.new(site, output_dir, filename, article)
|
|
115
117
|
end
|
|
116
118
|
end
|
|
@@ -137,12 +139,13 @@ module Jekyll
|
|
|
137
139
|
"published" => post.date.iso8601,
|
|
138
140
|
"object" => {
|
|
139
141
|
"id" => article_id,
|
|
140
|
-
"type" =>
|
|
141
|
-
"name" => post.data["title"]
|
|
142
|
+
"type" => post_type(site, post)
|
|
142
143
|
},
|
|
143
144
|
"to" => "as:Public"
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
activity["object"]["name"] = post.data["title"] unless post.data["title"].to_s.strip.empty?
|
|
148
|
+
|
|
146
149
|
site.static_files << JsonStaticFile.new(site, output_dir, filename, activity)
|
|
147
150
|
Jekyll.logger.info LOG_TAG, "Wrote activity to #{path}"
|
|
148
151
|
end
|
|
@@ -221,22 +224,31 @@ module Jekyll
|
|
|
221
224
|
url = site.config["url"]
|
|
222
225
|
summary = site.config["description"]
|
|
223
226
|
output_path = site.config.dig("activitypub", "output_path") || "activitypub"
|
|
227
|
+
update_interval = site.config.dig("activitypub", "update_interval") || "P1D"
|
|
224
228
|
|
|
225
229
|
{
|
|
226
230
|
"@context": [
|
|
227
231
|
"https://www.w3.org/ns/activitystreams",
|
|
228
232
|
"https://purl.archive.org/miscellany/1.0",
|
|
233
|
+
"https://purl.archive.org/socialweb/webfinger",
|
|
229
234
|
"https://w3id.org/fep/b06c"
|
|
230
235
|
],
|
|
231
236
|
"type": "Person",
|
|
232
237
|
"id": "#{url}/actor.jsonld",
|
|
233
238
|
"pollOnly": true,
|
|
239
|
+
"updateInterval": update_interval,
|
|
234
240
|
"name": name(site),
|
|
235
241
|
"preferredUsername": preferred_username(site),
|
|
236
242
|
"summary": (summary unless summary.to_s.strip.empty?),
|
|
237
243
|
"inbox": "#{url}/#{output_path}/inbox.jsonld",
|
|
238
244
|
"outbox": "#{url}/#{output_path}/outbox.jsonld",
|
|
239
245
|
"attributedTo": "#{url}/actor.jsonld",
|
|
246
|
+
"url" => {
|
|
247
|
+
"type" => "Link",
|
|
248
|
+
"mediaType" => "text/html",
|
|
249
|
+
"href" => homepage_url(site)
|
|
250
|
+
},
|
|
251
|
+
"webfinger" => webfinger_address(site),
|
|
240
252
|
"cc": "as:Public"
|
|
241
253
|
}
|
|
242
254
|
end
|
|
@@ -276,10 +288,83 @@ module Jekyll
|
|
|
276
288
|
page["prev"] = "#{url}/#{output_path}/outbox/page-#{page_number - 1}.jsonld" if page_number > 1
|
|
277
289
|
page
|
|
278
290
|
end
|
|
291
|
+
|
|
292
|
+
def article_summary(site, post)
|
|
293
|
+
property = site.config.dig("activitypub", "summary_property") || "description"
|
|
294
|
+
explicit = post.data[property]
|
|
295
|
+
return explicit unless explicit.to_s.strip.empty?
|
|
296
|
+
|
|
297
|
+
excerpt = post.data["excerpt"]
|
|
298
|
+
excerpt.output
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def note?(site, post)
|
|
302
|
+
title = post.data["title"]
|
|
303
|
+
return false unless title.to_s.strip.empty?
|
|
304
|
+
|
|
305
|
+
summary = explicit_summary(site, post)
|
|
306
|
+
return false unless summary.to_s.strip.empty?
|
|
307
|
+
|
|
308
|
+
return false unless paragraph_count(post.content) == 1
|
|
309
|
+
|
|
310
|
+
plain_text(post.content).length <= note_max_characters(site)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def post_type(site, post)
|
|
314
|
+
note?(site, post) ? "Note" : "Article"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def explicit_summary(site, post)
|
|
318
|
+
property = site.config.dig("activitypub", "summary_property") || "description"
|
|
319
|
+
post.data[property]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def note_max_characters(site)
|
|
323
|
+
site.config.dig("activitypub", "note_max_characters") || 500
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def paragraph_count(content)
|
|
327
|
+
content.scan(/<p\b[^>]*>/).length
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def plain_text(content)
|
|
331
|
+
content.gsub(/<[^>]*>/, "")
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def homepage_url(site)
|
|
335
|
+
url = site.config["url"]
|
|
336
|
+
url.end_with?("/") ? url : "#{url}/"
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def webfinger_address(site)
|
|
340
|
+
url = site.config["url"]
|
|
341
|
+
host = URI(url).host
|
|
342
|
+
"#{preferred_username(site)}@#{host}"
|
|
343
|
+
end
|
|
279
344
|
end
|
|
280
345
|
end
|
|
281
346
|
end
|
|
282
347
|
|
|
283
348
|
Jekyll::Hooks.register :site, :post_render do |site|
|
|
284
|
-
Jekyll::ActivityPubStatic::Generator.new(site.config)
|
|
349
|
+
generator = Jekyll::ActivityPubStatic::Generator.new(site.config)
|
|
350
|
+
|
|
351
|
+
generator.generate_articles(site)
|
|
352
|
+
generator.generate_activities(site)
|
|
353
|
+
generator.generate_outbox_pages(site)
|
|
354
|
+
generator.generate_outbox(site)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
Jekyll::Hooks.register :posts, :pre_render do |post|
|
|
358
|
+
site = post.site
|
|
359
|
+
url = site.config["url"]
|
|
360
|
+
output_path = site.config.dig("activitypub", "output_path") || "activitypub"
|
|
361
|
+
slug = post.basename_without_ext.sub(/^\d{4}-\d{2}-\d{2}-/, "")
|
|
362
|
+
post.data["activitypub_url"] = "#{url}/#{output_path}/posts/#{slug}.jsonld"
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
Jekyll::Hooks.register :pages, :post_init do |page|
|
|
366
|
+
next unless page.url == "/"
|
|
367
|
+
|
|
368
|
+
site = page.site
|
|
369
|
+
page.data["activitypub_url"] = "#{site.config["url"]}/actor.jsonld"
|
|
285
370
|
end
|