jekyll-activity-pub 0.1.0rc12 → 0.1.0rc13
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 +33 -3
- data/lib/jekyll/activity_pub/activity.rb +10 -2
- data/lib/jekyll/activity_pub/actor.rb +34 -2
- data/lib/jekyll/activity_pub/delete.rb +1 -1
- data/lib/jekyll/activity_pub/helper.rb +5 -0
- data/lib/jekyll/activity_pub/image.rb +3 -1
- data/lib/jekyll/activity_pub/notifier.rb +33 -18
- data/lib/jekyll/activity_pub/webfinger.rb +15 -1
- data/lib/jekyll/command_extension.rb +6 -0
- data/lib/jekyll/commands/generate_keys.rb +2 -3
- data/lib/jekyll-activity-pub.rb +5 -1
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c1484d32011db73dd1d0db605244a6ac077f0820aa4ef0b2c78111a210f0c04
|
4
|
+
data.tar.gz: 17841d449364d36b5b8a78377234b0215aa09951797776c51a8fd4835142c758
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ec20c6425542d0b83a2e995dcf38e3d9196728fcb5358e782ee66f23ba26aa625ab1523bd9235eebb2684cc1548912f9dd3a24ceac6ec30660fd0664969fac2
|
7
|
+
data.tar.gz: 8ec390af344d3e4f7558119a00e153c194029206af5c6b431282f2af1d73de22eeeafa6001757ebd368a8d0c5433753df7e09a8596de75b03915a1fca9a0cf2f
|
data/README.md
CHANGED
@@ -1,6 +1,36 @@
|
|
1
1
|
# Jekyll ActivityPub
|
2
2
|
|
3
|
-
|
3
|
+
Generates an [ActivityPub](https://www.w3.org/TR/activitypub/)
|
4
|
+
representation of your site and delegates notifications and followers to
|
5
|
+
the [Distributed Press](https://distributed.press) Social Inbox.
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
+
## Installation and Usage
|
8
|
+
|
9
|
+
See _docs/
|
10
|
+
|
11
|
+
## Development
|
12
|
+
|
13
|
+
After checking out the repo, run `bundle` to install dependencies.
|
14
|
+
|
15
|
+
To release a new version, update the version number in
|
16
|
+
`jekyll-activity-pub.gemspec`, and then run `go-task release`, which
|
17
|
+
will push the `.gem` file to [rubygems.org](https://rubygems.org).
|
18
|
+
|
19
|
+
## Contributing
|
20
|
+
|
21
|
+
Bug reports and pull requests are welcome on 0xacab.org at
|
22
|
+
<https://0xacab.org/sutty/jekyll/jekyll-activity-pub>. This
|
23
|
+
project is intended to be a safe, welcoming space for collaboration, and
|
24
|
+
contributors are expected to adhere to the [Sutty code of
|
25
|
+
conduct](https://sutty.nl/en/code-of-conduct/).
|
26
|
+
|
27
|
+
## License
|
28
|
+
|
29
|
+
The gem is available as free software under the terms of the Apache2
|
30
|
+
License.
|
31
|
+
|
32
|
+
## Code of Conduct
|
33
|
+
|
34
|
+
Everyone interacting in the jekyll-activity-pub project’s
|
35
|
+
codebases, issue trackers, chat rooms and mailing lists is expected to
|
36
|
+
follow the [code of conduct](https://sutty.nl/en/code-of-conduct/).
|
@@ -54,10 +54,11 @@ module Jekyll
|
|
54
54
|
'to' => [
|
55
55
|
'https://www.w3.org/ns/activitystreams#Public'
|
56
56
|
],
|
57
|
-
'cc' => [
|
57
|
+
'cc' => [Notifier.followers_url],
|
58
58
|
'inReplyTo' => doc.data['in_reply_to'],
|
59
59
|
'sensitive' => sensitive?,
|
60
60
|
'content' => doc.content,
|
61
|
+
'name' => doc.data['title'],
|
61
62
|
'contentMap' => {
|
62
63
|
locale => doc.content
|
63
64
|
},
|
@@ -75,7 +76,7 @@ module Jekyll
|
|
75
76
|
#
|
76
77
|
# @return [String,nil]
|
77
78
|
def summary
|
78
|
-
@summary ||=
|
79
|
+
@summary ||= doc.data.slice('title', 'summary').values.join(separator)
|
79
80
|
end
|
80
81
|
|
81
82
|
# Should it have a content warning?
|
@@ -83,6 +84,13 @@ module Jekyll
|
|
83
84
|
!!doc.data.fetch('sensitive', false)
|
84
85
|
end
|
85
86
|
|
87
|
+
# Separator to join title and summary by
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
def separator
|
91
|
+
@separator ||= site.config.dig('activity_pub', 'separator') || ' // '
|
92
|
+
end
|
93
|
+
|
86
94
|
# Find attachments
|
87
95
|
#
|
88
96
|
# @return [Array]
|
@@ -46,6 +46,7 @@ module Jekyll
|
|
46
46
|
],
|
47
47
|
'type' => 'Person',
|
48
48
|
'id' => absolute_url(url),
|
49
|
+
'url' => site.config['url'],
|
49
50
|
'outbox' => nil,
|
50
51
|
'inbox' => inbox,
|
51
52
|
'following' => nil,
|
@@ -53,16 +54,29 @@ module Jekyll
|
|
53
54
|
'preferredUsername' => username,
|
54
55
|
'name' => public_name,
|
55
56
|
'summary' => summary,
|
56
|
-
'icon' => icons,
|
57
|
+
'icon' => icons.first,
|
58
|
+
'image' => images.first,
|
57
59
|
'publicKey' => nil,
|
60
|
+
'published' => date.xmlschema,
|
61
|
+
'updated' => updated.xmlschema,
|
58
62
|
'attachment' => [
|
59
|
-
PropertyValue.new(website_name,
|
63
|
+
PropertyValue.new(website_name, website_link)
|
60
64
|
]
|
61
65
|
}
|
62
66
|
end
|
63
67
|
|
68
|
+
# @return [Time]
|
69
|
+
def date
|
70
|
+
@date ||= site.config.dig('activity_pub', 'published') || site.time
|
71
|
+
end
|
72
|
+
|
64
73
|
private
|
65
74
|
|
75
|
+
# @return [Time]
|
76
|
+
def updated
|
77
|
+
@updated ||= site.config.dig('activity_pub', 'updated') || site.time
|
78
|
+
end
|
79
|
+
|
66
80
|
# Finds public name
|
67
81
|
#
|
68
82
|
# @return [String,nil]
|
@@ -110,6 +124,24 @@ module Jekyll
|
|
110
124
|
inbox << '/inbox'
|
111
125
|
end
|
112
126
|
end
|
127
|
+
|
128
|
+
# Find images
|
129
|
+
#
|
130
|
+
# @return [Array]
|
131
|
+
def images
|
132
|
+
@images ||= [find_best_value_for(site.config, %w[activity_pub images],
|
133
|
+
%w[image path])].flatten.compact.map do |icon|
|
134
|
+
Image.new(site, icon, summary)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [String]
|
139
|
+
def website_link
|
140
|
+
@website_link ||=
|
141
|
+
<<~LINK
|
142
|
+
<a rel="me" href="#{site.config['url']}">#{site.config['url']}</a>
|
143
|
+
LINK
|
144
|
+
end
|
113
145
|
end
|
114
146
|
end
|
115
147
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
3
4
|
require 'jekyll/hooks'
|
4
5
|
|
5
6
|
module Jekyll
|
@@ -7,6 +8,10 @@ module Jekyll
|
|
7
8
|
# Container for common tools
|
8
9
|
module Helper
|
9
10
|
include Jekyll::Filters::URLFilters
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
# So we can deep dig between linked objects
|
14
|
+
def_delegators :data, :dig
|
10
15
|
|
11
16
|
# Some filters needs a Liquid-like context
|
12
17
|
StubContext = Struct.new(:registers, keyword_init: true)
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'marcel'
|
4
|
+
require 'pathname'
|
3
5
|
require_relative 'helper'
|
4
6
|
|
5
7
|
module Jekyll
|
@@ -20,7 +22,7 @@ module Jekyll
|
|
20
22
|
|
21
23
|
@data = {
|
22
24
|
'type' => 'Image',
|
23
|
-
'mediaType' =>
|
25
|
+
'mediaType' => Marcel::MimeType.for(Pathname.new(site.in_source_dir(path))),
|
24
26
|
'url' => absolute_url(path),
|
25
27
|
'name' => description.to_s
|
26
28
|
}
|
@@ -22,6 +22,12 @@ module Jekyll
|
|
22
22
|
site.in_dest_dir(relative_path)
|
23
23
|
end
|
24
24
|
|
25
|
+
def date
|
26
|
+
@date ||= Time.parse(data['published'])
|
27
|
+
rescue StandardError
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
25
31
|
# @return [Time, nil]
|
26
32
|
def updated_at
|
27
33
|
@updated_at ||= Time.parse(data['updated'])
|
@@ -107,7 +113,7 @@ module Jekyll
|
|
107
113
|
|
108
114
|
base_endpoint = "/v1/#{actor}"
|
109
115
|
outbox_endpoint = "#{base_endpoint}/outbox"
|
110
|
-
actor_object =
|
116
|
+
actor_object = object_for(site.in_dest_dir(actor_url.sub(site.config['url'], '')))
|
111
117
|
# TODO: Move to API client
|
112
118
|
inbox_body = {
|
113
119
|
'actorUrl' => actor_url,
|
@@ -130,15 +136,20 @@ module Jekyll
|
|
130
136
|
process_object(outbox_endpoint, actor_object, object_for(object_url), status)
|
131
137
|
end
|
132
138
|
|
139
|
+
# Update actor profile
|
140
|
+
if actor_object.updated_at > actor_object.date
|
141
|
+
Jekyll.logger.debug 'ActivityPub:', 'Updating Actor profile'
|
142
|
+
actor_update = Jekyll::ActivityPub::Update.new(site, actor_object, actor_object)
|
143
|
+
|
144
|
+
unless (response = client.post(endpoint: outbox_endpoint, body: actor_update)).ok?
|
145
|
+
raise NotificationError, "Couldn't update actor (#{response.code}: #{response.message})"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
133
149
|
# Store everything for later
|
134
150
|
save
|
135
151
|
end
|
136
152
|
|
137
|
-
# @return [Jekyll::Site]
|
138
|
-
def site
|
139
|
-
@@site
|
140
|
-
end
|
141
|
-
|
142
153
|
# Return data
|
143
154
|
#
|
144
155
|
# @return [Hash]
|
@@ -159,12 +170,12 @@ module Jekyll
|
|
159
170
|
#
|
160
171
|
# @param :path [String]
|
161
172
|
# @return [nil]
|
162
|
-
def update(path)
|
173
|
+
def update(path, **opts)
|
163
174
|
# Compare Unix timestamps
|
164
175
|
if created?(path) && (object_for(path)&.updated_at&.to_i || 0) > (status(path)['updated_at'] || 0)
|
165
|
-
action(path, 'update')
|
176
|
+
action(path, 'update', **opts)
|
166
177
|
else
|
167
|
-
create(path)
|
178
|
+
create(path, **opts)
|
168
179
|
end
|
169
180
|
|
170
181
|
nil
|
@@ -174,8 +185,8 @@ module Jekyll
|
|
174
185
|
#
|
175
186
|
# @param :path [String]
|
176
187
|
# @return [nil]
|
177
|
-
def create(path)
|
178
|
-
action(path, 'create') unless created?(path)
|
188
|
+
def create(path, **opts)
|
189
|
+
action(path, 'create', **opts) unless created?(path)
|
179
190
|
|
180
191
|
nil
|
181
192
|
end
|
@@ -201,21 +212,21 @@ module Jekyll
|
|
201
212
|
#
|
202
213
|
# @param :path [String]
|
203
214
|
def created?(path)
|
204
|
-
!
|
215
|
+
!status(path)['created_at'].nil?
|
205
216
|
end
|
206
217
|
|
207
218
|
# @param :path [String]
|
208
219
|
def updated?(path)
|
209
|
-
!
|
220
|
+
!status(path)['updated_at'].nil?
|
210
221
|
end
|
211
222
|
|
212
223
|
# @param :path [String]
|
213
224
|
def deleted?(path)
|
214
|
-
!
|
225
|
+
!status(path)['deleted_at'].nil?
|
215
226
|
end
|
216
227
|
|
217
228
|
def exist?(path)
|
218
|
-
!
|
229
|
+
!status(path)['action'].nil?
|
219
230
|
end
|
220
231
|
|
221
232
|
# Stores data back to a file and optionally commits it
|
@@ -294,12 +305,15 @@ module Jekyll
|
|
294
305
|
#
|
295
306
|
# @param :path [String]
|
296
307
|
# @param :action [String]
|
297
|
-
# @return [
|
298
|
-
def action(path, action)
|
308
|
+
# @return [nil]
|
309
|
+
def action(path, action, **opts)
|
299
310
|
path = path_relative_to_dest(path)
|
300
311
|
|
301
312
|
data['notifications'][path] ||= {}
|
302
313
|
data['notifications'][path]['action'] = action.to_s
|
314
|
+
data['notifications'][path].merge! opts.transform_keys(&:to_s)
|
315
|
+
|
316
|
+
nil
|
303
317
|
end
|
304
318
|
|
305
319
|
# Paths are relative to site destination
|
@@ -342,8 +356,9 @@ module Jekyll
|
|
342
356
|
rel = path_relative_to_dest(path)
|
343
357
|
path = site.in_dest_dir(rel)
|
344
358
|
data = JSON.parse(File.read(path)) if File.exist? path
|
359
|
+
data ||= { 'id' => status(path)['id'] }
|
345
360
|
|
346
|
-
PseudoObject.new(url: rel, site: site, relative_path: rel, data: data
|
361
|
+
PseudoObject.new(url: rel, site: site, relative_path: rel, data: data)
|
347
362
|
end
|
348
363
|
end
|
349
364
|
end
|
@@ -37,9 +37,23 @@ module Jekyll
|
|
37
37
|
'rel' => 'self',
|
38
38
|
'type' => 'application/activity+json',
|
39
39
|
'href' => absolute_url(@actor.url)
|
40
|
+
},
|
41
|
+
{
|
42
|
+
'rel' => 'http://webfinger.net/rel/profile-page',
|
43
|
+
'type' => 'text/html',
|
44
|
+
'href' => site.config['url']
|
40
45
|
}
|
41
46
|
]
|
42
|
-
}
|
47
|
+
}.tap do |data|
|
48
|
+
next unless @actor.data['icon']
|
49
|
+
|
50
|
+
data['links'] <<
|
51
|
+
{
|
52
|
+
'rel' => 'http://webfinger.net/rel/avatar',
|
53
|
+
'type' => @actor.data.dig('icon', 'mediaType'),
|
54
|
+
'href' => @actor.data.dig('icon', 'url')
|
55
|
+
}
|
56
|
+
end
|
43
57
|
end
|
44
58
|
|
45
59
|
# The webfinger file is expected to be at this location always
|
@@ -10,6 +10,12 @@ module Jekyll
|
|
10
10
|
def add_build_options(cmd)
|
11
11
|
super
|
12
12
|
|
13
|
+
activity_pub_private_key(cmd)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def activity_pub_private_key(cmd)
|
13
19
|
cmd.option 'activity_pub_private_key', '--key path/to/rsa.pem', String, 'Path to RSA private key in PEM format'
|
14
20
|
end
|
15
21
|
end
|
@@ -6,14 +6,14 @@ require 'distributed_press/v1/social/client'
|
|
6
6
|
module Jekyll
|
7
7
|
module Commands
|
8
8
|
# Send Activity Pub notifications
|
9
|
-
class
|
9
|
+
class GenerateKeys < Jekyll::Command
|
10
10
|
class << self
|
11
11
|
def init_with_program(prog)
|
12
12
|
prog.command(:generate_keys) do |c|
|
13
13
|
c.syntax 'generate_keys --key-size 2048 --key path/to/privkey.pem'
|
14
14
|
c.description 'Generate an RSA keypair for ActivityPub'
|
15
15
|
|
16
|
-
|
16
|
+
activity_pub_private_key c
|
17
17
|
|
18
18
|
c.option 'activity_pub_key_size', '--key-size 2048', Integer, 'RSA key size (2048 by default)'
|
19
19
|
|
@@ -34,7 +34,6 @@ module Jekyll
|
|
34
34
|
raise Jekyll::Errors::FatalException, "Private key already exists: #{private_key_path}"
|
35
35
|
end
|
36
36
|
|
37
|
-
options = configuration_from_options(options)
|
38
37
|
client = DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: key_size)
|
39
38
|
|
40
39
|
File.open(private_key_path, 'w') do |f|
|
data/lib/jekyll-activity-pub.rb
CHANGED
@@ -77,7 +77,11 @@ Jekyll::Hooks.register(:documents, :post_convert, priority: :high) do |doc|
|
|
77
77
|
end
|
78
78
|
|
79
79
|
create_or_update.new(site, actor, activity).tap do |action|
|
80
|
-
|
80
|
+
method = action.data['type'].downcase.to_sym
|
81
|
+
path = activity.destination(site.dest)
|
82
|
+
id = activity.data['id']
|
83
|
+
|
84
|
+
Jekyll::ActivityPub::Notifier.public_send(method, path, id: id)
|
81
85
|
|
82
86
|
outbox.data['totalItems'] += 1
|
83
87
|
outbox.data['orderedItems'] << action
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-activity-pub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.0rc13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sutty
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: distributed-press-api-client
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: marcel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: pry
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|