federails 0.4.0 → 0.6.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +19 -189
  4. data/app/controllers/federails/client/actors_controller.rb +18 -4
  5. data/app/controllers/federails/server/actors_controller.rb +4 -1
  6. data/app/controllers/federails/server/published_controller.rb +30 -0
  7. data/app/controllers/federails/server/web_finger_controller.rb +9 -6
  8. data/app/controllers/federails/server_controller.rb +7 -0
  9. data/app/models/concerns/federails/actor_entity.rb +120 -56
  10. data/app/models/concerns/federails/data_entity.rb +205 -0
  11. data/app/models/concerns/federails/handles_delete_requests.rb +31 -0
  12. data/app/models/concerns/federails/has_uuid.rb +27 -1
  13. data/app/models/federails/activity.rb +27 -4
  14. data/app/models/federails/actor.rb +93 -30
  15. data/app/models/federails/following.rb +29 -9
  16. data/app/policies/federails/server/publishable_policy.rb +15 -0
  17. data/app/views/federails/client/actors/_actor.json.jbuilder +4 -1
  18. data/app/views/federails/client/actors/gone.html.erb +1 -0
  19. data/app/views/federails/client/actors/index.html.erb +7 -1
  20. data/app/views/federails/server/activities/_activity.activitypub.jbuilder +8 -3
  21. data/app/views/federails/server/actors/_actor.activitypub.jbuilder +2 -2
  22. data/app/views/federails/server/actors/_tombstone.activitypub.jbuilder +9 -0
  23. data/app/views/federails/server/actors/show.activitypub.jbuilder +5 -1
  24. data/app/views/federails/server/published/_publishable.activitypub.jbuilder +11 -0
  25. data/app/views/federails/server/published/_tombstone.activitypub.jbuilder +9 -0
  26. data/app/views/federails/server/published/show.activitypub.jbuilder +5 -0
  27. data/config/routes.rb +4 -0
  28. data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
  29. data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
  30. data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
  31. data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
  32. data/lib/federails/configuration.rb +24 -1
  33. data/lib/federails/data_transformer/note.rb +31 -0
  34. data/lib/federails/maintenance/actors_updater.rb +67 -0
  35. data/lib/federails/utils/actor.rb +53 -0
  36. data/lib/federails/utils/object.rb +131 -0
  37. data/lib/federails/version.rb +1 -1
  38. data/lib/federails.rb +54 -0
  39. data/lib/fediverse/inbox.rb +40 -8
  40. data/lib/fediverse/notifier.rb +3 -0
  41. data/lib/fediverse/request.rb +13 -0
  42. data/lib/fediverse/webfinger.rb +72 -26
  43. data/lib/fediverse.rb +3 -0
  44. data/lib/tasks/federails_tasks.rake +8 -4
  45. metadata +22 -6
@@ -11,6 +11,7 @@ module Fediverse
11
11
  @id = id
12
12
  end
13
13
 
14
+ # FIXME: Replace by `Webfinger.get_json` (move other method here as class method)
14
15
  def get
15
16
  Rails.logger.debug { "GET #{@id}" }
16
17
  @response = Faraday.get(@id, nil, BASE_HEADERS)
@@ -21,6 +22,18 @@ module Fediverse
21
22
  def get(id)
22
23
  new(id).get
23
24
  end
25
+
26
+ # Dereferences a value
27
+ #
28
+ # @param value [String, Hash]
29
+ #
30
+ # @return [Hash, nil]
31
+ def dereference(value)
32
+ return value if value.is_a? Hash
33
+ return get(value) if value.is_a? String
34
+
35
+ raise "Unhandled object type #{value.class}"
36
+ end
24
37
  end
25
38
 
26
39
  private
@@ -4,39 +4,69 @@ require 'faraday/follow_redirects'
4
4
  require 'federails/utils/host'
5
5
 
6
6
  module Fediverse
7
+ # Methods related to Webfinger: find accounts, fetch actors,...
7
8
  class Webfinger
8
9
  class << self
9
10
  ACCOUNT_REGEX = /(?<username>[a-z0-9\-_.]+)(?:@(?<domain>.*))?/
10
11
 
