federails 0.6.2 → 0.8.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.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/app/controllers/concerns/federails/server/render_collections.rb +19 -0
- data/app/controllers/federails/client/actors_controller.rb +1 -1
- data/app/controllers/federails/client/followings_controller.rb +4 -4
- data/app/controllers/federails/server/activities_controller.rb +14 -6
- data/app/controllers/federails/server/actors_controller.rb +16 -7
- data/app/controllers/federails/server/nodeinfo_controller.rb +2 -2
- data/app/helpers/federails/server_helper.rb +6 -0
- data/app/jobs/federails/application_job.rb +2 -0
- data/app/jobs/federails/fetch_nodeinfo_job.rb +10 -0
- data/app/jobs/federails/notify_inbox_job.rb +0 -2
- data/app/models/concerns/federails/actor_entity.rb +5 -1
- data/app/models/concerns/federails/data_entity.rb +80 -8
- data/app/models/concerns/federails/handles_delete_requests.rb +5 -0
- data/app/models/federails/activity.rb +15 -22
- data/app/models/federails/actor.rb +44 -3
- data/app/models/federails/following.rb +3 -3
- data/app/models/federails/host.rb +48 -0
- data/app/views/federails/client/common/_client_links.html.erb +3 -1
- data/app/views/federails/client/followings/_follow_actions.html.erb +5 -1
- data/app/views/federails/client/followings/_follower.html.erb +1 -1
- data/app/views/federails/server/activities/_activity.activitypub.jbuilder +7 -3
- data/app/views/federails/server/actors/_actor.activitypub.jbuilder +7 -6
- data/app/views/federails/server/actors/_tombstone.activitypub.jbuilder +1 -4
- data/app/views/federails/server/followings/_following.activitypub.jbuilder +1 -1
- data/app/views/federails/server/nodeinfo/index.nodeinfo.jbuilder +1 -1
- data/app/views/federails/server/nodeinfo/show.nodeinfo.jbuilder +2 -3
- data/app/views/federails/server/published/_publishable.activitypub.jbuilder +2 -2
- data/app/views/federails/server/published/_tombstone.activitypub.jbuilder +1 -4
- data/app/views/federails/server/shared/ordered_collection.activitypub.jbuilder +6 -0
- data/app/views/federails/server/shared/ordered_collection_page.activitypub.jbuilder +12 -0
- data/app/views/users/show.html.erb +4 -0
- data/db/migrate/20250426061729_create_federails_hosts.rb +22 -0
- data/db/migrate/20251121160720_add_to_and_cc_to_federails_activities.rb +6 -0
- data/lib/federails/configuration.rb +25 -0
- data/lib/federails/data_transformer/note.rb +6 -1
- data/lib/federails/maintenance/actors_updater.rb +3 -4
- data/lib/federails/maintenance/hosts_updater.rb +19 -0
- data/lib/federails/utils/actor.rb +33 -1
- data/lib/federails/utils/context.rb +12 -0
- data/lib/federails/utils/json_request.rb +41 -0
- data/lib/federails/utils/object.rb +1 -1
- data/lib/federails/utils/response_codes.rb +11 -0
- data/lib/federails/version.rb +1 -1
- data/lib/federails.rb +16 -3
- data/lib/fediverse/collection.rb +31 -0
- data/lib/fediverse/inbox.rb +10 -0
- data/lib/fediverse/node_info.rb +38 -0
- data/lib/fediverse/notifier.rb +39 -12
- data/lib/fediverse/request.rb +6 -28
- data/lib/fediverse/signature.rb +1 -1
- data/lib/fediverse/webfinger.rb +10 -33
- data/lib/fediverse.rb +2 -0
- data/lib/generators/federails/copy_client_policies/USAGE +8 -0
- data/lib/generators/federails/copy_client_policies/copy_client_policies_generator.rb +9 -0
- data/lib/generators/federails/copy_factories/USAGE +8 -0
- data/lib/generators/federails/copy_factories/copy_factories_generator.rb +43 -0
- data/lib/generators/federails/install/templates/federails.yml +2 -0
- data/lib/tasks/federails_tasks.rake +5 -0
- metadata +38 -6
- data/app/views/federails/server/activities/outbox.activitypub.jbuilder +0 -18
- data/app/views/federails/server/actors/followers.activitypub.jbuilder +0 -18
- data/app/views/federails/server/actors/following.activitypub.jbuilder +0 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div>
|
|
2
2
|
<b><%= link_to following.actor.name, federails.client_actor_url(following.actor) %></b>
|
|
3
3
|
(<%= following.actor.at_address %>) (<%= following.status %>)
|
|
4
|
-
<% if following.pending? && following.target_actor == current_user
|
|
4
|
+
<% if following.pending? && following.target_actor == current_user&.federails_actor %>
|
|
5
5
|
<%= button_to 'Accept', federails.accept_client_following_path(following), method: :put %>
|
|
6
6
|
<% end %>
|
|
7
7
|
</div>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
context = true unless context == false
|
|
2
2
|
addressing = true unless addressing == false
|
|
3
|
-
json
|
|
3
|
+
set_json_ld_context(json) if context
|
|
4
4
|
|
|
5
5
|
json.id Federails::Engine.routes.url_helpers.server_actor_activity_url activity.actor, activity
|
|
6
6
|
json.type activity.action
|
|
7
7
|
json.actor activity.actor.federated_url
|
|
8
8
|
if addressing
|
|
9
|
-
json.
|
|
10
|
-
|
|
9
|
+
json.merge!(
|
|
10
|
+
{
|
|
11
|
+
to: activity.to,
|
|
12
|
+
cc: activity.cc,
|
|
13
|
+
}.compact
|
|
14
|
+
)
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
if activity.entity.is_a? Federails::Activity
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
actor_data = actor.entity&.to_activitypub_object || {}
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
]
|
|
3
|
+
set_json_ld_context(
|
|
4
|
+
json,
|
|
5
|
+
additional: [
|
|
6
|
+
'https://w3id.org/security/v1',
|
|
7
|
+
actor_data.delete(:@context),
|
|
8
|
+
]
|
|
9
|
+
)
|
|
9
10
|
|
|
10
11
|
json.id actor.federated_url
|
|
11
12
|
json.name actor.name
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
json.version '2.0'
|
|
2
|
-
|
|
3
|
-
json.software name: Federails::Configuration.app_name,
|
|
2
|
+
json.software name: Federails::Configuration.app_name&.parameterize,
|
|
4
3
|
version: Federails::Configuration.app_version
|
|
5
4
|
json.protocols [
|
|
6
5
|
'activitypub',
|
|
@@ -17,4 +16,4 @@ if @has_user_counts
|
|
|
17
16
|
activeHalfyear: @active_halfyear,
|
|
18
17
|
}
|
|
19
18
|
end
|
|
20
|
-
json.metadata({})
|
|
19
|
+
json.metadata(Federails::Configuration.nodeinfo_metadata || {})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
context = true unless context == false
|
|
2
|
-
json
|
|
2
|
+
set_json_ld_context(json) if context
|
|
3
3
|
|
|
4
4
|
publishable.to_activitypub_object.each_pair do |key, value|
|
|
5
5
|
json.set! key, value
|
|
@@ -7,5 +7,5 @@ end
|
|
|
7
7
|
|
|
8
8
|
json.id publishable.federated_url
|
|
9
9
|
json.actor publishable.federails_actor.federated_url
|
|
10
|
-
json.to [
|
|
10
|
+
json.to [Fediverse::Collection::PUBLIC]
|
|
11
11
|
json.cc [publishable.federails_actor.followers_url]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
set_json_ld_context(json)
|
|
2
|
+
json.type 'OrderedCollectionPage'
|
|
3
|
+
json.id send(url_helper, actor, page: collection.current_page)
|
|
4
|
+
json.partOf send(url_helper, actor)
|
|
5
|
+
json.first send(url_helper, actor, page: 1)
|
|
6
|
+
json.last send(url_helper, actor, page: collection.total_pages)
|
|
7
|
+
json.next send(url_helper, actor, page: collection.next_page) if collection.next_page
|
|
8
|
+
json.prev send(url_helper, actor, page: collection.prev_page) if collection.prev_page
|
|
9
|
+
json.totalItems collection.total_count
|
|
10
|
+
json.orderedItems do
|
|
11
|
+
items_block.call(json, collection)
|
|
12
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class CreateFederailsHosts < ActiveRecord::Migration[7.2]
|
|
2
|
+
def change
|
|
3
|
+
create_table :federails_hosts do |t|
|
|
4
|
+
t.string :domain, null: false, default: nil
|
|
5
|
+
t.string :nodeinfo_url
|
|
6
|
+
t.string :software_name
|
|
7
|
+
t.string :software_version
|
|
8
|
+
|
|
9
|
+
# Uncomment the lines below if you use PostgreSQL
|
|
10
|
+
# t.jsonb :protocols, default: []
|
|
11
|
+
# t.jsonb :services, default: {}
|
|
12
|
+
#
|
|
13
|
+
# Other databases
|
|
14
|
+
t.text :protocols, default: '[]'
|
|
15
|
+
t.text :services, default: '{}'
|
|
16
|
+
|
|
17
|
+
t.timestamps
|
|
18
|
+
|
|
19
|
+
t.index :domain, unique: true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -35,6 +35,14 @@ module Federails
|
|
|
35
35
|
@@open_registrations.is_a?(Proc) ? @@open_registrations.call : @@open_registrations
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# Custom metadata for nodeinfo
|
|
39
|
+
# Can either be a static hash, or a Proc which will be called to get the state.
|
|
40
|
+
mattr_writer :nodeinfo_metadata
|
|
41
|
+
@@nodeinfo_metadata = {}
|
|
42
|
+
def self.nodeinfo_metadata
|
|
43
|
+
@@nodeinfo_metadata.is_a?(Proc) ? @@nodeinfo_metadata.call : @@nodeinfo_metadata
|
|
44
|
+
end
|
|
45
|
+
|
|
38
46
|
# Application layout
|
|
39
47
|
mattr_accessor :app_layout
|
|
40
48
|
@@app_layout = nil
|
|
@@ -58,6 +66,13 @@ module Federails
|
|
|
58
66
|
# @!method self.remote_follow_url_method=(value)
|
|
59
67
|
#
|
|
60
68
|
# Sets the route method for remote-following requests
|
|
69
|
+
#
|
|
70
|
+
# The route should lead to a page displaying the remote actor and a button to follow it.
|
|
71
|
+
# Remote actor is specified in the `uri` query parameter.
|
|
72
|
+
#
|
|
73
|
+
# Its value defaults to a route of the Federails client, so your application will break
|
|
74
|
+
# if you don't use the client routes and don't override this value.
|
|
75
|
+
#
|
|
61
76
|
# @param value [String] Route method name as used in links
|
|
62
77
|
# @example
|
|
63
78
|
# remote_follow_url_method 'main_app.my_custom_route_helper'
|
|
@@ -74,6 +89,16 @@ module Federails
|
|
|
74
89
|
Federails::Engine.routes.default_url_options[:port] = value
|
|
75
90
|
end
|
|
76
91
|
|
|
92
|
+
# Default amount of seconds to consider that a remote entity could be updated
|
|
93
|
+
#
|
|
94
|
+
# This setting is used for hosts information only, for now.
|
|
95
|
+
mattr_accessor :remote_entities_cache_duration
|
|
96
|
+
@@remote_entities_cache_duration = 1.day
|
|
97
|
+
|
|
98
|
+
# Job queue name
|
|
99
|
+
mattr_accessor :job_queue
|
|
100
|
+
@@job_queue = :default
|
|
101
|
+
|
|
77
102
|
# List of actor types (classes using Federails::ActorEntity)
|
|
78
103
|
mattr_reader :actor_types
|
|
79
104
|
@@actor_types = {}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'federails/utils/context'
|
|
2
|
+
|
|
1
3
|
module Federails
|
|
2
4
|
module DataTransformer
|
|
3
5
|
module Note
|
|
@@ -17,7 +19,10 @@ module Federails
|
|
|
17
19
|
# - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object
|
|
18
20
|
# - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
|
|
19
21
|
def self.to_federation(entity, content:, name: nil, custom: {})
|
|
20
|
-
custom
|
|
22
|
+
# Merge default and custom contexts
|
|
23
|
+
context = Utils::Context.generate(additional: custom.delete('@context'))
|
|
24
|
+
# Merge in standard Note fields
|
|
25
|
+
custom.merge '@context' => context,
|
|
21
26
|
'id' => entity.federated_url,
|
|
22
27
|
'type' => 'Note',
|
|
23
28
|
'name' => name,
|
|
@@ -54,12 +54,11 @@ module Federails
|
|
|
54
54
|
def update(actor)
|
|
55
55
|
return :ignored_local if actor.local?
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
new_attributes = response.attributes.except 'id', 'uuid', 'created_at', 'updated_at', 'local', 'entity_id', 'entity_type'
|
|
59
|
-
|
|
60
|
-
actor.update(new_attributes) ? :updated : :failed
|
|
57
|
+
actor.sync! ? :updated : :failed
|
|
61
58
|
rescue ActiveRecord::RecordNotFound
|
|
62
59
|
:not_found
|
|
60
|
+
rescue StandardError
|
|
61
|
+
:failed
|
|
63
62
|
end
|
|
64
63
|
end
|
|
65
64
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Federails
|
|
2
|
+
module Maintenance
|
|
3
|
+
class HostsUpdater
|
|
4
|
+
class << self
|
|
5
|
+
# Update information for all known hosts, and complete if some are missing
|
|
6
|
+
def run(cache_interval: nil)
|
|
7
|
+
cache_interval ||= Federails::Configuration.remote_entities_cache_duration
|
|
8
|
+
|
|
9
|
+
domains = Federails::Actor.distant.distinct(:server).pluck(:server) + Federails::Host.pluck(:domain)
|
|
10
|
+
domains.uniq!
|
|
11
|
+
|
|
12
|
+
domains.each do |domain|
|
|
13
|
+
Federails::Host.create_or_update domain, min_update_interval: cache_interval
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -27,13 +27,21 @@ module Federails
|
|
|
27
27
|
actor
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
def untombstone!(actor)
|
|
31
|
+
if actor.local?
|
|
32
|
+
untombstone_local_actor actor
|
|
33
|
+
else
|
|
34
|
+
untombstone_distant_actor actor
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
30
38
|
private
|
|
31
39
|
|
|
32
40
|
def tombstone_local_actor(actor)
|
|
33
41
|
Federails::Actor.transaction do
|
|
34
42
|
hash = {
|
|
35
43
|
tombstoned_at: Time.current,
|
|
36
|
-
entity: nil,
|
|
44
|
+
entity: actor.entity || nil,
|
|
37
45
|
}
|
|
38
46
|
# Hardcode attributes depending on the actor's entity
|
|
39
47
|
COMPUTED_ATTRIBUTES.each { |attribute| hash[attribute] = actor.send(attribute) }
|
|
@@ -44,9 +52,33 @@ module Federails
|
|
|
44
52
|
end
|
|
45
53
|
end
|
|
46
54
|
|
|
55
|
+
def untombstone_local_actor(actor)
|
|
56
|
+
return unless actor.tombstoned?
|
|
57
|
+
raise 'Cannot restore a local actor without an entity' if actor.entity.blank?
|
|
58
|
+
|
|
59
|
+
Federails::Actor.transaction do
|
|
60
|
+
# Reset hardcoded attributes depending on the actor's entity
|
|
61
|
+
hash = { tombstoned_at: nil }
|
|
62
|
+
COMPUTED_ATTRIBUTES.each { |attribute| hash[attribute] = nil }
|
|
63
|
+
|
|
64
|
+
actor.update! hash
|
|
65
|
+
|
|
66
|
+
delete_activity = Activity.find_by actor: actor, action: 'Delete', entity: actor
|
|
67
|
+
return unless delete_activity
|
|
68
|
+
|
|
69
|
+
Activity.create! actor: actor, action: 'Undo', entity: delete_activity
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
47
73
|
def tombstone_distant_actor(actor)
|
|
48
74
|
actor.update! tombstoned_at: Time.current
|
|
49
75
|
end
|
|
76
|
+
|
|
77
|
+
def untombstone_distant_actor(actor)
|
|
78
|
+
actor.tombstoned_at = nil
|
|
79
|
+
actor.sync!
|
|
80
|
+
actor.save
|
|
81
|
+
end
|
|
50
82
|
end
|
|
51
83
|
end
|
|
52
84
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Federails
|
|
2
|
+
module Utils
|
|
3
|
+
module Context
|
|
4
|
+
class << self
|
|
5
|
+
def generate(additional: nil)
|
|
6
|
+
activity_streams = 'https://www.w3.org/ns/activitystreams'
|
|
7
|
+
additional.nil? ? activity_streams : [activity_streams, additional].flatten.compact
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'faraday/follow_redirects'
|
|
3
|
+
|
|
4
|
+
module Federails
|
|
5
|
+
module Utils
|
|
6
|
+
# Simple wrapper to make requests expecting JSON
|
|
7
|
+
module JsonRequest
|
|
8
|
+
class UnhandledResponseStatus < StandardError; end
|
|
9
|
+
|
|
10
|
+
BASE_HEADERS = {
|
|
11
|
+
'Content-Type' => 'application/json',
|
|
12
|
+
'Accept' => 'application/json',
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
# Makes a GET request and returns a +Hash+ from the parsed body
|
|
16
|
+
#
|
|
17
|
+
# @param url [String] Target URL
|
|
18
|
+
# @param params [Hash] Querystring parameters
|
|
19
|
+
# @param headers [Hash] Additional headers
|
|
20
|
+
# @param follow_redirects [Boolean] Whether to follow redirections
|
|
21
|
+
# @param expected_status [Integer] Expected response status. Will raise a +UnhandledResponseStatus+ when status is different
|
|
22
|
+
#
|
|
23
|
+
# @return The parsed JSON object
|
|
24
|
+
#
|
|
25
|
+
# @raise [UnhandledResponseStatus] when response status is not the expected_status
|
|
26
|
+
def self.get_json(url, params: {}, headers: {}, follow_redirects: false, expected_status: 200)
|
|
27
|
+
headers = BASE_HEADERS.merge headers
|
|
28
|
+
|
|
29
|
+
connection = Faraday.new url: url, params: params, headers: headers do |faraday|
|
|
30
|
+
faraday.response :follow_redirects if follow_redirects
|
|
31
|
+
faraday.adapter Faraday.default_adapter
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
response = connection.get
|
|
35
|
+
raise UnhandledResponseStatus, "Unhandled status code #{response.status} for GET #{url}" if expected_status && response.status != expected_status
|
|
36
|
+
|
|
37
|
+
JSON.parse(response.body)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -121,7 +121,7 @@ module Federails
|
|
|
121
121
|
entity = handler[:class].new_from_activitypub_object(hash)
|
|
122
122
|
return unless entity
|
|
123
123
|
|
|
124
|
-
entity.federails_actor = Federails::Actor.
|
|
124
|
+
entity.federails_actor = Federails::Actor.find_by_federation_url hash['attributedTo'] if entity && !entity.federails_actor
|
|
125
125
|
|
|
126
126
|
entity
|
|
127
127
|
end
|
data/lib/federails/version.rb
CHANGED
data/lib/federails.rb
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
require 'jbuilder'
|
|
2
|
+
require 'kaminari'
|
|
3
|
+
require 'pundit'
|
|
4
|
+
|
|
1
5
|
require 'federails/version'
|
|
2
6
|
require 'federails/engine'
|
|
3
7
|
require 'federails/configuration'
|
|
4
8
|
require 'federails/utils/object'
|
|
9
|
+
require 'federails/utils/json_request'
|
|
10
|
+
require 'federails/utils/response_codes'
|
|
11
|
+
|
|
12
|
+
require 'fediverse'
|
|
5
13
|
|
|
6
14
|
# rubocop:disable Style/ClassVars
|
|
7
15
|
|
|
@@ -30,11 +38,14 @@ module Federails
|
|
|
30
38
|
:site_port,
|
|
31
39
|
:enable_discovery,
|
|
32
40
|
:open_registrations,
|
|
41
|
+
:nodeinfo_metadata,
|
|
33
42
|
:app_layout,
|
|
34
43
|
:server_routes_path,
|
|
35
44
|
:client_routes_path,
|
|
36
45
|
:remote_follow_url_method,
|
|
37
46
|
:base_client_controller,
|
|
47
|
+
:remote_entities_cache_duration,
|
|
48
|
+
:job_queue,
|
|
38
49
|
].each { |key| Configuration.send :"#{key}=", config[key] if config.key?(key) }
|
|
39
50
|
end
|
|
40
51
|
|
|
@@ -77,9 +88,11 @@ module Federails
|
|
|
77
88
|
# @return [Hash, nil] Data entity configuration
|
|
78
89
|
def data_entity_handler_for(hash)
|
|
79
90
|
data_entity_handlers_for(hash['type']).find do |handler|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
if handler[:filter_method] || handler[:class].respond_to?(DEFAULT_DATA_FILTER_METHOD)
|
|
92
|
+
handler[:class].send(handler[:filter_method] || DEFAULT_DATA_FILTER_METHOD, hash)
|
|
93
|
+
else
|
|
94
|
+
true
|
|
95
|
+
end
|
|
83
96
|
end
|
|
84
97
|
end
|
|
85
98
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Fediverse
|
|
2
|
+
class Collection < Array
|
|
3
|
+
PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'.freeze
|
|
4
|
+
|
|
5
|
+
attr_reader :total_items, :id, :type
|
|
6
|
+
|
|
7
|
+
def self.fetch(url)
|
|
8
|
+
new.fetch(url)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def fetch(url)
|
|
12
|
+
json = Fediverse::Request.dereference(url)
|
|
13
|
+
@total_items = json['totalItems']
|
|
14
|
+
@id = json['id']
|
|
15
|
+
@type = json['type']
|
|
16
|
+
raise Errors::NotACollection unless %w[OrderedCollection Collection].include?(@type)
|
|
17
|
+
|
|
18
|
+
next_url = json['first']
|
|
19
|
+
while next_url
|
|
20
|
+
page = Fediverse::Request.dereference(next_url)
|
|
21
|
+
concat(page['orderedItems'] || page['items'])
|
|
22
|
+
next_url = page['next']
|
|
23
|
+
end
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module Errors
|
|
29
|
+
class NotACollection < StandardError; end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/fediverse/inbox.rb
CHANGED
|
@@ -90,11 +90,21 @@ module Fediverse
|
|
|
90
90
|
|
|
91
91
|
object.run_callbacks :on_federails_delete_requested
|
|
92
92
|
end
|
|
93
|
+
|
|
94
|
+
def handle_undelete_request(activity)
|
|
95
|
+
# Get to original object
|
|
96
|
+
delete_activity = Request.dereference(activity['object'])
|
|
97
|
+
object = Federails::Utils::Object.find_distant_object_in_all(delete_activity['object'])
|
|
98
|
+
return if object.blank?
|
|
99
|
+
|
|
100
|
+
object.run_callbacks :on_federails_undelete_requested
|
|
101
|
+
end
|
|
93
102
|
end
|
|
94
103
|
|
|
95
104
|
register_handler 'Follow', '*', self, :handle_create_follow_request
|
|
96
105
|
register_handler 'Accept', 'Follow', self, :handle_accept_follow_request
|
|
97
106
|
register_handler 'Undo', 'Follow', self, :handle_undo_follow_request
|
|
98
107
|
register_handler 'Delete', '*', self, :handle_delete_request
|
|
108
|
+
register_handler 'Undo', 'Delete', self, :handle_undelete_request
|
|
99
109
|
end
|
|
100
110
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Fediverse
|
|
2
|
+
class NodeInfo
|
|
3
|
+
class NotFoundError < StandardError; end
|
|
4
|
+
class NoActivityPubError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def fetch(domain)
|
|
8
|
+
url = nodeinfo_url(domain)
|
|
9
|
+
|
|
10
|
+
hash = Federails::Utils::JsonRequest.get_json url
|
|
11
|
+
raise NoActivityPubError, "#{domain} does not handle activitypub protocol" unless hash['protocols'].include? 'activitypub'
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
domain: domain,
|
|
15
|
+
nodeinfo_url: url,
|
|
16
|
+
software_version: hash.dig('software', 'version'),
|
|
17
|
+
software_name: hash.dig('software', 'name'),
|
|
18
|
+
protocols: hash['protocols'],
|
|
19
|
+
services: hash['services'],
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def base_url(domain)
|
|
26
|
+
scheme = Federails::Configuration.force_ssl ? 'https' : 'http'
|
|
27
|
+
@base_url = "#{scheme}://#{domain}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def nodeinfo_url(domain)
|
|
31
|
+
response = Federails::Utils::JsonRequest.get_json "#{base_url(domain)}/.well-known/nodeinfo", follow_redirects: true
|
|
32
|
+
entry = response['links']&.find { |link| link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0' }
|
|
33
|
+
|
|
34
|
+
entry['href']
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/fediverse/notifier.rb
CHANGED
|
@@ -7,18 +7,45 @@ module Fediverse
|
|
|
7
7
|
#
|
|
8
8
|
# @param activity [Federails::Activity]
|
|
9
9
|
def post_to_inboxes(activity)
|
|
10
|
-
actors
|
|
11
|
-
|
|
10
|
+
# Get the list of actors we need to send the activity to
|
|
11
|
+
inboxes = inboxes_for(activity)
|
|
12
|
+
Rails.logger.debug('Nobody to notice') && return if inboxes.none?
|
|
12
13
|
|
|
14
|
+
# Deliver to each inbox
|
|
13
15
|
message = payload(activity)
|
|
14
|
-
|
|
15
|
-
Rails.logger.debug { "Sending activity ##{activity.id} to #{
|
|
16
|
-
post_to_inbox(
|
|
16
|
+
inboxes.each do |url|
|
|
17
|
+
Rails.logger.debug { "Sending activity ##{activity.id} to inbox at #{url}" }
|
|
18
|
+
post_to_inbox(inbox_url: url, message: message, from: activity.actor)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
private
|
|
21
23
|
|
|
24
|
+
# Determines the list of inboxes that the activity should be delivered to
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<Federails::Actor>]
|
|
27
|
+
def inboxes_for(activity)
|
|
28
|
+
return [] unless activity.actor.local?
|
|
29
|
+
|
|
30
|
+
[activity.to, activity.cc].flatten.compact.reject { |x| x == Fediverse::Collection::PUBLIC }.map do |url|
|
|
31
|
+
actor = Federails::Actor.find_or_create_by_federation_url(url)
|
|
32
|
+
[actor.inbox_url]
|
|
33
|
+
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
|
|
34
|
+
collection_to_actors(url).map(&:inbox_url)
|
|
35
|
+
end.flatten.compact
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def collection_to_actors(url)
|
|
39
|
+
collection = Collection.fetch(url)
|
|
40
|
+
collection.filter_map do |actor_url|
|
|
41
|
+
Federails::Actor.find_or_create_by_federation_url(actor_url)
|
|
42
|
+
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
rescue Errors::NotACollection
|
|
46
|
+
[]
|
|
47
|
+
end
|
|
48
|
+
|
|
22
49
|
def payload(activity)
|
|
23
50
|
Federails::ServerController.renderer.new.render(
|
|
24
51
|
template: 'federails/server/activities/show',
|
|
@@ -27,27 +54,27 @@ module Fediverse
|
|
|
27
54
|
)
|
|
28
55
|
end
|
|
29
56
|
|
|
30
|
-
def post_to_inbox(
|
|
57
|
+
def post_to_inbox(inbox_url:, message:, from: nil)
|
|
31
58
|
conn = Faraday.default_connection
|
|
32
59
|
conn.builder.build_response(
|
|
33
60
|
conn,
|
|
34
|
-
signed_request(
|
|
61
|
+
signed_request(url: inbox_url, message: message, from: from)
|
|
35
62
|
)
|
|
36
63
|
end
|
|
37
64
|
|
|
38
|
-
def signed_request(
|
|
39
|
-
req = request(
|
|
65
|
+
def signed_request(url:, message:, from:)
|
|
66
|
+
req = request(url: url, message: message)
|
|
40
67
|
req.headers['Signature'] = Fediverse::Signature.sign(sender: from, request: req) if from
|
|
41
68
|
req
|
|
42
69
|
end
|
|
43
70
|
|
|
44
|
-
def request(
|
|
71
|
+
def request(url:, message:) # rubocop:todo Metrics/AbcSize
|
|
45
72
|
Faraday.default_connection.build_request(:post) do |req|
|
|
46
|
-
req.url
|
|
73
|
+
req.url url
|
|
47
74
|
req.body = message
|
|
48
75
|
req.headers['Content-Type'] = Mime[:activitypub].to_s
|
|
49
76
|
req.headers['Accept'] = Mime[:activitypub].to_s
|
|
50
|
-
req.headers['Host'] = URI.parse(
|
|
77
|
+
req.headers['Host'] = URI.parse(url).host
|
|
51
78
|
req.headers['Date'] = Time.now.utc.httpdate
|
|
52
79
|
req.headers['Digest'] = digest(message)
|
|
53
80
|
end
|