jekyll-activity-pub 0.1.0rc12 → 0.1.0rc13

Sign up to get free protection for your applications and to get access to all the features.
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