11
- def split_resource_account(account)
12
- /\Aacct:#{ACCOUNT_REGEX}\z/io.match account
13
- end
14
-
12
+ # Extracts username and domain from an account string.
13
+ # Accepts forms "user@domain", "@user@domain" and "acct:user@domain"
14
+ #
15
+ # @param account [String] Account string
16
+ #
17
+ # @return [MatchData, nil] Matches with +:username+ and +:domain+ or +nil+
15
18
  def split_account(account)
16
- /\A#{ACCOUNT_REGEX}\z/io.match account
19
+ /\A(acct:|@)?#{ACCOUNT_REGEX}\z/io.match account
17
20
  end
18
21
 
19
- def local_user?(account)
20
- account[:username] && (account[:domain].nil? || (account[:domain] == Federails::Utils::Host.localhost))
22
+ # Determines if a given account string should be a local account (same host as configured one)
23
+ #
24
+ # @param hash [Hash, MatchData] Object with +:username+ and +:domain+ keys
25
+ #
26
+ # @return [Boolean]
27
+ def local_user?(hash)
28
+ hash[:username] && (hash[:domain].nil? || (hash[:domain] == Federails::Utils::Host.localhost))
21
29
  end
22
30
 
31
+ # Fetches a distant actor
32
+ #
33
+ # @param username [String]
34
+ # @param domain [String]
35
+ #
36
+ # @return [Federails::Actor, nil] Federails actor or nothing when not found
23
37
  def fetch_actor(username, domain)
24
38
  fetch_actor_url webfinger(username, domain)
25
39
  end
26
40
 
41
+ # Fetches an actor given its URL
42
+ #
43
+ # @param url [String] Actor's federation URL
44
+ #
45
+ # @return [Federails::Actor, nil] Federails actor or nothing when not found
27
46
  def fetch_actor_url(url)
28
47
  webfinger_to_actor get_json url
29
48
  end
30
49
 
31
- # Returns actor id
50
+ # Gets the real actor's federation URL from its username and domain
51
+ #
52
+ # @param username [String]
53
+ # @param domain [String]
54
+ #
55
+ # @return [String, nil] Federation URL if found
32
56
  def webfinger(username, domain)
33
57
  json = webfinger_response(username, domain)
34
- link = json['links'].find { |l| l['type'] == 'application/activity+json' }
58
+ link = json['links'].find { |l| Mime::Type.lookup(l['type']).to_sym == :activitypub }
35
59
 
36
60
  link['href'] if link
37
61
  end
38
62
 
39
63
  # Returns remote follow link template, or complete link if actor_url is provided
64
+ #
65
+ # @param username [String]
66
+ # @param domain [String]
67
+ # @param actor_url [String] Optional Federation URL to provide when known
68
+ #
69
+ # @return [String] The URL to use as follow URL
40
70
  def remote_follow_url(username, domain, actor_url: nil)
41
71
  json = webfinger_response(username, domain)
42
72
  link = json['links'].find { |l| l['rel'] == 'http://ostatus.org/schema/1.0/subscribe' }
@@ -51,13 +81,17 @@ module Fediverse
51
81
 
52
82
  private
53
83
 
84
+ # Makes a webfinger request for a given username/domain
85
+ # @return [Hash] Webfinger response's content
54
86
  def webfinger_response(username, domain)
55
87
  scheme = Federails.configuration.force_ssl ? 'https' : 'http'
56
88
  get_json "#{scheme}://#{domain}/.well-known/webfinger", resource: "acct:#{username}@#{domain}"
57
89
  end
58
90
 
59
- def server_and_port(id)
60
- uri = URI.parse id
91
+ # Extracts the server and port from a string, omitting common ports
92
+ # @return [String] Server and port
93
+ def server_and_port(string)
94
+ uri = URI.parse string
61
95
  if uri.port && [80, 443].exclude?(uri.port)
62
96
  "#{uri.host}:#{uri.port}"
63
97
  else
@@ -65,19 +99,29 @@ module Fediverse
65
99
  end
66
100
  end
67
101
 
