jekyll-activity-pub 0.1.0rc0

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.
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ module Jekyll
6
+ module ActivityPub
7
+ # A Key:Value map
8
+ class PropertyValue
9
+ include Helper
10
+
11
+ attr_reader :data
12
+
13
+ # @param :name [String]
14
+ # @param :value [String]
15
+ def initialize(name, value)
16
+ @data = {
17
+ 'type' => 'PropertyValue',
18
+ 'name' => name.to_s,
19
+ 'value' => value.to_s
20
+ }
21
+
22
+ trigger_hooks :post_init
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll/page'
4
+ require_relative 'helper'
5
+ require_relative 'notifier'
6
+
7
+ module Jekyll
8
+ module ActivityPub
9
+ # Public key
10
+ class PublicKey < Jekyll::Page
11
+ include Helper
12
+
13
+ # Initialize with default data
14
+ #
15
+ # @param :site [Jekyll::Site]
16
+ # @param :actor [Jekyll::ActivityPub::Actor]
17
+ # @param :public_key [OpenSsl::Pkey::RSA]
18
+ # @param :base [String]
19
+ # @param :dir [String]
20
+ # @param :name [String]
21
+ def initialize(site, actor, public_key, base = '', dir = '', name = 'pubkey.pem')
22
+ @context = StubContext.new(registers: { site: site })
23
+ @actor = actor
24
+ @public_key = public_key
25
+
26
+ super(site, base, dir, name)
27
+
28
+ actor.data['publicKey'] = self
29
+
30
+ Notifier.public_key_url = absolute_url(url)
31
+
32
+ trigger_hooks :post_init
33
+ end
34
+
35
+ def read_yaml(*)
36
+ self.data = {
37
+ '@context' => 'https://w3id.org/security/v1',
38
+ '@type' => 'Key',
39
+ 'id' => absolute_url(url),
40
+ 'owner' => absolute_url(@actor.url),
41
+ 'publicKeyPem' => @public_key.public_to_pem
42
+ }
43
+ end
44
+
45
+ def content
46
+ data['publicKeyPem']
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'create'
4
+
5
+ module Jekyll
6
+ module ActivityPub
7
+ # Represents an Update activity
8
+ class Update < Create
9
+ # Initialize with default data
10
+ #
11
+ # @param :site [Jekyll::Site]
12
+ # @param :actor [Jekyll::ActivityPub::Actor]
13
+ # @param :followers [Jekyll::ActivityPub::Followers]
14
+ # @param :object [Jekyll::ActivityPub::Activity]
15
+ def initialize(site, actor, followers, object)
16
+ super
17
+
18
+ data['type'] = 'Update'
19
+
20
+ trigger_hooks :post_init
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll/page'
4
+ require_relative 'helper'
5
+ require_relative 'notifier'
6
+
7
+ module Jekyll
8
+ module ActivityPub
9
+ # Points to site's author
10
+ class WebFinger < Jekyll::Page
11
+ include Helper
12
+
13
+ # Initialize with default data
14
+ #
15
+ # @param :site [Jekyll::Site]
16
+ # @param :actor [Jekyll::ActivityPub::Actor]
17
+ # @param :base [String]
18
+ # @param :dir [String]
19
+ # @param :name [String]
20
+ def initialize(site, actor, base = '', dir = '.well-known', name = 'webfinger')
21
+ @context = StubContext.new(registers: { site: site })
22
+ @actor = actor
23
+
24
+ super(site, base, dir, name)
25
+
26
+ Notifier.actor = "@#{username}@#{hostname}"
27
+
28
+ # Set the actor profile in yet another format (no leading @)
29
+ site.config['activity_pub_profile'] = "#{username}@#{hostname}"
30
+
31
+ trigger_hooks :post_init
32
+ end
33
+
34
+ def read_yaml(*)
35
+ self.data = {
36
+ 'subject' => "acct:#{username}@#{hostname}",
37
+ 'aliases' => [],
38
+ 'links' => [
39
+ {
40
+ 'rel' => 'self',
41
+ 'type' => 'application/activity+json',
42
+ 'href' => absolute_url(@actor.url)
43
+ }
44
+ ]
45
+ }
46
+ end
47
+
48
+ # The webfinger file is expected to be at this location always
49
+ #
50
+ # @return [String]
51
+ def permalink
52
+ '.well-known/webfinger'
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'activity_pub/webfinger'
4
+ require_relative 'activity_pub/actor'
5
+ require_relative 'activity_pub/activity'
6
+ require_relative 'activity_pub/outbox'
7
+ require_relative 'activity_pub/followers'
8
+ require_relative 'activity_pub/following'
9
+ require_relative 'activity_pub/host_meta'
10
+ require_relative 'activity_pub/create'
11
+ require_relative 'activity_pub/update'
12
+ require_relative 'activity_pub/public_key'
13
+
14
+ module Jekyll
15
+ # ActivityPub
16
+ module ActivityPub
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll/command'
4
+
5
+ module Jekyll
6
+ # Extends Jekyll::Command to provide a private key to any command with
7
+ # build steps.
8
+ module CommandExtension
9
+ # Add private key option
10
+ def add_build_options(cmd)
11
+ super
12
+
13
+ cmd.option 'activity_pub_private_key', '--key path/to/rsa.pem', String, 'Path to RSA private key in PEM format'
14
+ end
15
+ end
16
+ end
17
+
18
+ Jekyll::Command.singleton_class.prepend Jekyll::CommandExtension
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll'
4
+ require 'distributed_press/v1/social/client'
5
+
6
+ module Jekyll
7
+ module Commands
8
+ # Send Activity Pub notifications
9
+ class Notify < Jekyll::Command
10
+ class << self
11
+ def init_with_program(prog)
12
+ prog.command(:generate_keys) do |c|
13
+ c.syntax 'generate_keys --key-size 2048 --key path/to/privkey.pem'
14
+ c.description 'Generate an RSA keypair for ActivityPub'
15
+
16
+ add_build_options(c)
17
+
18
+ c.option 'activity_pub_key_size', '--key-size 2048', Integer, 'RSA key size (2048 by default)'
19
+
20
+ c.action do |_, options|
21
+ process_with_graceful_fail(c, options, self)
22
+ end
23
+ end
24
+ end
25
+
26
+ def process(options)
27
+ # Adjust verbosity quickly
28
+ Jekyll.logger.adjust_verbosity(options)
29
+
30
+ private_key_path = options['activity_pub_private_key'] || 'privkey.pem'
31
+ key_size = options['activity_pub_key_size'] || 2048
32
+
33
+ if File.exist? private_key_path
34
+ raise Jekyll::Errors::FatalException, "Private key already exists: #{private_key_path}"
35
+ end
36
+
37
+ options = configuration_from_options(options)
38
+ client = DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: key_size)
39
+
40
+ File.open(private_key_path, 'w') do |f|
41
+ f.write client.private_key.export
42
+ rescue StandardError
43
+ FileUtils.rm_f(private_key_path)
44
+ raise
45
+ end
46
+
47
+ Jekyll.logger.info 'ActivityPub:', "Private key written to #{private_key_path}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll'
4
+ require_relative '../activity_pub/notifier'
5
+
6
+ module Jekyll
7
+ module Commands
8
+ # Send Activity Pub notifications
9
+ class Notify < Jekyll::Command
10
+ class << self
11
+ def init_with_program(prog)
12
+ prog.command(:notify) do |c|
13
+ c.syntax 'notify'
14
+ c.description 'Send ActivityPub notifications'
15
+
16
+ add_build_options(c)
17
+
18
+ c.action do |_, options|
19
+ process_with_graceful_fail(c, options, self)
20
+ end
21
+ end
22
+ end
23
+
24
+ # Send notifications
25
+ #
26
+ # @params :options [Hash]
27
+ def process(options)
28
+ # Adjust verbosity quickly
29
+ Jekyll.logger.adjust_verbosity(options)
30
+
31
+ options = configuration_from_options(options)
32
+ site = Jekyll::Site.new(options)
33
+
34
+ # Retrocompatibility
35
+ if site.reader.respond_to? :read_data
36
+ site.reader.read_data
37
+ else
38
+ site.data = DataReader.new(site).read(site.config['data_dir'])
39
+ end
40
+
41
+ Jekyll::ActivityPub::Notifier.site = site
42
+ Jekyll::ActivityPub::Notifier.notify!
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'distributed_press/v1/social/client'
4
+
5
+ require_relative 'jekyll/activity_pub'
6
+ require_relative 'jekyll/activity_pub/notifier'
7
+
8
+ # Generate Actor and WebFinger pages
9
+ Jekyll::Hooks.register :site, :post_read, priority: :low do |site|
10
+ Jekyll::ActivityPub::Notifier.site = site
11
+
12
+ # Private key is mandatory during build, so any errors should stop the
13
+ # build instead of logging a warning.
14
+ begin
15
+ private_key = File.read(site.config['activity_pub_private_key'])
16
+ rescue StandardError
17
+ Jekyll.logger.warn 'ActivityPub:', 'There\'s an issue with your private key'
18
+
19
+ raise
20
+ end
21
+
22
+ # We only need the public key at this point
23
+ client = DistributedPress::V1::Social::Client.new(public_key_url: nil, private_key_pem: private_key)
24
+
25
+ site.pages << site.config['actor'] = actor = Jekyll::ActivityPub::Actor.new(site)
26
+ site.pages << site.config['public_key'] = Jekyll::ActivityPub::PublicKey.new(site, actor, client.public_key)
27
+ site.pages << site.config['webfinger'] = webfinger = Jekyll::ActivityPub::WebFinger.new(site, actor)
28
+ site.pages << site.config['outbox'] = Jekyll::ActivityPub::Outbox.new(site, actor)
29
+ site.pages << site.config['host-meta'] = Jekyll::ActivityPub::HostMeta.new(site, webfinger)
30
+ site.pages << Jekyll::ActivityPub::Following.new(site, actor)
31
+ site.pages << site.config['followers'] = Jekyll::ActivityPub::Followers.new(site, actor)
32
+
33
+ # Add Actor to home page
34
+ home = site.pages.find do |page|
35
+ page.url == '/'
36
+ end
37
+
38
+ if home
39
+ Jekyll.logger.info 'ActivityPub:', 'Adding Actor to home page'
40
+ home.data['activity'] = actor
41
+ end
42
+ end
43
+
44
+ # Generate an activity for each document after the content has been
45
+ # rendered as HTML.
46
+ Jekyll::Hooks.register(:documents, :post_convert, priority: :high) do |doc|
47
+ next unless doc.write?
48
+ next if doc.data['sitemap'] == false
49
+ next if doc.data['activity'] == false
50
+
51
+ site = doc.site
52
+ dir = File.dirname(doc.url)
53
+ name = "#{File.basename(doc.url, '.html')}.jsonld"
54
+ actor = site.config['actor']
55
+ outbox = site.config['outbox']
56
+ followers = site.config['followers']
57
+
58
+ Jekyll::ActivityPub::Activity.new(site, actor, doc, nil, dir, name).tap do |activity|
59
+ doc.data['activity'] = activity
60
+
61
+ created_at = doc.data['created_at'] || doc.date
62
+ updated_at = doc.data['last_modified_at'] || doc.date
63
+
64
+ create_or_update =
65
+ if updated_at > created_at
66
+ Jekyll::ActivityPub::Update
67
+ else
68
+ Jekyll::ActivityPub::Create
69
+ end
70
+
71
+ create_or_update.new(site, actor, followers, activity).tap do |action|
72
+ Jekyll::ActivityPub::Notifier.public_send action.data['type'].downcase.to_sym, activity.destination(site.dest)
73
+
74
+ outbox.data['totalItems'] += 1
75
+ outbox.data['orderedItems'] << action
76
+ end
77
+
78
+ site.pages << activity
79
+ end
80
+ end
81
+
82
+ # This hook is shit because we don't have access to the site!
83
+ Jekyll::Hooks.register(:clean, :on_obsolete, priority: :low) do |list|
84
+ list.each do |path|
85
+ Jekyll::ActivityPub::Notifier.delete path
86
+ end
87
+ end
88
+
89
+ # Store data generated between builds and commits if
90
+ # jekyll-write-and-commit-changes is enabled.
91
+ Jekyll::Hooks.register(:site, :post_write, priority: :low) do |_|
92
+ Jekyll::ActivityPub::Notifier.save
93
+ end
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-activity-pub
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0rc0
5
+ platform: ruby
6
+ authors:
7
+ - Sutty
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: distributed-press-api-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.0rc0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.0rc0
27
+ - !ruby/object:Gem::Dependency
28
+ name: jekyll
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.14.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.14.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.6'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 3.6.0
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '3.6'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.6.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec-tap-formatters
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rubocop
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: yard
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ description:
118
+ email:
119
+ - sutty@riseup.net
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files:
123
+ - README.md
124
+ - LICENSE.txt
125
+ files:
126
+ - LICENSE.txt
127
+ - README.md
128
+ - lib/jekyll-activity-pub.rb
129
+ - lib/jekyll/activity_pub.rb
130
+ - lib/jekyll/activity_pub/activity.rb
131
+ - lib/jekyll/activity_pub/actor.rb
132
+ - lib/jekyll/activity_pub/cache.rb
133
+ - lib/jekyll/activity_pub/commands.rb
134
+ - lib/jekyll/activity_pub/create.rb
135
+ - lib/jekyll/activity_pub/delete.rb
136
+ - lib/jekyll/activity_pub/document.rb
137
+ - lib/jekyll/activity_pub/errors.rb
138
+ - lib/jekyll/activity_pub/followers.rb
139
+ - lib/jekyll/activity_pub/following.rb
140
+ - lib/jekyll/activity_pub/helper.rb
141
+ - lib/jekyll/activity_pub/host_meta.rb
142
+ - lib/jekyll/activity_pub/image.rb
143
+ - lib/jekyll/activity_pub/notifier.rb
144
+ - lib/jekyll/activity_pub/ordered_collection.rb
145
+ - lib/jekyll/activity_pub/ordered_collection_page.rb
146
+ - lib/jekyll/activity_pub/outbox.rb
147
+ - lib/jekyll/activity_pub/property_value.rb
148
+ - lib/jekyll/activity_pub/public_key.rb
149
+ - lib/jekyll/activity_pub/update.rb
150
+ - lib/jekyll/activity_pub/webfinger.rb
151
+ - lib/jekyll/command_extension.rb
152
+ - lib/jekyll/commands/generate_keys.rb
153
+ - lib/jekyll/commands/notify.rb
154
+ homepage: https://0xacab.org/sutty/jekyll/jekyll-activity-pub
155
+ licenses:
156
+ - Apache-2.0
157
+ metadata:
158
+ bug_tracker_uri: https://0xacab.org/sutty/jekyll/jekyll-activity-pub/issues
159
+ homepage_uri: https://0xacab.org/sutty/jekyll/jekyll-activity-pub
160
+ source_code_uri: https://0xacab.org/sutty/jekyll/jekyll-activity-pub
161
+ changelog_uri: https://0xacab.org/sutty/jekyll/jekyll-activity-pub/-/blob/master/CHANGELOG.md
162
+ documentation_uri: https://rubydoc.info/gems/jekyll-activity-pub
163
+ post_install_message:
164
+ rdoc_options:
165
+ - "--title"
166
+ - jekyll-activity-pub - ActivityPub support for Jekyll
167
+ - "--main"
168
+ - README.md
169
+ - "--line-numbers"
170
+ - "--inline-source"
171
+ - "--quiet"
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '2.7'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">"
182
+ - !ruby/object:Gem::Version
183
+ version: 1.3.1
184
+ requirements: []
185
+ rubygems_version: 3.3.26
186
+ signing_key:
187
+ specification_version: 4
188
+ summary: ActivityPub support for Jekyll
189
+ test_files: []