defmodule Mobilizon.Service.Workers.RefreshInstances do @moduledoc """ Worker to refresh the instances materialized view and the relay actors """ use Oban.Worker alias Mobilizon.Actors.Actor alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor alias Mobilizon.Federation.ActivityPub.Relay alias Mobilizon.Federation.NodeInfo alias Mobilizon.Instances alias Mobilizon.Instances.{Instance, InstanceActor} alias Oban.Job require Logger import Mobilizon.Storage.Ecto, only: [convert_ecto_errors: 1] @impl Oban.Worker @spec perform(Oban.Job.t()) :: :ok def perform(%Job{}) do Instances.refresh() Instances.all_domains() |> Enum.each(fn %Instance{domain: domain} -> refresh_instance_actor(domain) end) end @spec refresh_instance_actor(String.t() | nil) :: {:ok, Mobilizon.Actors.Actor.t()} | {:error, Ecto.Changeset.t()} | {:error, atom} def refresh_instance_actor(nil) do {:error, :not_remote_instance} end def refresh_instance_actor(domain) do %Actor{url: url} = Relay.get_actor() %URI{host: host} = URI.new!(url) if host == domain do {:error, :not_remote_instance} else actor_id = case fetch_actor(domain) do {:ok, %Actor{id: actor_id}} -> actor_id _ -> nil end with instance_metadata <- fetch_instance_metadata(domain), args <- %{ domain: domain, actor_id: actor_id, instance_name: get_in(instance_metadata, ["metadata", "nodeName"]), instance_description: get_in(instance_metadata, ["metadata", "nodeDescription"]), software: get_in(instance_metadata, ["software", "name"]), software_version: get_in(instance_metadata, ["software", "version"]) }, :ok <- Logger.debug("Ready to save instance actor details #{inspect(args)}"), {:ok, %InstanceActor{}} <- Instances.create_instance_actor(args) do Logger.info("Saved instance actor details for domain #{host}") else {:error, %Ecto.Changeset{} = changeset} -> Logger.error("Unable to save instance \"#{domain}\" metadata") Logger.debug(convert_ecto_errors(changeset)) err -> Logger.error(inspect(err)) end end end defp mobilizon(domain), do: "relay@#{domain}" defp peertube(domain), do: "peertube@#{domain}" defp mastodon(domain), do: "#{domain}@#{domain}" defp fetch_actor(domain) do case NodeInfo.application_actor(domain) do nil -> guess_application_actor(domain) url -> ActivityPubActor.get_or_fetch_actor_by_url(url) end end defp fetch_instance_metadata(domain) do case NodeInfo.nodeinfo(domain) do {:error, _} -> %{} {:ok, metadata} -> metadata end end defp guess_application_actor(domain) do Enum.find_value( [ &mobilizon/1, &peertube/1, &mastodon/1 ], {:error, :no_application_actor_found}, fn username_pattern -> case ActivityPubActor.find_or_make_actor_from_nickname(username_pattern.(domain)) do {:ok, %Actor{type: :Application} = actor} -> {:ok, actor} {:error, _err} -> false {:ok, _actor} -> false end end ) end end