68
- def webfinger_to_actor(data)
69
- Federails::Actor.new federated_url: data['id'],
70
- username: data['preferredUsername'],
71
- name: data['name'],
72
- server: server_and_port(data['id']),
73
- inbox_url: data['inbox'],
74
- outbox_url: data['outbox'],
75
- followers_url: data['followers'],
76
- followings_url: data['following'],
77
- profile_url: data['url'],
78
- public_key: data.dig('publicKey', 'publicKeyPem')
102
+ # Builds a +Federails::Actor+ from a Webfinger response
103
+ # @param data [Hash] Webfinger response
104
+ # @return [Federails::Actor]
105
+ def webfinger_to_actor(data) # rubocop:disable Metrics/MethodLength
106
+ data = data.clone
107
+ id = data.delete('id')
108
+ Federails::Actor.new federated_url: id,
109
+ username: data.delete('preferredUsername'),
110
+ actor_type: data.delete('type'),
111
+ name: data.delete('name'),
112
+ server: server_and_port(id),
113
+ inbox_url: data.delete('inbox'),
114
+ outbox_url: data.delete('outbox'),
115
+ followers_url: data.delete('followers'),
116
+ followings_url: data.delete('following'),
117
+ profile_url: data.delete('url'),
118
+ public_key: data.delete('publicKey')&.dig('publicKeyPem'),
119
+ extensions: data.except('@context')
79
120
  end
80
121
 
122
+ # Makes a simple GET request and returns a +Hash+ from the parsed body
123
+ # @return [Hash]
124
+ # @raise [ActiveRecord::RecordNotFound] when the response is invalid
81
125
  def get_json(url, payload = {})
82
126
  response = get(url, payload: payload, headers: { accept: 'application/json' })
83
127
 
@@ -93,10 +137,12 @@ module Fediverse
93
137
  raise ActiveRecord::RecordNotFound
94
138
  end
95
139
 
96
- # Only perform a GET request and throws an ActiveRecord::RecordNotFound
97
- # on error.
98
- # That's "ok-ish"; when an actor is unavailable, whatever the reason is, it's
99
- # not found...
140
+ # Only perform a GET request and throws an ActiveRecord::RecordNotFound on error.
141
+ #
142
+ # That's "ok-ish"; when an actor is unavailable, whatever the reason is, it's not found...
143
+ #
144
+ # @return [Faraday::Response]
145
+ # @raise [ActiveRecord::RecordNotFound] when the response is invalid
100
146
  def get(url, payload: {}, headers: {})
101
147
  connection = Faraday.new url: url, params: payload, headers: headers do |faraday|
102
148
  faraday.response :follow_redirects # use Faraday::FollowRedirects::Middleware
data/lib/fediverse.rb ADDED
@@ -0,0 +1,3 @@
1
+ # This module includes classes and helpers to interact with the Fediverse.
2
+ module Fediverse
3
+ end
@@ -1,4 +1,8 @@
1
- # desc "Explaining what the task does"
2
- # task :federails do
3
- # # Task goes here
4
- # end
1
+ namespace :federails do
2
+ desc 'Re-fetches every remote actors to update database'
3
+ task sync_actors: :environment do
4
+ Federails::Maintenance::ActorUpdater.run do |actor, status|
5
+ puts "#{actor.federated_url}: #{status}"
6
+ end
7
+ end
8
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: federails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Tancoigne
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-02 00:00:00.000000000 Z
10
+ date: 2025-04-07 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -115,6 +114,7 @@ executables: []
115
114
  extensions: []
116
115
  extra_rdoc_files: []
117
116
  files:
117
+ - LICENSE
118
118
  - README.md
119
119
  - Rakefile
120
120
  - app/assets/config/federails_manifest.js
@@ -127,6 +127,7 @@ files:
127
127
  - app/controllers/federails/server/actors_controller.rb
128
128
  - app/controllers/federails/server/followings_controller.rb
129
129
  - app/controllers/federails/server/nodeinfo_controller.rb
130
+ - app/controllers/federails/server/published_controller.rb
130
131
  - app/controllers/federails/server/web_finger_controller.rb
131
132
  - app/controllers/federails/server_controller.rb
132
133
  - app/helpers/federails/server_helper.rb
@@ -134,6 +135,8 @@ files:
134
135
  - app/jobs/federails/notify_inbox_job.rb
135
136
  - app/mailers/federails/application_mailer.rb
136
137
  - app/models/concerns/federails/actor_entity.rb
138
+ - app/models/concerns/federails/data_entity.rb
139
+ - app/models/concerns/federails/handles_delete_requests.rb
137
140
  - app/models/concerns/federails/has_uuid.rb
138
141
  - app/models/federails/activity.rb
139
142
  - app/models/federails/actor.rb
@@ -146,6 +149,7 @@ files:
146
149
  - app/policies/federails/server/activity_policy.rb
