federails 0.6.2 → 0.7.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/federails/client/actors_controller.rb +1 -1
- data/app/controllers/federails/client/followings_controller.rb +1 -1
- data/app/controllers/federails/server/nodeinfo_controller.rb +2 -2
- data/app/models/concerns/federails/actor_entity.rb +4 -0
- 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/actor.rb +37 -2
- 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/nodeinfo/index.nodeinfo.jbuilder +1 -1
- data/lib/federails/configuration.rb +7 -0
- data/lib/federails/maintenance/actors_updater.rb +3 -4
- data/lib/federails/utils/actor.rb +33 -1
- data/lib/federails/utils/json_request.rb +41 -0
- data/lib/federails/utils/object.rb +1 -1
- data/lib/federails/version.rb +1 -1
- data/lib/federails.rb +5 -0
- data/lib/fediverse/inbox.rb +10 -0
- data/lib/fediverse/node_info.rb +38 -0
- data/lib/fediverse/request.rb +6 -28
- data/lib/fediverse/webfinger.rb +10 -33
- 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 +21 -0
- metadata +22 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07b0025f15cc58379219f20956c53c0c3e6810e68ac60367b9728c9eff03220b
|
4
|
+
data.tar.gz: 22957563450ab52509a350e6e50f397cb564396c0927c510439ea1ad041106d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72e09779f3cdd23101c29e659f568f5dfddb5c85453c3853d49d1d7408b8017ea6c836b1b06afd6e3d62a6a476b8ce6746ec78b10e7f0d06ab32e825b947c258
|
7
|
+
data.tar.gz: 2b6bd12fb24cb50925e637c10f5f16453afa799a438695e36a77c3478c7394d3b3204ca8f6cbcddad49dfa1a031dd5a3ffaefb42c100b946f5c6ca8ae4550128
|
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
|
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -33,7 +33,9 @@ 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
|
38
40
|
|
39
41
|
scope :local, -> { where(local: true) }
|
@@ -42,6 +44,7 @@ module Federails
|
|
42
44
|
scope :not_tombstoned, -> { where(tombstoned_at: nil) }
|
43
45
|
|
44
46
|
on_federails_delete_requested -> { tombstone! }
|
47
|
+
on_federails_undelete_requested -> { untombstone! }
|
45
48
|
|
46
49
|
def distant?
|
47
50
|
!local?
|
@@ -96,8 +99,8 @@ module Federails
|
|
96
99
|
Rails.application.routes.url_helpers.send method, [entity]
|
97
100
|
end
|
98
101
|
|
99
|
-
def at_address
|
100
|
-
"
|
102
|
+
def at_address(prefix: '@')
|
103
|
+
"#{prefix}#{username}@#{server}"
|
101
104
|
end
|
102
105
|
|
103
106
|
def short_at_address
|
@@ -108,6 +111,9 @@ module Federails
|
|
108
111
|
"acct:#{username}@#{server}"
|
109
112
|
end
|
110
113
|
|
114
|
+
# Checks if a given actor follows the current actor
|
115
|
+
#
|
116
|
+
# @return [Federails::Following, false]
|
111
117
|
def follows?(actor)
|
112
118
|
list = following_follows.where target_actor: actor
|
113
119
|
return list.first if list.count == 1
|
@@ -115,12 +121,37 @@ module Federails
|
|
115
121
|
false
|
116
122
|
end
|
117
123
|
|
124
|
+
# Checks if current actor is followed by the given actor
|
125
|
+
#
|
126
|
+
# @return [Federails::Following, false]
|
127
|
+
def followed_by?(actor)
|
128
|
+
list = following_followers.where actor: actor
|
129
|
+
return list.first if list.count == 1
|
130
|
+
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
118
134
|
def entity_configuration
|
119
135
|
raise("Entity not configured for #{entity_type}. Did you use \"acts_as_federails_actor\"?") unless Federails.actor_entity? entity_type
|
120
136
|
|
121
137
|
Federails.actor_entity entity_type
|
122
138
|
end
|
123
139
|
|
140
|
+
# Synchronizes actor with distant data
|
141
|
+
#
|
142
|
+
# @raise [ActiveRecord::RecordNotFound] when distant data was not found
|
143
|
+
def sync!
|
144
|
+
if local?
|
145
|
+
Rails.logger.info 'Ignored attempt to sync a local actor'
|
146
|
+
return false
|
147
|
+
end
|
148
|
+
|
149
|
+
response = Fediverse::Webfinger.fetch_actor_url(federated_url)
|
150
|
+
new_attributes = response.attributes.except 'id', 'uuid', 'created_at', 'updated_at', 'local', 'entity_id', 'entity_type'
|
151
|
+
|
152
|
+
update! new_attributes
|
153
|
+
end
|
154
|
+
|
124
155
|
def tombstoned?
|
125
156
|
tombstoned_at.present?
|
126
157
|
end
|
@@ -129,6 +160,10 @@ module Federails
|
|
129
160
|
Federails::Utils::Actor.tombstone! self
|
130
161
|
end
|
131
162
|
|
163
|
+
def untombstone!
|
164
|
+
Federails::Utils::Actor.untombstone! self
|
165
|
+
end
|
166
|
+
|
132
167
|
class << self
|
133
168
|
# Searches for an actor from account URI
|
134
169
|
#
|
@@ -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 %>
|
@@ -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>
|
@@ -58,6 +58,13 @@ module Federails
|
|
58
58
|
# @!method self.remote_follow_url_method=(value)
|
59
59
|
#
|
60
60
|
# Sets the route method for remote-following requests
|
61
|
+
#
|
62
|
+
# The route should lead to a page displaying the remote actor and a button to follow it.
|
63
|
+
# Remote actor is specified in the `uri` query parameter.
|
64
|
+
#
|
65
|
+
# Its value defaults to a route of the Federails client, so your application will break
|
66
|
+
# if you don't use the client routes and don't override this value.
|
67
|
+
#
|
61
68
|
# @param value [String] Route method name as used in links
|
62
69
|
# @example
|
63
70
|
# remote_follow_url_method 'main_app.my_custom_route_helper'
|
@@ -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
|
@@ -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,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,12 @@
|
|
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'
|
5
10
|
|
6
11
|
# rubocop:disable Style/ClassVars
|
7
12
|
|
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/request.rb
CHANGED
@@ -2,27 +2,7 @@ require 'json/ld'
|
|
2
2
|
|
3
3
|
module Fediverse
|
4
4
|
class Request
|
5
|
-
BASE_HEADERS = {
|
6
|
-
'Content-Type' => 'application/json',
|
7
|
-
'Accept' => 'application/json',
|
8
|
-
}.freeze
|
9
|
-
|
10
|
-
def initialize(id)
|
11
|
-
@id = id
|
12
|
-
end
|
13
|
-
|
14
|
-
# FIXME: Replace by `Webfinger.get_json` (move other method here as class method)
|
15
|
-
def get
|
16
|
-
Rails.logger.debug { "GET #{@id}" }
|
17
|
-
@response = Faraday.get(@id, nil, BASE_HEADERS)
|
18
|
-
response_to_json
|
19
|
-
end
|
20
|
-
|
21
5
|
class << self
|
22
|
-
def get(id)
|
23
|
-
new(id).get
|
24
|
-
end
|
25
|
-
|
26
6
|
# Dereferences a value
|
27
7
|
#
|
28
8
|
# @param value [String, Hash]
|
@@ -34,18 +14,16 @@ module Fediverse
|
|
34
14
|
|
35
15
|
raise "Unhandled object type #{value.class}"
|
36
16
|
end
|
37
|
-
end
|
38
17
|
|
39
|
-
|
18
|
+
private
|
40
19
|
|
41
|
-
|
42
|
-
|
43
|
-
|
20
|
+
def get(id)
|
21
|
+
json = Federails::Utils::JsonRequest.get_json(id)
|
22
|
+
|
23
|
+
JSON::LD::API.compact json, json['@context']
|
44
24
|
rescue JSON::ParserError
|
45
|
-
|
25
|
+
nil
|
46
26
|
end
|
47
|
-
|
48
|
-
JSON::LD::API.compact body, body['@context']
|
49
27
|
end
|
50
28
|
end
|
51
29
|
end
|
data/lib/fediverse/webfinger.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
require 'faraday'
|
2
|
-
require 'faraday/follow_redirects'
|
3
|
-
|
4
1
|
require 'federails/utils/host'
|
2
|
+
require 'federails/utils/json_request'
|
5
3
|
|
6
4
|
module Fediverse
|
7
5
|
# Methods related to Webfinger: find accounts, fetch actors,...
|
@@ -122,42 +120,21 @@ module Fediverse
|
|
122
120
|
# Makes a simple GET request and returns a +Hash+ from the parsed body
|
123
121
|
# @return [Hash]
|
124
122
|
# @raise [ActiveRecord::RecordNotFound] when the response is invalid
|
125
|
-
def get_json(url,
|
126
|
-
|
123
|
+
def get_json(url, params = {})
|
124
|
+
Federails::Utils::JsonRequest.get_json(url, params: params, follow_redirects: true, headers: { accept: 'application/json' })
|
125
|
+
rescue Federails::Utils::JsonRequest::UnhandledResponseStatus => e
|
126
|
+
Rails.logger.debug { e.message }
|
127
127
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
128
|
+
raise ActiveRecord::RecordNotFound
|
129
|
+
rescue Faraday::ConnectionFailed
|
130
|
+
Rails.logger.debug { "Failed to reach server for GET #{url}" }
|
132
131
|
|
133
|
-
|
132
|
+
raise ActiveRecord::RecordNotFound
|
134
133
|
rescue JSON::ParserError
|
135
|
-
Rails.logger.debug { "Invalid JSON response GET #{url}" }
|
134
|
+
Rails.logger.debug { "Invalid JSON response for GET #{url}" }
|
136
135
|
|
137
136
|
raise ActiveRecord::RecordNotFound
|
138
137
|
end
|
139
|
-
|
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
|
146
|
-
def get(url, payload: {}, headers: {})
|
147
|
-
connection = Faraday.new url: url, params: payload, headers: headers do |faraday|
|
148
|
-
faraday.response :follow_redirects # use Faraday::FollowRedirects::Middleware
|
149
|
-
faraday.adapter Faraday.default_adapter
|
150
|
-
end
|
151
|
-
|
152
|
-
begin
|
153
|
-
response = connection.get
|
154
|
-
rescue Faraday::ConnectionFailed
|
155
|
-
Rails.logger.debug { "Failed to reach server for GET #{url}" }
|
156
|
-
raise ActiveRecord::RecordNotFound
|
157
|
-
end
|
158
|
-
|
159
|
-
response
|
160
|
-
end
|
161
138
|
end
|
162
139
|
end
|
163
140
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Federails
|
2
|
+
class CopyClientPoliciesGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path('../../../../app/policies/federails', __dir__)
|
4
|
+
|
5
|
+
def copy_policies
|
6
|
+
directory 'client', Rails.root.join('app', 'policies', 'federails', 'client')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Federails
|
2
|
+
class CopyFactoriesGenerator < Rails::Generators::Base
|
3
|
+
SOURCE_DIRECTORY = File.expand_path('../../../../spec/factories/federails', __dir__)
|
4
|
+
|
5
|
+
source_root SOURCE_DIRECTORY
|
6
|
+
|
7
|
+
def copy_factories
|
8
|
+
dest = Rails.root.join('spec', 'factories')
|
9
|
+
|
10
|
+
Dir.entries(SOURCE_DIRECTORY)
|
11
|
+
.each do |node|
|
12
|
+
source_path = File.join(SOURCE_DIRECTORY, node)
|
13
|
+
next unless File.file?(source_path) && node.match?(/\.rb\Z/)
|
14
|
+
|
15
|
+
file_path = File.join(dest, "federails_#{node}")
|
16
|
+
copy_file node, file_path
|
17
|
+
gsub_file file_path, /(FactoryBot.define do\n\s+factory) :(\w+),/, '\1 :federails_\2,'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: federails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manuel Tancoigne
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-05-10 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: faraday
|
@@ -37,6 +37,20 @@ dependencies:
|
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: jbuilder
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.7'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.7'
|
40
54
|
- !ruby/object:Gem::Dependency
|
41
55
|
name: json-ld
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
@@ -209,16 +223,22 @@ files:
|
|
209
223
|
- lib/federails/maintenance/actors_updater.rb
|
210
224
|
- lib/federails/utils/actor.rb
|
211
225
|
- lib/federails/utils/host.rb
|
226
|
+
- lib/federails/utils/json_request.rb
|
212
227
|
- lib/federails/utils/object.rb
|
213
228
|
- lib/federails/version.rb
|
214
229
|
- lib/fediverse.rb
|
215
230
|
- lib/fediverse/inbox.rb
|
231
|
+
- lib/fediverse/node_info.rb
|
216
232
|
- lib/fediverse/notifier.rb
|
217
233
|
- lib/fediverse/request.rb
|
218
234
|
- lib/fediverse/signature.rb
|
219
235
|
- lib/fediverse/webfinger.rb
|
236
|
+
- lib/generators/federails/copy_client_policies/USAGE
|
237
|
+
- lib/generators/federails/copy_client_policies/copy_client_policies_generator.rb
|
220
238
|
- lib/generators/federails/copy_client_views/USAGE
|
221
239
|
- lib/generators/federails/copy_client_views/copy_client_views_generator.rb
|
240
|
+
- lib/generators/federails/copy_factories/USAGE
|
241
|
+
- lib/generators/federails/copy_factories/copy_factories_generator.rb
|
222
242
|
- lib/generators/federails/install/USAGE
|
223
243
|
- lib/generators/federails/install/install_generator.rb
|
224
244
|
- lib/generators/federails/install/templates/federails.rb
|