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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52d19e3cacf79bb7a5c041ae37b4caae232947f50fc94eb7b269edebe37a15b7
4
- data.tar.gz: 976bf99864bc71f49bec91090de41b4ddb829cfc296ba0addaa7dc43400daddd
3
+ metadata.gz: 2c1484d32011db73dd1d0db605244a6ac077f0820aa4ef0b2c78111a210f0c04
4
+ data.tar.gz: 17841d449364d36b5b8a78377234b0215aa09951797776c51a8fd4835142c758
5
5
  SHA512:
6
- metadata.gz: abfa3e386181380e62a2223b64c3201f5b3751921f669c4129e637959c0600c8fa529f8ce929540a51c975154f7a5c007e4d39522561e1c05ce82ff1eda7bce5
7
- data.tar.gz: 98bb2c9fcec4955dc7b79582421fca47d6d50008bce879a821e5d3d2dc4c800901566230dc402ec1ea03f173c1ce1457d2c3c6751a5f8b9991b16c329edd7604
6
+ metadata.gz: 1ec20c6425542d0b83a2e995dcf38e3d9196728fcb5358e782ee66f23ba26aa625ab1523bd9235eebb2684cc1548912f9dd3a24ceac6ec30660fd0664969fac2
7
+ data.tar.gz: 8ec390af344d3e4f7558119a00e153c194029206af5c6b431282f2af1d73de22eeeafa6001757ebd368a8d0c5433753df7e09a8596de75b03915a1fca9a0cf2f
data/README.md CHANGED
@@ -1,6 +1,36 @@
1
1
  # Jekyll ActivityPub
2
2
 
3
- ## Immediate goals
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
- * Create an outbox from a Jekyll website
6
- * Send notifications to [Distributed Press](https://distributed.press/)
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' => [ Notifier.followers_url ],
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 ||= find_best_value_for(doc.data, 'summary', 'description', 'title')
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, absolute_url(site.config['url']))
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
@@ -23,7 +23,7 @@ module Jekyll
23
23
  'id' => absolute_url(object.url).sub('.jsonld', '/delete.jsonld'),
24
24
  'type' => 'Delete',
25
25
  'actor' => absolute_url(actor.url),
26
- 'object' => absolute_url(object.url)
26
+ 'object' => object.data['id']
27
27
  }
28
28
 
29
29
  trigger_hooks :post_init
@@ -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' => "image/#{File.extname(path).sub('.', '')}",
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 = PseudoObject.new(url: actor_url)
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
- !(status(path)['created_at'].nil?)
215
+ !status(path)['created_at'].nil?
205
216
  end
206
217
 
207
218
  # @param :path [String]
208
219
  def updated?(path)
209
- !(status(path)['updated_at'].nil?)
220
+ !status(path)['updated_at'].nil?
210
221
  end
211
222
 
212
223
  # @param :path [String]
213
224
  def deleted?(path)
214
- !(status(path)['deleted_at'].nil?)
225
+ !status(path)['deleted_at'].nil?
215
226
  end
216
227
 
217
228
  def exist?(path)
218
- !(status(path)['action'].nil?)
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 [Hash]
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 Notify < Jekyll::Command
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
- add_build_options(c)
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|
@@ -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
- Jekyll::ActivityPub::Notifier.public_send action.data['type'].downcase.to_sym, activity.destination(site.dest)
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.0rc12
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-09-07 00:00:00.000000000 Z
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