147
150
  - app/policies/federails/server/actor_policy.rb
148
151
  - app/policies/federails/server/following_policy.rb
152
+ - app/policies/federails/server/publishable_policy.rb
149
153
  - app/views/federails/client/activities/_activity.html.erb
150
154
  - app/views/federails/client/activities/_activity.json.jbuilder
151
155
  - app/views/federails/client/activities/_index.json.jbuilder
@@ -155,6 +159,7 @@ files:
155
159
  - app/views/federails/client/activities/index.json.jbuilder
156
160
  - app/views/federails/client/actors/_actor.json.jbuilder
157
161
  - app/views/federails/client/actors/_lookup_form.html.erb
162
+ - app/views/federails/client/actors/gone.html.erb
158
163
  - app/views/federails/client/actors/index.html.erb
159
164
  - app/views/federails/client/actors/index.json.jbuilder
160
165
  - app/views/federails/client/actors/show.html.erb
@@ -173,6 +178,7 @@ files:
173
178
  - app/views/federails/server/activities/outbox.activitypub.jbuilder
174
179
  - app/views/federails/server/activities/show.activitypub.jbuilder
175
180
  - app/views/federails/server/actors/_actor.activitypub.jbuilder
181
+ - app/views/federails/server/actors/_tombstone.activitypub.jbuilder
176
182
  - app/views/federails/server/actors/followers.activitypub.jbuilder
177
183
  - app/views/federails/server/actors/following.activitypub.jbuilder
178
184
  - app/views/federails/server/actors/show.activitypub.jbuilder
@@ -180,6 +186,9 @@ files:
180
186
  - app/views/federails/server/followings/show.activitypub.jbuilder
181
187
  - app/views/federails/server/nodeinfo/index.nodeinfo.jbuilder
182
188
  - app/views/federails/server/nodeinfo/show.nodeinfo.jbuilder
189
+ - app/views/federails/server/published/_publishable.activitypub.jbuilder
190
+ - app/views/federails/server/published/_tombstone.activitypub.jbuilder
191
+ - app/views/federails/server/published/show.activitypub.jbuilder
183
192
  - app/views/federails/server/web_finger/find.jrd.jbuilder
184
193
  - app/views/federails/server/web_finger/host_meta.xrd.erb
185
194
  - config/initializers/mime_types.rb
@@ -189,11 +198,20 @@ files:
189
198
  - db/migrate/20200712174938_create_federails_activities.rb
190
199
  - db/migrate/20241002094500_add_uuids.rb
191
200
  - db/migrate/20241002094501_add_keypair_to_actors.rb
201
+ - db/migrate/20250122160618_add_extensions_to_federails_actors.rb
202
+ - db/migrate/20250301082500_add_local_to_actors.rb
203
+ - db/migrate/20250329123939_add_actor_type_to_actors.rb
204
+ - db/migrate/20250329123940_add_tombstoned_at_to_actors.rb
192
205
  - lib/federails.rb
193
206
  - lib/federails/configuration.rb
207
+ - lib/federails/data_transformer/note.rb
194
208
  - lib/federails/engine.rb
209
+ - lib/federails/maintenance/actors_updater.rb
210
+ - lib/federails/utils/actor.rb
195
211
  - lib/federails/utils/host.rb
212
+ - lib/federails/utils/object.rb
196
213
  - lib/federails/version.rb
214
+ - lib/fediverse.rb
197
215
  - lib/fediverse/inbox.rb
198
216
  - lib/fediverse/notifier.rb
199
217
  - lib/fediverse/request.rb
@@ -215,7 +233,6 @@ metadata:
215
233
  homepage_uri: https://experimentslabs.com
216
234
  source_code_uri: https://gitlab.com/experimentslabs/federails/
217
235
  changelog_uri: https://gitlab.com/experimentslabs/federails/-/blob/main/CHANGELOG.md
218
- post_install_message:
219
236
  rdoc_options: []
220
237
  require_paths:
221
238
  - lib
@@ -230,8 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
230
247
  - !ruby/object:Gem::Version
231
248
  version: '0'
232
249
  requirements: []
233
- rubygems_version: 3.3.7
234
- signing_key:
250
+ rubygems_version: 3.6.5
235
251
  specification_version: 4
236
252
  summary: An ActivityPub engine for Ruby on Rails
237
253
  test_files: []