diff --git a/app/models/activity_pub/fediblock.rb b/app/models/activity_pub/fediblock.rb new file mode 100644 index 00000000..8d024f56 --- /dev/null +++ b/app/models/activity_pub/fediblock.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'httparty' + +# Listas de bloqueo y sus URLs de descarga +class ActivityPub + class Fediblock < ApplicationRecord + class Client + include ::HTTParty + + # @param url [String] + # @return [HTTParty::Response] + def get(url) + self.class.get(url, parser: csv_parser) + end + + # Procesa el CSV + # + # @return [Proc] + def csv_parser + @csv_parser ||= + begin + require 'csv' + + proc do |body, _| + CSV.parse(body, headers: true) + end + end + end + end + + class FediblockDownloadError < ::StandardError; end + + validates_presence_of :title, :url, :download_url, :format + validates_inclusion_of :format, in: %w[mastodon fediblock] + + HOSTNAME_HEADERS = { + 'mastodon' => '#domain', + 'fediblock' => 'domain' + } + + def client + @client ||= Client.new + end + + # Descarga la lista y crea las instancias con el estado necesario + def process! + response = client.get(download_url) + + raise FediblockDownloadError unless response.ok? + + Fediblock.transaction do + csv = response.parsed_response + process_csv! csv + + update(instances: csv.map { |r| r[hostname_header] }) + end + end + + private + + def hostname_header + HOSTNAME_HEADERS[format] + end + + # Crea o encuentra instancias que ya existían y las bloquea + # + # @param csv [CSV::Table] + def process_csv!(csv) + csv.each do |row| + ActivityPub::Instance.find_or_create_by(hostname: row[hostname_header]).tap do |i| + i.block! if i.may_block? + end + end + end + end +end diff --git a/app/models/activity_pub/instance.rb b/app/models/activity_pub/instance.rb index 42cd2695..749d98ac 100644 --- a/app/models/activity_pub/instance.rb +++ b/app/models/activity_pub/instance.rb @@ -21,6 +21,12 @@ class ActivityPub state :paused, initial: true state :allowed state :blocked + + # Al pasar una instancia a bloqueo, quiere decir que todos los + # sitios adoptan esta lista + event :block do + transitions from: %i[paused allowed], to: :blocked + end end def list_name diff --git a/db/migrate/20240227134845_create_fediblocks.rb b/db/migrate/20240227134845_create_fediblocks.rb new file mode 100644 index 00000000..03f65f7c --- /dev/null +++ b/db/migrate/20240227134845_create_fediblocks.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Las fediblocks son listas descargables de instancias bloqueadas. El +# formato hace una recomendación sobre suspensión o desfederación, pero +# nosotres bloqueamos todo. +class CreateFediblocks < ActiveRecord::Migration[6.1] + def up + create_table :activity_pub_fediblocks, id: :uuid do |t| + t.timestamps + + t.string :title, null: false + t.string :url, null: false + t.string :download_url, null: false + t.string :format, null: false + t.jsonb :instances, default: [] + end + + YAML.safe_load(File.read('db/seeds/activity_pub/fediblocks.yml')).each do |fediblock| + ActivityPub::Fediblock.create(**fediblock).process! + end + end + + def down + drop_table :activity_pub_fediblocks + end +end diff --git a/db/seeds.rb b/db/seeds.rb index b9ef96a1..8e8c291f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -27,3 +27,9 @@ if PrivacyPolicy.count.zero? PrivacyPolicy.new(**pp).save! end end + +YAML.safe_load(File.read('db/seeds/activity_pub/fediblocks.yml')).each do |fediblock| + ActivityPub::Fediblock.find_or_create_by(id: fediblock['id']).tap do |f| + f.update(**fediblock) + end +end diff --git a/db/seeds/activity_pub/fediblocks.yml b/db/seeds/activity_pub/fediblocks.yml new file mode 100644 index 00000000..c977f9bf --- /dev/null +++ b/db/seeds/activity_pub/fediblocks.yml @@ -0,0 +1,16 @@ +--- +- title: "Gardenfence" + url: "https://gardenfence.github.io/" + download_url: "https://github.com/gardenfence/blocklist/raw/main/gardenfence-fediblocksync.csv" + format: "fediblock" + id: "9046789a-5de8-4b16-beed-796060f8f3cc" +- title: "Oliphant Tier 0" + url: "https://writer.oliphant.social/oliphant/the-oliphant-social-blocklist" + download_url: "https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/mastodon/tier0.csv" + format: "mastodon" + id: "fc1efcb8-7e68-4a76-ae9e-0c447752b12b" +- title: "The Bad Space (90%)" + url: "https://tweaking.thebad.space/exports" + download_url: "https://tweaking.thebad.space/exports/mastodon/90" + format: "fediblock" + id: "5dd6705a-c28f-4912-9456-07b0d4983108" diff --git a/db/structure.sql b/db/structure.sql index c3896060..241039d1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -502,6 +502,22 @@ CREATE TABLE public.activity_pub_actors ( ); +-- +-- Name: activity_pub_fediblocks; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_pub_fediblocks ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + title character varying NOT NULL, + url character varying NOT NULL, + download_url character varying NOT NULL, + format character varying NOT NULL, + instances jsonb DEFAULT '[]'::jsonb +); + + -- -- Name: activity_pub_instances; Type: TABLE; Schema: public; Owner: - -- @@ -1694,6 +1710,14 @@ ALTER TABLE ONLY public.activity_pub_actors ADD CONSTRAINT activity_pub_actors_pkey PRIMARY KEY (id); +-- +-- Name: activity_pub_fediblocks activity_pub_fediblocks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_pub_fediblocks + ADD CONSTRAINT activity_pub_fediblocks_pkey PRIMARY KEY (id); + + -- -- Name: activity_pub_instances activity_pub_instances_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2546,6 +2570,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240221184007'), ('20240223170317'), ('20240226133022'), -('20240226134335'); +('20240226134335'), +('20240227134845');