5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-05-19 12:30:49 +00:00
panel/app/models/activity_pub.rb
2024-05-02 16:05:35 -03:00

153 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