mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-28 15:26:21 +00:00
152 lines
4.6 KiB
Ruby
152 lines
4.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# = ActivityPub =
|
|
#
|
|
# El registro de actividades recibidas y su estado. Cuando recibimos
|
|
# una actividad, puede estar destinada a varies actores dentro de Sutty,
|
|
# con lo que generamos una cola para cada une.
|
|
#
|
|
#
|
|
# @todo Ya que une actore puede hacer varias actividades sobre el mismo
|
|
# objeto, lo correcto sería que la actividad a moderar sea una sola en
|
|
# lugar de una lista acumulativa. Es decir cada ActivityPub representa
|
|
# el estado del conjunto (Actor, Object, Activity)
|
|
#
|
|
# @see {https://www.w3.org/TR/activitypub/#client-to-server-interactions}
|
|
class ActivityPub < ApplicationRecord
|
|
IGNORED_EVENTS = %i[pause remove].freeze
|
|
IGNORED_STATES = %i[removed].freeze
|
|
|
|
include AASM
|
|
|
|
belongs_to :instance
|
|
belongs_to :site
|
|
belongs_to :object, polymorphic: true
|
|
belongs_to :actor
|
|
belongs_to :remote_flag, optional: true, class_name: 'ActivityPub::RemoteFlag'
|
|
has_many :activities
|
|
|
|
validates :site_id, presence: true
|
|
validates :object_id, presence: true
|
|
validates :aasm_state, presence: true, inclusion: { in: %w[paused approved rejected reported removed] }
|
|
|
|
accepts_nested_attributes_for :remote_flag
|
|
|
|
# Encuentra la URI de un objeto
|
|
#
|
|
# @return [String, nil]
|
|
def self.uri_from_object(object)
|
|
case object
|
|
when Array then uri_from_object(object.first)
|
|
when String then object
|
|
when Hash then (object['id'] || object[:id])
|
|
end
|
|
end
|
|
|
|
# Obtiene el campo `url` de diversas formas. Si es una String, asumir
|
|
# que es una URL, si es un Hash, asumir que es un Link, si es un
|
|
# Array de Strings, obtener la primera, si es de Hash, obtener el
|
|
# primer link con rel=canonical y mediaType=text/html
|
|
#
|
|
# De lo contrario devolver el ID.
|
|
#
|
|
# @todo Refactorizar
|
|
# @param object [Hash]
|
|
# @return [String]
|
|
def self.url_from_object(object)
|
|
raise unless object.respond_to?(:[])
|
|
|
|
url =
|
|
case object['url']
|
|
when String then object['url']
|
|
when Hash then object['href']
|
|
# Esto es un lío porque queremos saber si es un Array<Hash> o
|
|
# Array<String> o mezcla y obtener el que más nos convenga o
|
|
# adivinar uno.
|
|
when Array
|
|
links = object['url'].map.with_index do |link, _i|
|
|
case link
|
|
when Hash then link
|
|
else { 'href' => link.to_s }
|
|
end
|
|
end
|
|
|
|
links.find do |link|
|
|
link['rel'] == 'canonical' && link['mediaType'] == 'text/html'
|
|
end&.[]('href') || links.first&.[]('href')
|
|
end
|
|
|
|
url || object['id']
|
|
end
|
|
|
|
aasm do
|
|
# Todavía no hay una decisión sobre el objeto
|
|
state :paused, initial: true
|
|
# Le usuarie aprobó el objeto
|
|
state :approved
|
|
# Le usuarie rechazó el objeto
|
|
state :rejected
|
|
# Le usuarie reportó el objeto
|
|
state :reported
|
|
# Le actore eliminó el objeto
|
|
state :removed
|
|
|
|
# Gestionar todos los errores
|
|
error_on_all_events do |e|
|
|
ExceptionNotifier.notify_exception(e,
|
|
data: { site: site.name, activity_pub: id, activity: activities.first.uri })
|
|
end
|
|
|
|
# Se puede volver a pausa en caso de actualización remota, para
|
|
# revisar los cambios.
|
|
event :pause do
|
|
transitions to: :paused
|
|
end
|
|
|
|
# Recibir una acción de eliminación, eliminar el contenido de la
|
|
# base de datos. Esto elimina el contenido para todos los sitios
|
|
# porque estamos respetando lo que pidió le actore.
|
|
event :remove do
|
|
transitions to: :removed
|
|
|
|
after do
|
|
next if object.blank?
|
|
|
|
object.update(content: {}) unless object.content.empty?
|
|
end
|
|
end
|
|
|
|
# La actividad se aprueba, informándole a la Social Inbox que está
|
|
# aprobada. También recibimos la aprobación via
|
|
# webhook a modo de confirmación.
|
|
event :approve do
|
|
transitions from: %i[paused], to: :approved
|
|
|
|
after do
|
|
ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :accept)
|
|
end
|
|
end
|
|
|
|
# La actividad fue rechazada
|
|
event :reject do
|
|
transitions from: %i[paused approved], to: :rejected
|
|
|
|
after do
|
|
ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :reject)
|
|
end
|
|
end
|
|
|
|
# Reportarla implica rechazarla
|
|
event :report do
|
|
transitions from: %i[paused approved rejected], to: :reported
|
|
|
|
after do
|
|
ActivityPub::InboxJob.perform_later(site: site, activity: activities.first.uri, action: :reject)
|
|
ActivityPub::RemoteFlagJob.perform_later(remote_flag: remote_flag) if remote_flag.waiting?
|
|
end
|
|
end
|
|
end
|
|
|
|
# Definir eventos en masa
|
|
include AasmEventsConcern
|
|
end
|