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
         |