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.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +19 -189
- data/app/controllers/federails/client/actors_controller.rb +18 -4
- data/app/controllers/federails/server/actors_controller.rb +4 -1
- data/app/controllers/federails/server/published_controller.rb +30 -0
- data/app/controllers/federails/server/web_finger_controller.rb +9 -6
- data/app/controllers/federails/server_controller.rb +7 -0
- data/app/models/concerns/federails/actor_entity.rb +120 -56
- data/app/models/concerns/federails/data_entity.rb +205 -0
- data/app/models/concerns/federails/handles_delete_requests.rb +31 -0
- data/app/models/concerns/federails/has_uuid.rb +27 -1
- data/app/models/federails/activity.rb +27 -4
- data/app/models/federails/actor.rb +93 -30
- data/app/models/federails/following.rb +29 -9
- data/app/policies/federails/server/publishable_policy.rb +15 -0
- data/app/views/federails/client/actors/_actor.json.jbuilder +4 -1
- data/app/views/federails/client/actors/gone.html.erb +1 -0
- data/app/views/federails/client/actors/index.html.erb +7 -1
- data/app/views/federails/server/activities/_activity.activitypub.jbuilder +8 -3
- data/app/views/federails/server/actors/_actor.activitypub.jbuilder +2 -2
- data/app/views/federails/server/actors/_tombstone.activitypub.jbuilder +9 -0
- data/app/views/federails/server/actors/show.activitypub.jbuilder +5 -1
- data/app/views/federails/server/published/_publishable.activitypub.jbuilder +11 -0
- data/app/views/federails/server/published/_tombstone.activitypub.jbuilder +9 -0
- data/app/views/federails/server/published/show.activitypub.jbuilder +5 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20250122160618_add_extensions_to_federails_actors.rb +5 -0
- data/db/migrate/20250301082500_add_local_to_actors.rb +11 -0
- data/db/migrate/20250329123939_add_actor_type_to_actors.rb +5 -0
- data/db/migrate/20250329123940_add_tombstoned_at_to_actors.rb +5 -0
- data/lib/federails/configuration.rb +24 -1
- data/lib/federails/data_transformer/note.rb +31 -0
- data/lib/federails/maintenance/actors_updater.rb +67 -0
- data/lib/federails/utils/actor.rb +53 -0
- data/lib/federails/utils/object.rb +131 -0
- data/lib/federails/version.rb +1 -1
- data/lib/federails.rb +54 -0
- data/lib/fediverse/inbox.rb +40 -8
- data/lib/fediverse/notifier.rb +3 -0
- data/lib/fediverse/request.rb +13 -0
- data/lib/fediverse/webfinger.rb +72 -26
- data/lib/fediverse.rb +3 -0
- data/lib/tasks/federails_tasks.rake +8 -4
- metadata +22 -6
@@ -1,13 +1,18 @@
|
|
1
1
|
context = true unless context == false
|
2
|
+
addressing = true unless addressing == false
|
2
3
|
json.set! '@context', 'https://www.w3.org/ns/activitystreams' if context
|
3
4
|
|
4
5
|
json.id Federails::Engine.routes.url_helpers.server_actor_activity_url activity.actor, activity
|
5
6
|
json.type activity.action
|
6
7
|
json.actor activity.actor.federated_url
|
7
|
-
|
8
|
-
json.
|
8
|
+
if addressing
|
9
|
+
json.to ['https://www.w3.org/ns/activitystreams#Public']
|
10
|
+
json.cc [activity.actor.followers_url]
|
11
|
+
end
|
9
12
|
|
10
|
-
if activity.entity.
|
13
|
+
if activity.entity.is_a? Federails::Activity
|
14
|
+
json.object { json.partial!('federails/server/activities/activity', activity: activity.entity, context: false, addressing: false) }
|
15
|
+
elsif activity.entity.respond_to? :to_activitypub_object
|
11
16
|
json.object activity.entity.to_activitypub_object
|
12
17
|
elsif activity.entity.respond_to? :federated_url
|
13
18
|
json.object activity.entity.federated_url
|
@@ -1,4 +1,4 @@
|
|
1
|
-
actor_data = actor.entity
|
1
|
+
actor_data = actor.entity&.to_activitypub_object || {}
|
2
2
|
|
3
3
|
json.set! '@context', ([
|
4
4
|
'https://www.w3.org/ns/activitystreams',
|
@@ -9,7 +9,7 @@ json.set! '@context', ([
|
|
9
9
|
|
10
10
|
json.id actor.federated_url
|
11
11
|
json.name actor.name
|
12
|
-
json.type actor.
|
12
|
+
json.type actor.actor_type
|
13
13
|
json.preferredUsername actor.username
|
14
14
|
json.inbox actor.inbox_url
|
15
15
|
json.outbox actor.outbox_url
|
@@ -0,0 +1,11 @@
|
|
1
|
+
context = true unless context == false
|
2
|
+
json.set! '@context', 'https://www.w3.org/ns/activitystreams' if context
|
3
|
+
|
4
|
+
publishable.to_activitypub_object.each_pair do |key, value|
|
5
|
+
json.set! key, value
|
6
|
+
end
|
7
|
+
|
8
|
+
json.id publishable.federated_url
|
9
|
+
json.actor publishable.federails_actor.federated_url
|
10
|
+
json.to ['https://www.w3.org/ns/activitystreams#Public']
|
11
|
+
json.cc [publishable.federails_actor.followers_url]
|
@@ -0,0 +1,9 @@
|
|
1
|
+
json.set! '@context', [
|
2
|
+
'https://www.w3.org/ns/activitystreams',
|
3
|
+
'https://w3id.org/security/v1',
|
4
|
+
]
|
5
|
+
|
6
|
+
json.id publishable.federated_url
|
7
|
+
json.type 'Tombstone'
|
8
|
+
json.deleted publishable.federails_tombstoned_at
|
9
|
+
json.formerType publishable.federails_data_configuration[:handles]
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddLocalToActors < ActiveRecord::Migration[7.0]
|
2
|
+
def change
|
3
|
+
add_column :federails_actors, :local, :boolean, null: false, default: false
|
4
|
+
|
5
|
+
reversible do |dir|
|
6
|
+
dir.up do
|
7
|
+
exec_update 'UPDATE federails_actors SET local=true WHERE entity_type IS NOT NULL'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Federails
|
2
2
|
# rubocop:disable Style/ClassVars
|
3
|
+
|
4
|
+
# Stores the Federails configuration in a _singleton_.
|
3
5
|
module Configuration
|
4
6
|
# Application name, used in well-known and nodeinfo endpoints
|
5
7
|
mattr_accessor :app_name
|
@@ -26,8 +28,12 @@ module Federails
|
|
26
28
|
@@enable_discovery = true
|
27
29
|
|
28
30
|
# Does the site allow open registrations? (only used for nodeinfo reporting)
|
29
|
-
|
31
|
+
# Can either be a static boolean, or a Proc which will be called to get the state.
|
32
|
+
mattr_writer :open_registrations
|
30
33
|
@@open_registrations = false
|
34
|
+
def self.open_registrations
|
35
|
+
@@open_registrations.is_a?(Proc) ? @@open_registrations.call : @@open_registrations
|
36
|
+
end
|
31
37
|
|
32
38
|
# Application layout
|
33
39
|
mattr_accessor :app_layout
|
@@ -45,7 +51,16 @@ module Federails
|
|
45
51
|
mattr_accessor :base_client_controller
|
46
52
|
@@base_client_controller = 'ActionController::Base'
|
47
53
|
|
54
|
+
# @!method self.remote_follow_url_method
|
55
|
+
#
|
48
56
|
# Route method for remote-following requests
|
57
|
+
|
58
|
+
# @!method self.remote_follow_url_method=(value)
|
59
|
+
#
|
60
|
+
# Sets the route method for remote-following requests
|
61
|
+
# @param value [String] Route method name as used in links
|
62
|
+
# @example
|
63
|
+
# remote_follow_url_method 'main_app.my_custom_route_helper'
|
49
64
|
mattr_accessor :remote_follow_url_method
|
50
65
|
@@remote_follow_url_method = 'federails.new_client_following_url'
|
51
66
|
|
@@ -66,6 +81,14 @@ module Federails
|
|
66
81
|
def self.register_actor_class(klass, config = {})
|
67
82
|
@@actor_types[klass.name] = config.merge(class: klass)
|
68
83
|
end
|
84
|
+
|
85
|
+
# List of data types (classes using Federails::DataEntity)
|
86
|
+
mattr_reader :data_types
|
87
|
+
@@data_types = {}
|
88
|
+
|
89
|
+
def self.register_data_type(klass, config = {})
|
90
|
+
@@data_types[klass.name] = config.merge(class: klass)
|
91
|
+
end
|
69
92
|
end
|
70
93
|
# rubocop:enable Style/ClassVars
|
71
94
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Federails
|
2
|
+
module DataTransformer
|
3
|
+
module Note
|
4
|
+
# Renders a Note. The entity is used to determine actor and generic fields data
|
5
|
+
#
|
6
|
+
# @param entity [#federail_actor, #federated_url, #created_at, #updated_at] A model instance
|
7
|
+
# @param content [String] Note content
|
8
|
+
# @param name [String, nil] Optional name/title
|
9
|
+
# @param custom [Hash] Optional additional keys (e.g.: attachment, icon, ...). Defaults will override these.
|
10
|
+
#
|
11
|
+
# @return [Hash]
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# Federails::DataTransformer::Note.to_federation(comment, content: comment.content, custom: { 'inReplyTo' => comment.parent.federated_url })
|
15
|
+
#
|
16
|
+
# See:
|
17
|
+
# - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-object
|
18
|
+
# - https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
|
19
|
+
def self.to_federation(entity, content:, name: nil, custom: {})
|
20
|
+
custom.merge '@context' => 'https://www.w3.org/ns/activitystreams',
|
21
|
+
'id' => entity.federated_url,
|
22
|
+
'type' => 'Note',
|
23
|
+
'name' => name,
|
24
|
+
'content' => content,
|
25
|
+
'attributedTo' => entity.federails_actor.federated_url,
|
26
|
+
'published' => entity.created_at,
|
27
|
+
'updated' => entity.updated_at
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Federails
|
2
|
+
module Maintenance
|
3
|
+
class ActorsUpdater
|
4
|
+
class << self
|
5
|
+
# Fetches all distant actors again and update their local copy
|
6
|
+
#
|
7
|
+
# A block can be passed with two arguments: the actor being updated and the update status
|
8
|
+
#
|
9
|
+
# @param actors [Integer, Federails::Actor, Array<Federails::Actor>, nil] Actor ID, Actor or list of actors to update.
|
10
|
+
# If nothing is passed, all distant actors are processed
|
11
|
+
# @example
|
12
|
+
# Update all distant actors
|
13
|
+
# Federails::Maintenance::ActorUpdater.run
|
14
|
+
# With an actor id:
|
15
|
+
# Federails::Maintenance::ActorUpdater.run 1
|
16
|
+
# With a federated URL:
|
17
|
+
# Federails::Maintenance::ActorUpdater.run 'https://example.com/actor'
|
18
|
+
# With a federated URL:
|
19
|
+
# Federails::Maintenance::ActorUpdater.run ['https://example.com/actors/1', 'https://example.com/actors/1']
|
20
|
+
# With actors:
|
21
|
+
# Federails::Maintenance::ActorUpdater.run Federails::Actor.last(10)
|
22
|
+
# Update all distant actors and puts status for each actor
|
23
|
+
# Federails::Maintenance::ActorUpdater.run {|actor, status| puts "#{actor.federated_url}: #{status}"}
|
24
|
+
def run(actors = nil, &block)
|
25
|
+
actors_list(actors).each do |actor|
|
26
|
+
status = update(actor)
|
27
|
+
|
28
|
+
yield(actor, status) if block
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Make a list of actors to update from the passed attribute
|
35
|
+
def actors_list(param) # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
|
36
|
+
if param.nil?
|
37
|
+
Federails::Actor.distant
|
38
|
+
elsif param.is_a? String
|
39
|
+
[Federails::Actor.distant.find_by!(federated_url: param)]
|
40
|
+
elsif param.is_a? Integer
|
41
|
+
[Federails::Actor.distant.find(param)]
|
42
|
+
elsif param.is_a?(Federails::Actor)
|
43
|
+
[param]
|
44
|
+
elsif param.respond_to?(:pluck) && param.first.is_a?(Federails::Actor)
|
45
|
+
param
|
46
|
+
elsif param.is_a?(Array) && param.first.is_a?(String)
|
47
|
+
Federails::Actor.distant.where(federated_url: param)
|
48
|
+
else
|
49
|
+
raise "Cannot extract actors from #{param.class}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param actor [Federails::Actor]
|
54
|
+
def update(actor)
|
55
|
+
return :ignored_local if actor.local?
|
56
|
+
|
57
|
+
response = Fediverse::Webfinger.fetch_actor_url(actor.federated_url)
|
58
|
+
new_attributes = response.attributes.except 'id', 'uuid', 'created_at', 'updated_at', 'local'
|
59
|
+
|
60
|
+
actor.update(new_attributes) ? :updated : :failed
|
61
|
+
rescue ActiveRecord::RecordNotFound
|
62
|
+
:not_found
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Federails
|
2
|
+
module Utils
|
3
|
+
class Actor
|
4
|
+
# List of the attributes computed for local actors
|
5
|
+
COMPUTED_ATTRIBUTES = [
|
6
|
+
:federated_url,
|
7
|
+
:username,
|
8
|
+
:name,
|
9
|
+
:server,
|
10
|
+
:inbox_url,
|
11
|
+
:outbox_url,
|
12
|
+
:followers_url,
|
13
|
+
:followings_url,
|
14
|
+
:profile_url,
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# @param actor [Federails::Actor]
|
19
|
+
# @return [Federails::Actor]
|
20
|
+
def tombstone!(actor)
|
21
|
+
if actor.local?
|
22
|
+
tombstone_local_actor actor
|
23
|
+
else
|
24
|
+
tombstone_distant_actor actor
|
25
|
+
end
|
26
|
+
|
27
|
+
actor
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def tombstone_local_actor(actor)
|
33
|
+
Federails::Actor.transaction do
|
34
|
+
hash = {
|
35
|
+
tombstoned_at: Time.current,
|
36
|
+
entity: nil,
|
37
|
+
}
|
38
|
+
# Hardcode attributes depending on the actor's entity
|
39
|
+
COMPUTED_ATTRIBUTES.each { |attribute| hash[attribute] = actor.send(attribute) }
|
40
|
+
|
41
|
+
actor.update! hash
|
42
|
+
|
43
|
+
Activity.create! actor: actor, action: 'Delete', entity: actor
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def tombstone_distant_actor(actor)
|
48
|
+
actor.update! tombstoned_at: Time.current
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Federails
|
2
|
+
module Utils
|
3
|
+
# Methods to manipulate incoming objects
|
4
|
+
class Object
|
5
|
+
class << self
|
6
|
+
# Finds data from an object or its ID.
|
7
|
+
#
|
8
|
+
# When data exists locally, the entity is returned.
|
9
|
+
# For distant data, a new instance is returned unless the target does not exist.
|
10
|
+
#
|
11
|
+
# @param object_or_id [String, Hash] String identifier or incoming object
|
12
|
+
#
|
13
|
+
# @return [ApplicationRecord, nil] Entity or nil when invalid/not found
|
14
|
+
def find_or_initialize(object_or_id)
|
15
|
+
federated_url = object_or_id.is_a?(Hash) ? object_or_id['id'] : object_or_id
|
16
|
+
|
17
|
+
route = local_route(federated_url)
|
18
|
+
return from_local_route(route) if route
|
19
|
+
|
20
|
+
from_distant_server(object_or_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Search for a distant object in actors and configured data entities.
|
24
|
+
#
|
25
|
+
# This is useful to find something when the type is unknown, as an object from a Delete activity.
|
26
|
+
#
|
27
|
+
# @param federated_url [String] Object identifier
|
28
|
+
# @return [Federails::Actor, Federails::DataEntity, Federails::Following, nil]
|
29
|
+
def find_distant_object_in_all(federated_url)
|
30
|
+
# Search in actors
|
31
|
+
object = Federails::Actor.find_by federated_url: federated_url
|
32
|
+
return object if object.present?
|
33
|
+
|
34
|
+
# Search in followings
|
35
|
+
object = Federails::Following.find_by federated_url: federated_url
|
36
|
+
return object if object.present?
|
37
|
+
|
38
|
+
# Search in data entities
|
39
|
+
Federails.configuration.data_types.keys.sort.each do |klass|
|
40
|
+
object = klass.constantize.find_by federated_url: federated_url
|
41
|
+
|
42
|
+
break if object.present?
|
43
|
+
end
|
44
|
+
|
45
|
+
object
|
46
|
+
end
|
47
|
+
|
48
|
+
# Finds or initializes an entity from an ActivityPub object or id
|
49
|
+
#
|
50
|
+
# @see .find_or_initialize
|
51
|
+
#
|
52
|
+
# @param object_or_id [String, Hash] String identifier or incoming object
|
53
|
+
#
|
54
|
+
# @return [ApplicationRecord, nil] Entity or nil when invalid/not found
|
55
|
+
def find_or_initialize!(object_or_id)
|
56
|
+
entity = find_or_initialize object_or_id
|
57
|
+
raise ActiveRecord::RecordNotFound unless entity
|
58
|
+
|
59
|
+
entity
|
60
|
+
end
|
61
|
+
|
62
|
+
# Finds or create an entity from an ActivityPub object or id
|
63
|
+
#
|
64
|
+
# Note that the data transformer MUST return timestamps from the ActivityPub object if used on the model,
|
65
|
+
# as they won't be set automatically.
|
66
|
+
#
|
67
|
+
# @see .find_or_initialize!
|
68
|
+
#
|
69
|
+
# @param object_or_id [String, Hash] String identifier or incoming object
|
70
|
+
#
|
71
|
+
# @return [ApplicationRecord, nil] Entity or nil when invalid/not found
|
72
|
+
def find_or_create!(object_or_id)
|
73
|
+
entity = find_or_initialize! object_or_id
|
74
|
+
return entity if entity.persisted?
|
75
|
+
|
76
|
+
entity.save!(touch: false)
|
77
|
+
entity
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the timestamps to use from an ActivityPub object
|
81
|
+
#
|
82
|
+
# @param hash [Hash] ActivityPub object
|
83
|
+
#
|
84
|
+
# @return [Hash] Hash with timestamps
|
85
|
+
def timestamp_attributes(hash)
|
86
|
+
{
|
87
|
+
created_at: hash['published'] ||= Time.current,
|
88
|
+
updated_at: hash['updated'].presence || hash['published'],
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def local_route(url)
|
95
|
+
route = Utils::Host.local_route(url)
|
96
|
+
|
97
|
+
return nil unless route && route[:controller] == 'federails/server/published' && route[:action] == 'show'
|
98
|
+
|
99
|
+
route
|
100
|
+
end
|
101
|
+
|
102
|
+
def from_local_route(route)
|
103
|
+
config = Federails.data_entity_handled_on route[:publishable_type]
|
104
|
+
return unless config
|
105
|
+
|
106
|
+
config[:class]&.find_by(config[:url_param] => route[:id])
|
107
|
+
rescue ActiveRecord::RecordNotFound
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def from_distant_server(federated_url)
|
112
|
+
hash = Fediverse::Request.dereference(federated_url)
|
113
|
+
return unless hash
|
114
|
+
|
115
|
+
handler = Federails.data_entity_handler_for hash
|
116
|
+
return unless handler
|
117
|
+
|
118
|
+
entity = handler[:class].find_by federated_url: hash['id']
|
119
|
+
return entity if entity
|
120
|
+
|
121
|
+
entity = handler[:class].new_from_activitypub_object(hash)
|
122
|
+
return unless entity
|
123
|
+
|
124
|
+
entity.federails_actor = Federails::Actor.find_or_create_by_object hash['attributedTo'] if entity && !entity.federails_actor
|
125
|
+
|
126
|
+
entity
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/federails/version.rb
CHANGED
data/lib/federails.rb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
require 'federails/version'
|
2
2
|
require 'federails/engine'
|
3
3
|
require 'federails/configuration'
|
4
|
+
require 'federails/utils/object'
|
4
5
|
|
5
6
|
# rubocop:disable Style/ClassVars
|
7
|
+
|
8
|
+
# This module includes classes and methods related to Ruby on Rails: engine configuration, models, controllers, etc.
|
6
9
|
module Federails
|
10
|
+
DEFAULT_DATA_FILTER_METHOD = :handle_federated_object?
|
11
|
+
|
7
12
|
mattr_reader :configuration
|
8
13
|
@@configuration = Configuration
|
9
14
|
|
@@ -49,6 +54,55 @@ module Federails
|
|
49
54
|
Configuration.actor_types[klass]
|
50
55
|
end
|
51
56
|
|
57
|
+
# @return [Boolean] True if the given model is a possible data entity
|
58
|
+
def data_entity?(class_or_instance)
|
59
|
+
Configuration.data_types.key? class_or_instance_name(class_or_instance)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Finds configured data types from ActivityPub type
|
63
|
+
#
|
64
|
+
# @param type [String] ActivityPub object type, as configured with `:handles`
|
65
|
+
# @return [Array] List of data entity configurations
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# data_entity_handlers_for 'Note'
|
69
|
+
def data_entity_handlers_for(type)
|
70
|
+
Federails::Configuration.data_types.select { |_, v| v[:handles] == type }.map(&:last)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Finds the configured handler for a given ActivityPub object
|
74
|
+
#
|
75
|
+
# @param hash [Hash] ActivityPub object hash
|
76
|
+
#
|
77
|
+
# @return [Hash, nil] Data entity configuration
|
78
|
+
def data_entity_handler_for(hash)
|
79
|
+
data_entity_handlers_for(hash['type']).find do |handler|
|
80
|
+
return true if !handler[:filter_method] && !handler[:class].respond_to?(DEFAULT_DATA_FILTER_METHOD)
|
81
|
+
|
82
|
+
handler[:class].send(handler[:filter_method] || DEFAULT_DATA_FILTER_METHOD, hash)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Finds configured data type from route path segment
|
87
|
+
#
|
88
|
+
# @param route_path_segment [Symbol, String] Route path segment, as configured with `:route_path_segment`
|
89
|
+
# @return [Hash, nil] Entity configuration
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# data_entity_handled_on :articles
|
93
|
+
def data_entity_handled_on(route_path_segment)
|
94
|
+
route_path_segment = route_path_segment.to_sym
|
95
|
+
Federails::Configuration.data_types.find { |_, v| v[:route_path_segment] == route_path_segment }&.last
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [Hash] The configuration for the given data entity
|
99
|
+
def data_entity_configuration(class_or_instance)
|
100
|
+
klass = class_or_instance_name(class_or_instance)
|
101
|
+
raise "#{klass} is not a configured data entity" unless Configuration.data_types.key?(klass)
|
102
|
+
|
103
|
+
Configuration.data_types[klass]
|
104
|
+
end
|
105
|
+
|
52
106
|
private
|
53
107
|
|
54
108
|
# @return [String] Class name of the provided class or instance
|
data/lib/fediverse/inbox.rb
CHANGED
@@ -4,27 +4,51 @@ module Fediverse
|
|
4
4
|
class Inbox
|
5
5
|
@@handlers = {} # rubocop:todo Style/ClassVars
|
6
6
|
class << self
|
7
|
+
# Registers a handler for incoming data
|
8
|
+
#
|
9
|
+
# Unless a specific type is not implemented in Federails, you should leave the 'Delete' activity to Federails:
|
10
|
+
# it will dispatch a `on_federails_delete_requested` event on the right objects.
|
11
|
+
#
|
12
|
+
# @param activity_type [String] Target activity type ('Create', 'Follow', 'Like', ...)
|
13
|
+
# See https://www.w3.org/TR/activitystreams-vocabulary/#activity-types for a list of common ones
|
14
|
+
# @param object_type [String] Type of the related object ('Article', 'Note', ...)
|
15
|
+
# See https://www.w3.org/TR/activitystreams-vocabulary/#object-types for a list of common object types
|
16
|
+
# @param klass [String] Class handling the incoming object
|
17
|
+
# @param method [Symbol] Method in the class that will handle the object
|
7
18
|
def register_handler(activity_type, object_type, klass, method)
|
8
19
|
@@handlers[activity_type] ||= {}
|
9
20
|
@@handlers[activity_type][object_type] ||= {}
|
10
21
|
@@handlers[activity_type][object_type][klass] = method
|
11
22
|
end
|
12
23
|
|
24
|
+
# Executes the registered handler for an incoming object
|
25
|
+
#
|
26
|
+
# @param payload [Hash] Dereferenced activity
|
13
27
|
def dispatch_request(payload)
|
14
|
-
|
28
|
+
return dispatch_delete_request(payload) if payload['type'] == 'Delete'
|
29
|
+
|
30
|
+
payload['object'] = Fediverse::Request.dereference(payload['object']) if payload.key? 'object'
|
31
|
+
|
32
|
+
handlers = get_handlers(payload['type'], payload.dig('object', 'type'))
|
15
33
|
handlers.each_pair do |klass, method|
|
16
34
|
klass.send method, payload
|
17
35
|
end
|
18
36
|
return true unless handlers.empty?
|
19
37
|
|
20
|
-
# FIXME: Fails silently
|
21
|
-
# raise NotImplementedError
|
22
38
|
Rails.logger.debug { "Unhandled activity type: #{payload['type']}" }
|
23
39
|
false
|
24
40
|
end
|
25
41
|
|
26
42
|
private
|
27
43
|
|
44
|
+
def dispatch_delete_request(payload)
|
45
|
+
payload['object'] = payload['object']['id'] unless payload['object'].is_a? String
|
46
|
+
object = Federails::Utils::Object.find_distant_object_in_all payload['object']
|
47
|
+
return if object.blank?
|
48
|
+
|
49
|
+
object.run_callbacks :on_federails_delete_requested
|
50
|
+
end
|
51
|
+
|
28
52
|
def get_handlers(activity_type, object_type)
|
29
53
|
{}.merge(@@handlers.dig(activity_type, object_type) || {})
|
30
54
|
.merge(@@handlers.dig(activity_type, '*') || {})
|
@@ -39,8 +63,8 @@ module Fediverse
|
|
39
63
|
Federails::Following.create! actor: actor, target_actor: target_actor, federated_url: activity['id']
|
40
64
|
end
|
41
65
|
|
42
|
-
def
|
43
|
-
original_activity = Request.
|
66
|
+
def handle_accept_follow_request(activity)
|
67
|
+
original_activity = Request.dereference(activity['object'])
|
44
68
|
|
45
69
|
actor = Federails::Actor.find_or_create_by_object original_activity['actor']
|
46
70
|
target_actor = Federails::Actor.find_or_create_by_object original_activity['object']
|
@@ -50,7 +74,7 @@ module Fediverse
|
|
50
74
|
follow.accept!
|
51
75
|
end
|
52
76
|
|
53
|
-
def
|
77
|
+
def handle_undo_follow_request(activity)
|
54
78
|
original_activity = activity['object']
|
55
79
|
|
56
80
|
actor = Federails::Actor.find_or_create_by_object original_activity['actor']
|
@@ -59,10 +83,18 @@ module Fediverse
|
|
59
83
|
follow = Federails::Following.find_by actor: actor, target_actor: target_actor
|
60
84
|
follow&.destroy
|
61
85
|
end
|
86
|
+
|
87
|
+
def handle_delete_request(activity)
|
88
|
+
object = Federails::Utils::Object.find_distant_object_in_all(activity['object'])
|
89
|
+
return if object.blank?
|
90
|
+
|
91
|
+
object.run_callbacks :on_federails_delete_requested
|
92
|
+
end
|
62
93
|
end
|
63
94
|
|
64
95
|
register_handler 'Follow', '*', self, :handle_create_follow_request
|
65
|
-
register_handler 'Accept', 'Follow', self, :
|
66
|
-
register_handler 'Undo', 'Follow', self, :
|
96
|
+
register_handler 'Accept', 'Follow', self, :handle_accept_follow_request
|
97
|
+
register_handler 'Undo', 'Follow', self, :handle_undo_follow_request
|
98
|
+
register_handler 'Delete', '*', self, :handle_delete_request
|
67
99
|
end
|
68
100
|
end
|
data/lib/fediverse/notifier.rb
CHANGED
@@ -3,6 +3,9 @@ require 'fediverse/signature'
|
|
3
3
|
module Fediverse
|
4
4
|
class Notifier
|
5
5
|
class << self
|
6
|
+
# Posts an activity to its recipients
|
7
|
+
#
|
8
|
+
# @param activity [Federails::Activity]
|
6
9
|
def post_to_inboxes(activity)
|
7
10
|
actors = activity.recipients
|
8
11
|
Rails.logger.debug('Nobody to notice') && return if actors.count.zero?
|