diff --git a/lib/vonbraun/activity_pub/handler/accept.ex b/lib/vonbraun/activity_pub/handler/accept.ex index f69a08d..10da07a 100644 --- a/lib/vonbraun/activity_pub/handler/accept.ex +++ b/lib/vonbraun/activity_pub/handler/accept.ex @@ -23,12 +23,12 @@ defmodule Vonbraun.ActivityPub.Handler.Accept do {:ok, actor_id} else _ -> - {:error, :notfound} + {:error, :not_found} end end def extract_follow_object_actor(_) do - {:error, :notfound} + {:error, :not_found} end # Lots of kinds of things can be accepted but for right now only follows. diff --git a/lib/vonbraun/activity_pub/object.ex b/lib/vonbraun/activity_pub/object.ex index 3f675d6..2bbc2ff 100644 --- a/lib/vonbraun/activity_pub/object.ex +++ b/lib/vonbraun/activity_pub/object.ex @@ -51,12 +51,16 @@ defmodule Vonbraun.ActivityPub.Object do } end - def my_follow_activity_id(to_follow_id) when is_binary(to_follow_id), - do: - "https://#{Application.fetch_env!(:vonbraun, :domain)}/id/follow:#{URI.encode(to_follow_id)}" + @spec create_actor_activity_id(String.t(), String.t()) :: String.t() + def create_actor_activity_id(actor_id, verb) when is_binary(actor_id) and is_binary(verb) do + "https://#{Application.fetch_env!(:vonbraun, :domain)}/id/#{URI.encode(verb)}:#{URI.encode(actor_id)}" + end def follow_activity(to_follow_id) when is_binary(to_follow_id), - do: activity("Follow", my_follow_activity_id(to_follow_id), to_follow_id, to: to_follow_id) + do: + activity("Follow", create_actor_activity_id(to_follow_id, "follow"), to_follow_id, + to: to_follow_id + ) def accept_follow_activity(followee_id, activity_id, type \\ :accept) when is_binary(followee_id) and is_binary(activity_id) and type in [:accept, :reject] do @@ -74,7 +78,7 @@ defmodule Vonbraun.ActivityPub.Object do } accept_activity_id = - "https://#{Application.fetch_env!(:vonbraun, :domain)}/id/follow-reply:#{URI.encode(followee_id)}" + create_actor_activity_id(followee_id, "follow-reply") activity(activity_type, accept_activity_id, object, to: followee_id) end diff --git a/lib/vonbraun/activitypub_req.ex b/lib/vonbraun/activitypub_req.ex index 05f2f07..2595e6f 100644 --- a/lib/vonbraun/activitypub_req.ex +++ b/lib/vonbraun/activitypub_req.ex @@ -86,6 +86,6 @@ defmodule Vonbraun.ActivityPubReq do end def extract_actor_inbox(%{}) do - {:error, :notfound} + {:error, :not_found} end end diff --git a/lib/vonbraun/control.ex b/lib/vonbraun/control.ex index c5e8b20..dca4087 100644 --- a/lib/vonbraun/control.ex +++ b/lib/vonbraun/control.ex @@ -58,7 +58,49 @@ defmodule Vonbraun.Control do end end - def unfollow(id) when is_binary(id) do + def unfollow(followee_actor_id) when is_binary(followee_actor_id) do + with {:actor, {:ok, actor}} <- {:actor, ActivityPubReq.get_cached_actor(followee_actor_id)}, + {:inbox, {:ok, raw_inbox}} <- {:inbox, ActivityPubReq.extract_actor_inbox(actor)}, + {:parse_inbox, inbox = %URI{}} <- {:parse_inbox, URI.parse(raw_inbox)}, + {:state, {:ok, %Actor{}}} <- + {:state, Actor.remove_followee(followee_actor_id)} do + object = Object.follow_activity(followee_actor_id) + activity_id = Object.create_actor_activity_id(followee_actor_id, "unfollow") + activity = Object.activity("Undo", activity_id, object) + payload = Jason.encode!(activity) + + case ActivityPubReq.post(inbox, payload) do + {:ok, %{:status => status, :body => body}} when status >= 200 and status <= 299 -> + Logger.debug(inspect(body)) + Logger.info("Unfollow request sent successfully, now it's up to the remote server.") + :ok + + {:ok, %{:status => status, :body => body}} -> + Logger.debug(inspect(body)) + Logger.warning("Got a status of: #{status}, probably not good.") + {:error, {:status, status}} + + {:error, error} -> + Logger.error("Failed to send unfollow request: #{inspect(error)}") + {:error, {:post, error}} + end + else + {:inbox, {:error, _}} -> + Logger.warning("Actor had an invalid inbox.") + {:error, :invalid_inbox} + + {:actor, {:error, error}} -> + Logger.warning("Failed to query remote actor: #{inspect(error)}") + {:error, {:actor, error}} + + {:state, error = {:error, :not_following}} -> + Logger.warning("Not following user.") + error + + {:state, {:error, error}} -> + Logger.error("Unexpected error: #{inspect(error)}") + {:error, error} + end end def post_note(content, public?, to \\ []) diff --git a/lib/vonbraun/ecto/schema/actor.ex b/lib/vonbraun/ecto/schema/actor.ex index a3537c8..81b2c32 100644 --- a/lib/vonbraun/ecto/schema/actor.ex +++ b/lib/vonbraun/ecto/schema/actor.ex @@ -181,6 +181,28 @@ defmodule Vonbraun.Ecto.Schema.Actor do end end + @spec remove_followee(String.t()) :: {:ok, __MODULE__.t()} | {:error, any()} + @doc """ + Mark a remote actor as no longer being followed. + """ + def remove_followee(id) when is_binary(id) do + case Repo.get(__MODULE__, id) do + nil -> + {:error, :not_following} + + actor = %__MODULE__{:following_state => following_state} + when following_state in ["accepted", "pending"] -> + now = DateTime.now!("Etc/UTC") + + changeset(actor, %{following_state: nil, following_ts: now}) + |> Repo.update() + + %__MODULE__{:following_state => following_state} + when following_state == "rejected" or is_nil(following_state) -> + {:error, :not_following} + end + end + @spec get_my_followers() :: list(String.t()) def get_my_followers() do query =