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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 36117bc1ea548be65aebe4ee505850ecac3233851f07aaae18136188ec1c1077
|
|
4
|
+
data.tar.gz: '0958b68fce09e2775504b28f5227e098470c0cb3c89eb4ca3dec7f5ece871d78'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f6e4dc1e3554b81a02710b9d893c88ffa77ff7c33e623574378cd7b87a13165297dcd4b22a4ce38e90073a545708d7d82e17e9867b1f345a1ebcad2c1cdf237
|
|
7
|
+
data.tar.gz: a8fb6f9811a9369ee7cf706c64517f47c8a66b335de715b8325e508ca6790dc4e1f7db6ae67b62651920cf3fd467d1c2e23263fb5b4f38f2d40a2c1d5a9cdfc2
|
data/README.md
CHANGED
|
@@ -40,6 +40,13 @@ It _may_ work on other versions, but we won't provide support.
|
|
|
40
40
|
- [Common questions](docs/faq.md)
|
|
41
41
|
- [Contributing](CONTRIBUTING.md)
|
|
42
42
|
|
|
43
|
+
## Extensions
|
|
44
|
+
|
|
45
|
+
Extensions extends the features of Federails.
|
|
46
|
+
|
|
47
|
+
- [Federails Moderation](https://github.com/manyfold3d/federails-moderation/)
|
|
48
|
+
> A gem that provides moderation capabilities for Federails
|
|
49
|
+
|
|
43
50
|
## License
|
|
44
51
|
|
|
45
52
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -53,6 +60,7 @@ See [CONTRIBUTING](CONTRIBUTING.md) to have an overview of the process and the t
|
|
|
53
60
|
- [echarp](https://gitlab.com/echarp)
|
|
54
61
|
- [James Smith](https://gitlab.com/floppy.uk)
|
|
55
62
|
- [Manuel Tancoigne](https://gitlab.com/mtancoigne)
|
|
63
|
+
- [pessi-v](https://github.com/pessi-v)
|
|
56
64
|
|
|
57
65
|
### Indirect contributions
|
|
58
66
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Federails
|
|
2
|
+
module Server
|
|
3
|
+
module RenderCollections
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def render_collection(actor:, collection:, url_helper:, &items_block)
|
|
7
|
+
if params[:page].present?
|
|
8
|
+
render_collection_page(actor: actor, collection: collection, url_helper: url_helper, items_block: items_block)
|
|
9
|
+
else
|
|
10
|
+
render 'federails/server/shared/ordered_collection', locals: { collection: collection, url_helper: url_helper, actor: actor }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def render_collection_page(collection:, actor:, url_helper:, items_block:)
|
|
15
|
+
render 'federails/server/shared/ordered_collection_page', locals: { collection: collection, url_helper: url_helper, actor: actor, items_block: items_block }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -8,7 +8,7 @@ module Federails
|
|
|
8
8
|
# GET /app/followings/new?uri={uri}
|
|
9
9
|
def new
|
|
10
10
|
# Find actor (and fetch if necessary)
|
|
11
|
-
actor = Actor.find_or_create_by_federation_url(params
|
|
11
|
+
actor = Actor.find_or_create_by_federation_url(params.require(:uri))
|
|
12
12
|
# Redirect to local profile page which will have a follow button on it
|
|
13
13
|
redirect_to federails.client_actor_url(actor)
|
|
14
14
|
end
|
|
@@ -23,7 +23,7 @@ module Federails
|
|
|
23
23
|
format.json { render :show, status: :ok, location: @following }
|
|
24
24
|
else
|
|
25
25
|
format.html { redirect_to url, alert: I18n.t('controller.followings.accept.error') }
|
|
26
|
-
format.json { render json: @following.errors, status:
|
|
26
|
+
format.json { render json: @following.errors, status: Federails::Utils::ResponseCodes::UNPROCESSABLE_CONTENT }
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -49,7 +49,7 @@ module Federails
|
|
|
49
49
|
# Renders a 422 instead of a 404
|
|
50
50
|
respond_to do |format|
|
|
51
51
|
format.html { redirect_to federails.client_actors_url, alert: I18n.t('controller.followings.follow.error') }
|
|
52
|
-
format.json { render json: { target_actor: ['does not exist'] }, status:
|
|
52
|
+
format.json { render json: { target_actor: ['does not exist'] }, status: Federails::Utils::ResponseCodes::UNPROCESSABLE_CONTENT }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
return
|
|
@@ -94,7 +94,7 @@ module Federails
|
|
|
94
94
|
format.json { render :show, status: :created, location: @following }
|
|
95
95
|
else
|
|
96
96
|
format.html { redirect_to url, alert: I18n.t('controller.followings.save_and_render.error') }
|
|
97
|
-
format.json { render json: @following.errors, status:
|
|
97
|
+
format.json { render json: @following.errors, status: Federails::Utils::ResponseCodes::UNPROCESSABLE_CONTENT }
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
end
|
|
@@ -3,6 +3,8 @@ require 'fediverse/inbox'
|
|
|
3
3
|
module Federails
|
|
4
4
|
module Server
|
|
5
5
|
class ActivitiesController < Federails::ServerController
|
|
6
|
+
include Federails::Server::RenderCollections
|
|
7
|
+
|
|
6
8
|
before_action :set_activity, only: [:show]
|
|
7
9
|
|
|
8
10
|
# GET /federation/activities
|
|
@@ -10,10 +12,16 @@ module Federails
|
|
|
10
12
|
def outbox
|
|
11
13
|
authorize Federails::Activity, policy_class: Federails::Server::ActivityPolicy
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
actor = Actor.find_param(params[:actor_id])
|
|
16
|
+
activities = policy_scope(Federails::Activity, policy_scope_class: Federails::Server::ActivityPolicy::Scope).where(actor: actor).order(created_at: :desc)
|
|
17
|
+
|
|
18
|
+
render_collection(
|
|
19
|
+
collection: activities.page(params[:page]),
|
|
20
|
+
actor: actor,
|
|
21
|
+
url_helper: :server_actor_outbox_url
|
|
22
|
+
) do |builder, items|
|
|
23
|
+
builder.array! items, partial: 'federails/server/activities/activity', as: :activity, context: false
|
|
24
|
+
end
|
|
17
25
|
end
|
|
18
26
|
|
|
19
27
|
# GET /federation/actors/1/activities/1.json
|
|
@@ -24,12 +32,12 @@ module Federails
|
|
|
24
32
|
skip_authorization
|
|
25
33
|
|
|
26
34
|
payload = payload_from_params
|
|
27
|
-
return head
|
|
35
|
+
return head Federails::Utils::ResponseCodes::UNPROCESSABLE_CONTENT unless payload
|
|
28
36
|
|
|
29
37
|
if Fediverse::Inbox.dispatch_request(payload)
|
|
30
38
|
head :created
|
|
31
39
|
else
|
|
32
|
-
head
|
|
40
|
+
head Federails::Utils::ResponseCodes::UNPROCESSABLE_CONTENT
|
|
33
41
|
end
|
|
34
42
|
end
|
|
35
43
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Federails
|
|
2
2
|
module Server
|
|
3
3
|
class ActorsController < Federails::ServerController
|
|
4
|
+
include Federails::Server::RenderCollections
|
|
5
|
+
|
|
4
6
|
before_action :set_actor, only: [:show, :followers, :following]
|
|
5
7
|
|
|
6
8
|
# GET /federation/actors/1
|
|
@@ -14,14 +16,26 @@ module Federails
|
|
|
14
16
|
# GET /federation/actors/:id/followers.json
|
|
15
17
|
def followers
|
|
16
18
|
@actors = @actor.followers.order(created_at: :desc)
|
|
17
|
-
|
|
19
|
+
render_collection(
|
|
20
|
+
collection: @actors.page(params[:page]),
|
|
21
|
+
actor: @actor,
|
|
22
|
+
url_helper: :followers_server_actor_url
|
|
23
|
+
) do |builder, items|
|
|
24
|
+
builder.array! items.map(&:federated_url)
|
|
25
|
+
end
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
# GET /federation/actors/:id/followers
|
|
21
29
|
# GET /federation/actors/:id/followers.json
|
|
22
30
|
def following
|
|
23
31
|
@actors = @actor.follows.order(created_at: :desc)
|
|
24
|
-
|
|
32
|
+
render_collection(
|
|
33
|
+
collection: @actors.page(params[:page]),
|
|
34
|
+
actor: @actor,
|
|
35
|
+
url_helper: :following_server_actor_url
|
|
36
|
+
) do |builder, items|
|
|
37
|
+
builder.array! items.map(&:federated_url)
|
|
38
|
+
end
|
|
25
39
|
end
|
|
26
40
|
|
|
27
41
|
private
|
|
@@ -31,11 +45,6 @@ module Federails
|
|
|
31
45
|
@actor = Actor.find_param(params[:id])
|
|
32
46
|
authorize @actor, policy_class: Federails::Server::ActorPolicy
|
|
33
47
|
end
|
|
34
|
-
|
|
35
|
-
def followings_queries
|
|
36
|
-
@total_actors = @actors.count
|
|
37
|
-
@actors = @actors.page(params[:page])
|
|
38
|
-
end
|
|
39
48
|
end
|
|
40
49
|
end
|
|
41
50
|
end
|
|
@@ -18,8 +18,8 @@ module Federails
|
|
|
18
18
|
@has_user_counts = true
|
|
19
19
|
model = config[:class]
|
|
20
20
|
@total += model.send(method, nil)
|
|
21
|
-
@active_month += model.send(method, (
|
|
22
|
-
@active_halfyear += model.send(method, (
|
|
21
|
+
@active_month += model.send(method, (30.days.ago)...Time.current)
|
|
22
|
+
@active_halfyear += model.send(method, (180.days.ago)...Time.current)
|
|
23
23
|
end
|
|
24
24
|
render formats: [:nodeinfo]
|
|
25
25
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'federails/utils/context'
|
|
2
|
+
|
|
1
3
|
module Federails
|
|
2
4
|
module ServerHelper
|
|
3
5
|
def remote_follow_url
|
|
@@ -8,5 +10,9 @@ module Federails
|
|
|
8
10
|
Rails.application.routes.url_helpers.send(method_name)
|
|
9
11
|
end
|
|
10
12
|
end
|
|
13
|
+
|
|
14
|
+
def set_json_ld_context(json, additional: nil)
|
|
15
|
+
json.set! '@context', Federails::Utils::Context.generate(additional: additional)
|
|
16
|
+
end
|
|
11
17
|
end
|
|
12
18
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require 'fediverse/notifier'
|
|
2
|
+
|
|
3
|
+
module Federails
|
|
4
|
+
class FetchNodeinfoJob < ApplicationJob
|
|
5
|
+
# @param domain [String] Domain to create/update
|
|
6
|
+
def perform(domain)
|
|
7
|
+
Federails::Host.create_or_update domain, min_update_interval: Federails::Configuration.remote_entities_cache_duration
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -156,7 +156,11 @@ module Federails
|
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
def tombstone_federails_actor!
|
|
159
|
-
federails_actor.tombstone!
|
|
159
|
+
federails_actor.presence&.tombstone!
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def untombstone_federails_actor!
|
|
163
|
+
federails_actor.presence&.untombstone!
|
|
160
164
|
end
|
|
161
165
|
end
|
|
162
166
|
end
|
|
@@ -12,9 +12,18 @@ module Federails
|
|
|
12
12
|
#
|
|
13
13
|
# ## Pre-requisites
|
|
14
14
|
#
|
|
15
|
-
# Model
|
|
15
|
+
# Model should have the following methods:
|
|
16
|
+
# - `to_activitypub_object`, returning a valid ActivityPub object
|
|
17
|
+
# - `self.from_activitypub_object`, returning a hash of valid attributes from a hash of incoming data
|
|
18
|
+
#
|
|
19
|
+
# Table needs at least:
|
|
20
|
+
# - `t.string :federated_url, null: true, default: nil`
|
|
21
|
+
# - `t.references :federails_actor, foreign_key: true, null: true, default: nil
|
|
22
|
+
#
|
|
23
|
+
# Model must have the following attributes:
|
|
16
24
|
# ```rb
|
|
17
25
|
# add_column :posts, :federated_url, :string, null: true, default: nil
|
|
26
|
+
# add_reference :posts, :federails_actor, foreign_key: true, null: true, default: nil
|
|
18
27
|
# ```
|
|
19
28
|
#
|
|
20
29
|
# ## Usage
|
|
@@ -29,6 +38,61 @@ module Federails
|
|
|
29
38
|
# # This will be called when a Delete activity comes for the entry. As we don't know how you want to handle it,
|
|
30
39
|
# # you'll have to implement the behavior yourself.
|
|
31
40
|
# on_federails_delete_requested :do_something
|
|
41
|
+
#
|
|
42
|
+
# # This will be called when a Undo activity comes for the entry. The easiest way to handle this case is to re-fetch
|
|
43
|
+
# # the entity
|
|
44
|
+
# on_federails_undelete_requested :do_something_else
|
|
45
|
+
#
|
|
46
|
+
# def to_activitypub_object
|
|
47
|
+
# Federails::DataTransformer::Note.to_federation self,
|
|
48
|
+
# content: content,
|
|
49
|
+
# name: title
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# # Creates a hash of attributes from incoming Note
|
|
53
|
+
# def self.from_activitypub_object(hash)
|
|
54
|
+
# {
|
|
55
|
+
# title: hash['name'] || 'A post',
|
|
56
|
+
# content: hash['content'],
|
|
57
|
+
# }
|
|
58
|
+
# end
|
|
59
|
+
# end
|
|
60
|
+
# ```
|
|
61
|
+
#
|
|
62
|
+
# **If your model has a mechanism for soft deletion:**
|
|
63
|
+
# - you can specify some methods names to handle it in Federails responses:
|
|
64
|
+
# - you will need to send the delete activity yourself
|
|
65
|
+
#
|
|
66
|
+
# ```rb
|
|
67
|
+
# acts_as_federails_data handles: 'Note',
|
|
68
|
+
# ...,
|
|
69
|
+
# soft_deleted_method: :deleted?
|
|
70
|
+
# soft_delete_date_method: :deleted_at
|
|
71
|
+
#
|
|
72
|
+
# on_federails_delete_requested :soft_delete!
|
|
73
|
+
# on_federails_undelete_requested :restore_remote_entity!
|
|
74
|
+
#
|
|
75
|
+
# # Method you use to soft-delete entities
|
|
76
|
+
# def soft_delete!
|
|
77
|
+
# update deleted_at: time.current
|
|
78
|
+
#
|
|
79
|
+
# send_federails_activity 'Delete' unless local_federails_entity?
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# # Method you use to restore soft-deleted entities
|
|
83
|
+
# def restore!
|
|
84
|
+
# update deleted_at: nil
|
|
85
|
+
#
|
|
86
|
+
# if local_federails_entity?
|
|
87
|
+
# delete_activity = Activity.find_by action: 'Delete', entity: self
|
|
88
|
+
# send_federails_activity 'Undo', entity: delete_activity, actor: federails_actor if delete_activity.present?
|
|
89
|
+
# end
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# def restore_remote_entity!
|
|
93
|
+
# self.deleted_at: nil
|
|
94
|
+
# federails_sync!
|
|
95
|
+
# save!
|
|
32
96
|
# end
|
|
33
97
|
# ```
|
|
34
98
|
module DataEntity
|
|
@@ -41,9 +105,6 @@ module Federails
|
|
|
41
105
|
module ClassMethods
|
|
42
106
|
# Configures the mapping between entity and Fediverse
|
|
43
107
|
#
|
|
44
|
-
# Model should have the following methods:
|
|
45
|
-
# - `to_activitypub_object`, returning a valid ActivityPub object
|
|
46
|
-
#
|
|
47
108
|
# @param actor_entity_method [Symbol] Method returning an object responding to 'federails_actor', for local content
|
|
48
109
|
# @param url_param [Symbol] Column name of the object ID that should be used in URLs. Defaults to +:id+
|
|
49
110
|
# @param route_path_segment [Symbol] Segment used in Federails routes to display the ActivityPub representation.
|
|
@@ -56,9 +117,9 @@ module Federails
|
|
|
56
117
|
# @param should_federate_method [Symbol] method to determine if an object should be federated. If the method returns false,
|
|
57
118
|
# no create/update activities will happen, and object will not be accessible at federated_url. Defaults to a method
|
|
58
119
|
# that always returns true.
|
|
59
|
-
# @param soft_deleted_method [Symbol, nil]
|
|
60
|
-
# is not required by the spec but greatly encouraged as the app will return a 410
|
|
61
|
-
# instead of an 404 error.
|
|
120
|
+
# @param soft_deleted_method [Symbol, nil] If the model uses a soft-delete mechanism, this is the method to check
|
|
121
|
+
# if entity is soft-deleted. This is not required by the spec but greatly encouraged as the app will return a 410
|
|
122
|
+
# response with a Tombstone object instead of an 404 error.
|
|
62
123
|
# @param soft_delete_date_method [Symbol, nil] Method to get the date of the soft-deletion
|
|
63
124
|
#
|
|
64
125
|
# @example
|
|
@@ -142,7 +203,7 @@ module Federails
|
|
|
142
203
|
|
|
143
204
|
before_validation :set_federails_actor
|
|
144
205
|
after_create -> { create_federails_activity 'Create' }
|
|
145
|
-
after_update -> { create_federails_activity 'Update' }, :federails_tombstoned?
|
|
206
|
+
after_update -> { create_federails_activity 'Update' }, unless: :federails_tombstoned?
|
|
146
207
|
after_destroy -> { create_federails_activity 'Delete' }
|
|
147
208
|
end
|
|
148
209
|
|
|
@@ -177,6 +238,17 @@ module Federails
|
|
|
177
238
|
Federails.data_entity_configuration(self)
|
|
178
239
|
end
|
|
179
240
|
|
|
241
|
+
def federails_sync!
|
|
242
|
+
if local_federails_entity?
|
|
243
|
+
Rails.logger.info { "Ignored attempt to sync a local #{self.class.name}" }
|
|
244
|
+
return false
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
object = Fediverse::Request.dereference(federated_url)
|
|
248
|
+
|
|
249
|
+
update! self.class.from_activitypub_object(object)
|
|
250
|
+
end
|
|
251
|
+
|
|
180
252
|
private
|
|
181
253
|
|
|
182
254
|
def set_federails_actor
|
|
@@ -22,10 +22,15 @@ module Federails
|
|
|
22
22
|
def on_federails_delete_requested(*args)
|
|
23
23
|
set_callback :on_federails_delete_requested, *args
|
|
24
24
|
end
|
|
25
|
+
|
|
26
|
+
def on_federails_undelete_requested(*args)
|
|
27
|
+
set_callback :on_federails_undelete_requested, *args
|
|
28
|
+
end
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
included do
|
|
28
32
|
define_callbacks :on_federails_delete_requested
|
|
33
|
+
define_callbacks :on_federails_undelete_requested
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
end
|
|
@@ -24,31 +24,24 @@ module Federails
|
|
|
24
24
|
|
|
25
25
|
after_create_commit :post_to_inboxes
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return [] unless actor.local?
|
|
32
|
-
|
|
33
|
-
case action
|
|
34
|
-
when 'Follow'
|
|
35
|
-
[entity]
|
|
36
|
-
when 'Undo'
|
|
37
|
-
[entity.entity]
|
|
38
|
-
when 'Accept'
|
|
39
|
-
[entity.actor]
|
|
40
|
-
else
|
|
41
|
-
default_recipient_list
|
|
42
|
-
end
|
|
43
|
-
end
|
|
27
|
+
before_validation :set_default_addressing, on: :create
|
|
28
|
+
|
|
29
|
+
serialize :cc, coder: YAML
|
|
30
|
+
serialize :to, coder: YAML
|
|
44
31
|
|
|
45
32
|
private
|
|
46
33
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
# Sets up default public-and-followers addressing unless to and cc are already set
|
|
35
|
+
#
|
|
36
|
+
# This retains compatibility with previous behaviour
|
|
37
|
+
def set_default_addressing
|
|
38
|
+
return if to.present? || cc.present?
|
|
39
|
+
|
|
40
|
+
self.to = [Fediverse::Collection::PUBLIC]
|
|
41
|
+
self.cc = [
|
|
42
|
+
actor.followers_url,
|
|
43
|
+
(entity.try(:followers_url) if entity.try(:local?)),
|
|
44
|
+
].compact.uniq
|
|
52
45
|
end
|
|
53
46
|
|
|
54
47
|
def post_to_inboxes
|
|
@@ -33,15 +33,24 @@ module Federails
|
|
|
33
33
|
has_many :activities_as_entity, class_name: 'Federails::Activity', as: :entity, dependent: :destroy
|
|
34
34
|
has_many :following_followers, class_name: 'Federails::Following', foreign_key: :target_actor_id, dependent: :destroy, inverse_of: :target_actor
|
|
35
35
|
has_many :following_follows, class_name: 'Federails::Following', dependent: :destroy, inverse_of: :actor
|
|
36
|
+
# Actors following actor
|
|
36
37
|
has_many :followers, source: :actor, through: :following_followers
|
|
38
|
+
# Actors followed by actor
|
|
37
39
|
has_many :follows, source: :target_actor, through: :following_follows
|
|
40
|
+
belongs_to :host, class_name: 'Federails::Host', foreign_key: :server, primary_key: :domain, inverse_of: :actors, optional: true
|
|
41
|
+
|
|
42
|
+
# Explicitly explain serialization for MariaDB
|
|
43
|
+
attribute :extensions, :json
|
|
38
44
|
|
|
39
45
|
scope :local, -> { where(local: true) }
|
|
40
46
|
scope :distant, -> { where(local: false) }
|
|
41
47
|
scope :tombstoned, -> { where.not(tombstoned_at: nil) }
|
|
42
48
|
scope :not_tombstoned, -> { where(tombstoned_at: nil) }
|
|
43
49
|
|
|
50
|
+
after_create -> { FetchNodeinfoJob.perform_later(server) }, unless: :local?
|
|
51
|
+
|
|
44
52
|
on_federails_delete_requested -> { tombstone! }
|
|
53
|
+
on_federails_undelete_requested -> { untombstone! }
|
|
45
54
|
|
|
46
55
|
def distant?
|
|
47
56
|
!local?
|
|
@@ -96,8 +105,8 @@ module Federails
|
|
|
96
105
|
Rails.application.routes.url_helpers.send method, [entity]
|
|
97
106
|
end
|
|
98
107
|
|
|
99
|
-
def at_address
|
|
100
|
-
"
|
|
108
|
+
def at_address(prefix: '@')
|
|
109
|
+
"#{prefix}#{username}@#{server}"
|
|
101
110
|
end
|
|
102
111
|
|
|
103
112
|
def short_at_address
|
|
@@ -108,9 +117,22 @@ module Federails
|
|
|
108
117
|
"acct:#{username}@#{server}"
|
|
109
118
|
end
|
|
110
119
|
|
|
120
|
+
# Checks if a given actor follows the current actor
|
|
121
|
+
#
|
|
122
|
+
# @return [Federails::Following, false]
|
|
111
123
|
def follows?(actor)
|
|
112
124
|
list = following_follows.where target_actor: actor
|
|
113
|
-
return list.first if list.
|
|
125
|
+
return list.first if list.one?
|
|
126
|
+
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Checks if current actor is followed by the given actor
|
|
131
|
+
#
|
|
132
|
+
# @return [Federails::Following, false]
|
|
133
|
+
def followed_by?(actor)
|
|
134
|
+
list = following_followers.where actor: actor
|
|
135
|
+
return list.first if list.one?
|
|
114
136
|
|
|
115
137
|
false
|
|
116
138
|
end
|
|
@@ -121,6 +143,21 @@ module Federails
|
|
|
121
143
|
Federails.actor_entity entity_type
|
|
122
144
|
end
|
|
123
145
|
|
|
146
|
+
# Synchronizes actor with distant data
|
|
147
|
+
#
|
|
148
|
+
# @raise [ActiveRecord::RecordNotFound] when distant data was not found
|
|
149
|
+
def sync!
|
|
150
|
+
if local?
|
|
151
|
+
Rails.logger.info 'Ignored attempt to sync a local actor'
|
|
152
|
+
return false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
response = Fediverse::Webfinger.fetch_actor_url(federated_url)
|
|
156
|
+
new_attributes = response.attributes.except 'id', 'uuid', 'created_at', 'updated_at', 'local', 'entity_id', 'entity_type'
|
|
157
|
+
|
|
158
|
+
update! new_attributes
|
|
159
|
+
end
|
|
160
|
+
|
|
124
161
|
def tombstoned?
|
|
125
162
|
tombstoned_at.present?
|
|
126
163
|
end
|
|
@@ -129,6 +166,10 @@ module Federails
|
|
|
129
166
|
Federails::Utils::Actor.tombstone! self
|
|
130
167
|
end
|
|
131
168
|
|
|
169
|
+
def untombstone!
|
|
170
|
+
Federails::Utils::Actor.untombstone! self
|
|
171
|
+
end
|
|
172
|
+
|
|
132
173
|
class << self
|
|
133
174
|
# Searches for an actor from account URI
|
|
134
175
|
#
|
|
@@ -28,7 +28,7 @@ module Federails
|
|
|
28
28
|
|
|
29
29
|
def accept!
|
|
30
30
|
update! status: :accepted
|
|
31
|
-
Activity.create! actor: target_actor, action: 'Accept', entity: self
|
|
31
|
+
Activity.create! actor: target_actor, action: 'Accept', entity: self, to: [actor.federated_url]
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def follow_activity
|
|
@@ -62,11 +62,11 @@ module Federails
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def create_activity
|
|
65
|
-
Activity.create! actor: actor, action: 'Follow', entity: target_actor
|
|
65
|
+
Activity.create! actor: actor, action: 'Follow', entity: target_actor, to: [target_actor.federated_url]
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def destroy_activity
|
|
69
|
-
Activity.create! actor: actor, action: 'Undo', entity: follow_activity
|
|
69
|
+
Activity.create! actor: actor, action: 'Undo', entity: follow_activity, to: [target_actor.federated_url]
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'fediverse/node_info'
|
|
2
|
+
|
|
3
|
+
module Federails
|
|
4
|
+
class Host < ApplicationRecord
|
|
5
|
+
attribute :protocols, :json
|
|
6
|
+
attribute :services, :json
|
|
7
|
+
|
|
8
|
+
validates :domain, presence: true, allow_blank: false, uniqueness: true
|
|
9
|
+
|
|
10
|
+
# No "dependent" option here as this is not a hard reference, and we want to keep the actors if the host gets deleted
|
|
11
|
+
has_many :actors, class_name: 'Federails::Actor', primary_key: :domain, foreign_key: :server, inverse_of: :host # rubocop:disable Rails/HasManyOrHasOneDependent
|
|
12
|
+
|
|
13
|
+
scope :same_app, -> { where software_name: Configuration.app_name }
|
|
14
|
+
scope :same_app_and_version, -> { same_app.where app_version: Configuration.app_version }
|
|
15
|
+
|
|
16
|
+
def same_app?
|
|
17
|
+
software_name == Configuration.app_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def same_app_and_version?
|
|
21
|
+
software_name == Configuration.app_name && app_version == Configuration.app_version
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Update from remote data
|
|
25
|
+
def sync!
|
|
26
|
+
update! Fediverse::NodeInfo.fetch(domain)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
# Creates or update a Host
|
|
31
|
+
#
|
|
32
|
+
# @param domain [String] Domain to check
|
|
33
|
+
# @param min_update_interval [Integer, ActiveSupport::Duration] Minimum amount of seconds since the last update to fetch fresh data
|
|
34
|
+
def create_or_update(domain, min_update_interval: 0)
|
|
35
|
+
entry = find_or_initialize_by domain: domain
|
|
36
|
+
return if min_update_interval && entry.persisted? && (entry.updated_at + min_update_interval) > Time.current
|
|
37
|
+
|
|
38
|
+
entry.sync!
|
|
39
|
+
|
|
40
|
+
entry
|
|
41
|
+
rescue Fediverse::NodeInfo::NoActivityPubError
|
|
42
|
+
Rails.logger.info { "#{domain} does not provide ActivityPub service" }
|
|
43
|
+
rescue Federails::Utils::JsonRequest::UnhandledResponseStatus, Faraday::SSLError => e
|
|
44
|
+
Rails.logger.info { "Error connecting to #{domain}: '#{e.message}'" }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -17,7 +17,9 @@ Federation:
|
|
|
17
17
|
</ul>
|
|
18
18
|
|
|
19
19
|
<%# Debug information%>
|
|
20
|
-
<% if !
|
|
20
|
+
<% if !user %>
|
|
21
|
+
Register or login to view your actor's information
|
|
22
|
+
<% elsif !Federails::actor_entity?(user) %>
|
|
21
23
|
<p><%= user.class.name %> is not configured to have an associated actor; you won't be allowed to follow or be followed</p>
|
|
22
24
|
<% elsif !user.federails_actor.present? %>
|
|
23
25
|
<p>Your account does not have an associated actor; you won't be allowed to follow or be followed</p>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<% if actor.entity == user %>
|
|
11
11
|
<button type="button" role="button" disabled="disabled">That's you</button>
|
|
12
12
|
<% elsif follow %>
|
|
13
|
-
|
|
13
|
+
You are already following (<%= follow.status %>)
|
|
14
14
|
<%= button_to 'Cancel', federails.client_following_path(follow), method: :delete %>
|
|
15
15
|
<% else %>
|
|
16
16
|
<%= button_to "Follow #{actor.username}", federails.follow_client_followings_path, params: { account: actor.at_address }, method: :post %>
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
<%= button_to 'Revoke', federails.client_following_path(followed), method: :delete %>
|
|
27
27
|
<% end %>
|
|
28
28
|
<% end %>
|
|
29
|
+
<% elsif !user %>
|
|
30
|
+
Register or login to be able to follow this actor. Alternatively, search for this address on Fediverse server where
|
|
31
|
+
you already have an account:
|
|
32
|
+
<input type="text" readonly="readonly" value="<%= actor.at_address(prefix: '') %>">
|
|
29
33
|
<% else %>
|
|
30
34
|
<%= user.class.name %> is not configured to follow/be followed, or has no associated actor.
|
|
31
35
|
<% end %>
|