From 5721c5fe058620f2f69450a7c5ff0a03d14b5247 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <thomas.citharel@framasoft.org>
Date: Mon, 12 Nov 2018 09:05:31 +0100
Subject: [PATCH] Toot

---
 config/config.exs                             |   2 +
 lib/mix/tasks/toot.ex                         |  34 +++++
 lib/mobilizon/actors/actor.ex                 |  25 +++-
 lib/mobilizon/actors/actors.ex                |  55 ++++---
 lib/mobilizon/events/comment.ex               |  15 +-
 lib/mobilizon/events/events.ex                |  10 +-
 .../controllers/activity_pub_controller.ex    |  37 ++++-
 lib/mobilizon_web/router.ex                   |   4 +-
 .../views/activity_pub/actor_view.ex          |   6 +-
 .../views/activity_pub/object_view.ex         |  44 +++---
 lib/service/activity_pub/activity_pub.ex      |  47 ++++--
 lib/service/activity_pub/transmogrifier.ex    | 137 +++---------------
 lib/service/activity_pub/utils.ex             |  69 ++++++---
 lib/service/federator.ex                      |   2 +-
 .../http_signatures/http_signatures.ex        |  28 ++--
 lib/service/web_finger/web_finger.ex          |   2 +-
 16 files changed, 289 insertions(+), 228 deletions(-)
 create mode 100644 lib/mix/tasks/toot.ex

diff --git a/config/config.exs b/config/config.exs
index 33b032d9c..0b63f9ebe 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -65,3 +65,5 @@ config :arc,
 
 config :email_checker,
   validations: [EmailChecker.Check.Format]
+
+config :phoenix, :format_encoders, json: Jason
diff --git a/lib/mix/tasks/toot.ex b/lib/mix/tasks/toot.ex
new file mode 100644
index 000000000..d208a030d
--- /dev/null
+++ b/lib/mix/tasks/toot.ex
@@ -0,0 +1,34 @@
+defmodule Mix.Tasks.Toot do
+  @moduledoc """
+  Creates a bot from a source
+  """
+
+  use Mix.Task
+  alias Mobilizon.Actors
+  alias Mobilizon.Actors.Actor
+  alias Mobilizon.Repo
+  alias Mobilizon.Events
+  alias Mobilizon.Events.Comment
+  alias Mobilizon.Service.ActivityPub
+  alias Mobilizon.Service.ActivityPub.Utils
+  require Logger
+
+  @shortdoc "Toot to an user"
+  def run([from, to, content]) do
+    Mix.Task.run("app.start")
+
+    with %Actor{} = from <- Actors.get_actor_by_name(from),
+         {:ok, %Actor{} = to} <- ActivityPub.find_or_make_actor_from_nickname(to) do
+      comment = Utils.make_comment_data(from.url, [to.url], content)
+
+      ActivityPub.create(%{
+        to: [to.url],
+        actor: from,
+        object: comment,
+        local: true
+      })
+    else
+      e -> Logger.error(inspect(e))
+    end
+  end
+end
diff --git a/lib/mobilizon/actors/actor.ex b/lib/mobilizon/actors/actor.ex
index f0b5ebaac..c4f5f5238 100644
--- a/lib/mobilizon/actors/actor.ex
+++ b/lib/mobilizon/actors/actor.ex
@@ -244,20 +244,29 @@ defmodule Mobilizon.Actors.Actor do
     )
   end
 
-  def follow(%Actor{} = follower, %Actor{} = followed) do
-    # Check if actor is locked
-    # Check if followed has blocked follower
-    # Check if follower already follows followed
-    cond do
-      following?(follower, followed) ->
+  def follow(%Actor{} = follower, %Actor{} = followed, approved \\ true) do
+    with {:suspended, false} <- {:suspended, followed.suspended},
+         # Check if followed has blocked follower
+         {:already_following, false} <- {:already_following, following?(follower, followed)} do
+      do_follow(follower, followed, approved)
+    else
+      {:already_following, _} ->
         {:error,
          "Could not follow actor: you are already following #{followed.preferred_username}"}
 
-        # true -> nil
-        # Follow the person
+      {:suspended, _} ->
+        {:error, "Could not follow actor: #{followed.preferred_username} has been suspended"}
     end
   end
 
+  defp do_follow(%Actor{} = follower, %Actor{} = followed, approved \\ true) do
+    Actors.create_follower(%{
+      "actor_id" => follower.id,
+      "target_actor_id" => followed.id,
+      "approved" => approved
+    })
+  end
+
   def following?(%Actor{} = follower, %Actor{followers: followers}) do
     Enum.member?(followers, follower)
   end
diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex
index bb70d770d..427661ef4 100644
--- a/lib/mobilizon/actors/actors.ex
+++ b/lib/mobilizon/actors/actors.ex
@@ -203,21 +203,24 @@ defmodule Mobilizon.Actors do
   defp blank?(""), do: nil
   defp blank?(n), do: n
 
-  def insert_or_update_actor(data) do
+  def insert_or_update_actor(data, preload \\ false) do
     cs = Actor.remote_actor_creation(data)
 
-    Repo.insert(
-      cs,
-      on_conflict: [
-        set: [
-          keys: data.keys,
-          avatar_url: data.avatar_url,
-          banner_url: data.banner_url,
-          name: data.name
-        ]
-      ],
-      conflict_target: [:preferred_username, :domain]
-    )
+    actor =
+      Repo.insert(
+        cs,
+        on_conflict: [
+          set: [
+            keys: data.keys,
+            avatar_url: data.avatar_url,
+            banner_url: data.banner_url,
+            name: data.name
+          ]
+        ],
+        conflict_target: [:preferred_username, :domain]
+      )
+
+    if preload, do: {:ok, Repo.preload(actor, [:followers])}, else: {:ok, actor}
   end
 
   #  def increase_event_count(%Actor{} = actor) do
@@ -267,8 +270,24 @@ defmodule Mobilizon.Actors do
     end
   end
 
-  def get_actor_by_url(url) do
-    Repo.get_by(Actor, url: url)
+  @doc """
+  Get an actor by it's URL (ActivityPub ID)
+  """
+  @spec get_actor_by_url(String.t(), boolean()) :: {:ok, struct()} | {:error, :actor_not_found}
+  def get_actor_by_url(url, preload \\ false) do
+    case Repo.get_by(Actor, url: url) do
+      nil ->
+        {:error, :actor_not_found}
+
+      actor ->
+        if preload, do: {:ok, Repo.preload(actor, [:followers])}, else: {:ok, actor}
+    end
+  end
+
+  @spec get_actor_by_url!(String.t(), boolean()) :: struct()
+  def get_actor_by_url!(url, preload \\ false) do
+    actor = Repo.get_by!(Actor, url: url)
+    if preload, do: Repo.preload(actor, [:followers]), else: actor
   end
 
   def get_actor_by_name(name) do
@@ -304,11 +323,11 @@ defmodule Mobilizon.Actors do
     Repo.preload(actor, :organized_events)
   end
 
-  def get_or_fetch_by_url(url) do
-    if actor = get_actor_by_url(url) do
+  def get_or_fetch_by_url(url, preload \\ false) do
+    if {:ok, actor} = get_actor_by_url(url, preload) do
       {:ok, actor}
     else
-      case ActivityPub.make_actor_from_url(url) do
+      case ActivityPub.make_actor_from_url(url, preload) do
         {:ok, actor} ->
           {:ok, actor}
 
diff --git a/lib/mobilizon/events/comment.ex b/lib/mobilizon/events/comment.ex
index e9a960b85..d49e459ed 100644
--- a/lib/mobilizon/events/comment.ex
+++ b/lib/mobilizon/events/comment.ex
@@ -26,7 +26,10 @@ defmodule Mobilizon.Events.Comment do
 
   @doc false
   def changeset(comment, attrs) do
-    uuid = Ecto.UUID.generate()
+    uuid =
+      if Map.has_key?(attrs, "uuid"),
+        do: attrs["uuid"],
+        else: Ecto.UUID.generate()
 
     # TODO : really change me right away
     url =
@@ -35,7 +38,15 @@ defmodule Mobilizon.Events.Comment do
         else: "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}"
 
     comment
-    |> cast(attrs, [:url, :text, :actor_id, :event_id, :in_reply_to_comment_id, :origin_comment_id, :attributed_to_id])
+    |> cast(attrs, [
+      :url,
+      :text,
+      :actor_id,
+      :event_id,
+      :in_reply_to_comment_id,
+      :origin_comment_id,
+      :attributed_to_id
+    ])
     |> put_change(:uuid, uuid)
     |> put_change(:url, url)
     |> validate_required([:text, :actor_id, :url])
diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex
index cf3164bf9..a2c5b63d4 100644
--- a/lib/mobilizon/events/events.ex
+++ b/lib/mobilizon/events/events.ex
@@ -885,7 +885,15 @@ defmodule Mobilizon.Events do
   """
   def get_comment!(id), do: Repo.get!(Comment, id)
 
-  def get_comment_with_uuid!(uuid), do: Repo.get_by!(Comment, uuid: uuid)
+  def get_comment_from_uuid(uuid), do: Repo.get_by(Comment, uuid: uuid)
+
+  def get_comment_from_uuid!(uuid), do: Repo.get_by!(Comment, uuid: uuid)
+
+  def get_comment_full_from_uuid(uuid) do
+    with %Comment{} = comment <- Repo.get_by!(Comment, uuid: uuid) do
+      Repo.preload(comment, [:actor, :attributed_to])
+    end
+  end
 
   def get_comment_from_url(url), do: Repo.get_by(Comment, url: url)
 
diff --git a/lib/mobilizon_web/controllers/activity_pub_controller.ex b/lib/mobilizon_web/controllers/activity_pub_controller.ex
index 7d377df63..65b806d06 100644
--- a/lib/mobilizon_web/controllers/activity_pub_controller.ex
+++ b/lib/mobilizon_web/controllers/activity_pub_controller.ex
@@ -1,6 +1,7 @@
 defmodule MobilizonWeb.ActivityPubController do
   use MobilizonWeb, :controller
-  alias Mobilizon.{Actors, Actors.Actor, Events, Events.Event}
+  alias Mobilizon.{Actors, Actors.Actor, Events}
+  alias Mobilizon.Events.{Event, Comment}
   alias MobilizonWeb.ActivityPub.{ObjectView, ActorView}
   alias Mobilizon.Service.ActivityPub
   alias Mobilizon.Service.Federator
@@ -9,16 +10,18 @@ defmodule MobilizonWeb.ActivityPubController do
 
   action_fallback(:errors)
 
+  @activity_pub_headers [
+    "application/activity+json",
+    "application/activity+json, application/ld+json"
+  ]
+
   def actor(conn, %{"name" => name}) do
     with %Actor{} = actor <- Actors.get_local_actor_by_name(name) do
-      case get_req_header(conn, "accept") do
-        ["application/activity+json"] ->
+      cond do
+        conn |> get_req_header("accept") |> is_ap_header() ->
           conn |> render_ap_actor(actor)
 
-        ["application/activity+json, application/ld+json"] ->
-          conn |> render_ap_actor(actor)
-
-        _ ->
+        true ->
           conn
           |> put_resp_content_type("text/html")
           |> send_file(200, "priv/static/index.html")
@@ -28,6 +31,10 @@ defmodule MobilizonWeb.ActivityPubController do
     end
   end
 
+  defp is_ap_header(ap_headers) do
+    length(@activity_pub_headers -- ap_headers) < 2
+  end
+
   defp render_ap_actor(conn, %Actor{} = actor) do
     conn
     |> put_resp_header("content-type", "application/activity+json")
@@ -41,7 +48,21 @@ defmodule MobilizonWeb.ActivityPubController do
       |> put_resp_header("content-type", "application/activity+json")
       |> json(ObjectView.render("event.json", %{event: event}))
     else
-      false ->
+      _ ->
+        {:error, :not_found}
+    end
+  end
+
+  def comment(conn, %{"uuid" => uuid}) do
+    with %Comment{} = comment <- Events.get_comment_full_from_uuid(uuid) do
+      # Comments are always public for now
+      # TODO : Make comments maybe restricted
+      # true <- comment.public do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(ObjectView.render("comment.json", %{comment: comment}))
+    else
+      _ ->
         {:error, :not_found}
     end
   end
diff --git a/lib/mobilizon_web/router.ex b/lib/mobilizon_web/router.ex
index bda486b23..c40e6774f 100644
--- a/lib/mobilizon_web/router.ex
+++ b/lib/mobilizon_web/router.ex
@@ -14,7 +14,7 @@ defmodule MobilizonWeb.Router do
   end
 
   pipeline :activity_pub do
-    plug(:accepts, ["activity-json", "text/html"])
+    plug(:accepts, ["activity-json", "html"])
     plug(MobilizonWeb.HTTPSignaturePlug)
   end
 
@@ -55,7 +55,7 @@ defmodule MobilizonWeb.Router do
     get("/@:name/following", ActivityPubController, :following)
     get("/@:name/followers", ActivityPubController, :followers)
     get("/events/:uuid", ActivityPubController, :event)
-    get("/comments/:uuid", ActivityPubController, :event)
+    get("/comments/:uuid", ActivityPubController, :comment)
     post("/@:name/inbox", ActivityPubController, :inbox)
     post("/inbox", ActivityPubController, :inbox)
   end
diff --git a/lib/mobilizon_web/views/activity_pub/actor_view.ex b/lib/mobilizon_web/views/activity_pub/actor_view.ex
index 8fdfb536f..6a94747bb 100644
--- a/lib/mobilizon_web/views/activity_pub/actor_view.ex
+++ b/lib/mobilizon_web/views/activity_pub/actor_view.ex
@@ -32,6 +32,8 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
         "owner" => actor.url,
         "publicKeyPem" => public_key
       },
+      # TODO : Make have actors have an uuid
+      # "uuid" => actor.uuid
       "endpoints" => %{
         "sharedInbox" => actor.shared_inbox_url
       }
@@ -135,6 +137,7 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
           "Announce"
         end,
       "actor" => activity.actor,
+      # Not sure if needed since this is used into outbox
       "published" => Timex.now(),
       "to" => ["https://www.w3.org/ns/activitystreams#Public"],
       "object" =>
@@ -143,9 +146,10 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
             render_one(activity.data, ObjectView, "event.json", as: :event)
 
           :Comment ->
-            render_one(activity.data, ObjectView, "note.json", as: :note)
+            render_one(activity.data, ObjectView, "comment.json", as: :comment)
         end
     }
+    |> Map.merge(Utils.make_json_ld_header())
   end
 
   def collection(collection, iri, page, total \\ nil) do
diff --git a/lib/mobilizon_web/views/activity_pub/object_view.ex b/lib/mobilizon_web/views/activity_pub/object_view.ex
index f43d4463b..83ccab60d 100644
--- a/lib/mobilizon_web/views/activity_pub/object_view.ex
+++ b/lib/mobilizon_web/views/activity_pub/object_view.ex
@@ -2,20 +2,7 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do
   use MobilizonWeb, :view
   alias MobilizonWeb.ActivityPub.ObjectView
   alias Mobilizon.Service.ActivityPub.Transmogrifier
-
-  @base %{
-    "@context" => [
-      "https://www.w3.org/ns/activitystreams",
-      "https://w3id.org/security/v1",
-      %{
-        "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
-        "sensitive" => "as:sensitive",
-        "Hashtag" => "as:Hashtag",
-        "toot" => "http://joinmastodon.org/ns#",
-        "Emoji" => "toot:Emoji"
-      }
-    ]
-  }
+  alias Mobilizon.Service.ActivityPub.Utils
 
   def render("event.json", %{event: event}) do
     event = %{
@@ -24,29 +11,36 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do
       "name" => event.title,
       "category" => render_one(event.category, ObjectView, "category.json", as: :category),
       "content" => event.description,
-      "mediaType" => "text/markdown",
+      "mediaType" => "text/html",
       "published" => Timex.format!(event.inserted_at, "{ISO:Extended}"),
       "updated" => Timex.format!(event.updated_at, "{ISO:Extended}")
     }
 
-    Map.merge(event, @base)
+    Map.merge(event, Utils.make_json_ld_header())
   end
 
-  def render("note.json", %{note: note}) do
-    event = %{
+  def render("comment.json", %{comment: comment}) do
+    comment = %{
+      "actor" => comment.actor.url,
+      "uuid" => comment.uuid,
+      # The activity should have attributedTo, not the comment itself
+      #      "attributedTo" => comment.attributed_to,
       "type" => "Note",
-      "id" => note.url,
-      "content" => note.text,
-      "mediaType" => "text/markdown",
-      "published" => Timex.format!(note.inserted_at, "{ISO:Extended}"),
-      "updated" => Timex.format!(note.updated_at, "{ISO:Extended}")
+      "id" => comment.url,
+      "content" => comment.text,
+      "mediaType" => "text/html",
+      "published" => Timex.format!(comment.inserted_at, "{ISO:Extended}"),
+      "updated" => Timex.format!(comment.updated_at, "{ISO:Extended}")
     }
 
-    Map.merge(event, @base)
+    Map.merge(comment, Utils.make_json_ld_header())
   end
 
   def render("category.json", %{category: category}) do
-    %{"title" => category.title}
+    %{
+      "identifier" => category.id,
+      "name" => category.title
+    }
   end
 
   def render("category.json", %{category: nil}) do
diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex
index 803021e7c..db4466aa7 100644
--- a/lib/service/activity_pub/activity_pub.ex
+++ b/lib/service/activity_pub/activity_pub.ex
@@ -16,7 +16,7 @@ defmodule Mobilizon.Service.ActivityPub do
 
   alias Mobilizon.Service.Federator
 
-  import Logger
+  require Logger
   import Mobilizon.Service.ActivityPub.Utils
 
   def get_recipients(data) do
@@ -24,9 +24,21 @@ defmodule Mobilizon.Service.ActivityPub do
   end
 
   def insert(map, local \\ true) when is_map(map) do
+    Logger.debug("preparing an activity")
+    Logger.debug(inspect(map))
+
     with map <- lazy_put_activity_defaults(map),
-         :ok <- insert_full_object(map) do
-      map = Map.put(map, "id", Ecto.UUID.generate())
+         :ok <- insert_full_object(map, local) do
+      object_id =
+        cond do
+          is_map(map["object"]) ->
+            map["object"]["id"]
+
+          is_binary(map["object"]) ->
+            map["id"]
+        end
+
+      map = Map.put(map, "id", "#{object_id}/activity")
 
       activity = %Activity{
         data: map,
@@ -106,7 +118,8 @@ defmodule Mobilizon.Service.ActivityPub do
     end
   end
 
-  def create(%{to: to, actor: actor, context: context, object: object} = params) do
+  def create(%{to: to, actor: actor, object: object} = params) do
+    Logger.debug("creating an activity")
     additional = params[:additional] || %{}
     # only accept false as false value
     local = !(params[:local] == false)
@@ -114,7 +127,7 @@ defmodule Mobilizon.Service.ActivityPub do
 
     with create_data <-
            make_create_data(
-             %{to: to, actor: actor, published: published, context: context, object: object},
+             %{to: to, actor: actor, published: published, object: object},
              additional
            ),
          {:ok, activity} <- insert(create_data, local),
@@ -157,10 +170,14 @@ defmodule Mobilizon.Service.ActivityPub do
   end
 
   def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
-    with data <- make_follow_data(follower, followed, activity_id),
+    with {:ok, follow} <- Actor.follow(follower, followed, true),
+         data <- make_follow_data(follower, followed, follow.id),
          {:ok, activity} <- insert(data, local),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
+    else
+      {err, _} when err in [:already_following, :suspended] ->
+        {:error, err}
     end
   end
 
@@ -199,9 +216,9 @@ defmodule Mobilizon.Service.ActivityPub do
   def create_public_activities(%Actor{} = actor) do
   end
 
-  def make_actor_from_url(url) do
-    with {:ok, data} <- fetch_and_prepare_user_from_url(url) do
-      Actors.insert_or_update_actor(data)
+  def make_actor_from_url(url, preload \\ false) do
+    with {:ok, data} <- fetch_and_prepare_actor_from_url(url) do
+      Actors.insert_or_update_actor(data, preload)
     else
       # Request returned 410
       {:error, :actor_deleted} ->
@@ -243,12 +260,13 @@ defmodule Mobilizon.Service.ActivityPub do
       end
 
     remote_inboxes =
-      followers
+      (remote_actors(activity) ++ followers)
       |> Enum.map(fn follower -> follower.shared_inbox_url end)
       |> Enum.uniq()
 
     {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
     json = Jason.encode!(data)
+    Logger.debug("Remote inboxes are : #{inspect(remote_inboxes)}")
 
     Enum.each(remote_inboxes, fn inbox ->
       Federator.enqueue(:publish_single_ap, %{
@@ -273,6 +291,9 @@ defmodule Mobilizon.Service.ActivityPub do
     Logger.debug("signature")
     Logger.debug(inspect(signature))
 
+    Logger.debug("body json")
+    Logger.debug(inspect(json))
+
     {:ok, response} =
       HTTPoison.post(
         inbox,
@@ -284,8 +305,8 @@ defmodule Mobilizon.Service.ActivityPub do
     Logger.debug(inspect(response))
   end
 
-  def fetch_and_prepare_user_from_url(url) do
-    Logger.debug("Fetching and preparing user from url")
+  def fetch_and_prepare_actor_from_url(url) do
+    Logger.debug("Fetching and preparing actor from url")
 
     with {:ok, %HTTPoison.Response{status_code: 200, body: body}} <-
            HTTPoison.get(url, [Accept: "application/activity+json"], follow_redirect: true),
@@ -297,7 +318,7 @@ defmodule Mobilizon.Service.ActivityPub do
         {:error, :actor_deleted}
 
       e ->
-        Logger.error("Could not decode user at fetch #{url}, #{inspect(e)}")
+        Logger.error("Could not decode actor at fetch #{url}, #{inspect(e)}")
         e
     end
   end
diff --git a/lib/service/activity_pub/transmogrifier.ex b/lib/service/activity_pub/transmogrifier.ex
index 1153cf087..2c093d96e 100644
--- a/lib/service/activity_pub/transmogrifier.ex
+++ b/lib/service/activity_pub/transmogrifier.ex
@@ -18,7 +18,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
     object
     |> Map.put("actor", object["attributedTo"])
     |> fix_attachments
-    |> fix_context
     # |> fix_in_reply_to
     |> fix_tag
   end
@@ -31,10 +30,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
   #
   #        object
   #        |> Map.put("inReplyTo", replied_object.data["id"])
-  #        |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
-  #        |> Map.put("inReplyToStatusId", activity.id)
-  #        |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
-  #        |> Map.put("context", replied_object.data["context"] || object["conversation"])
   #
   #      e ->
   #        Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
@@ -44,11 +39,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
 
   def fix_in_reply_to(object), do: object
 
-  def fix_context(object) do
-    object
-    |> Map.put("context", object["conversation"])
-  end
-
   def fix_attachments(object) do
     attachments =
       (object["attachment"] || [])
@@ -87,7 +77,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
         to: data["to"],
         object: object,
         actor: actor,
-        context: object["conversation"],
         local: false,
         published: data["published"],
         additional:
@@ -104,12 +93,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
   def handle_incoming(
         %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
       ) do
-    with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed),
+    with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true),
          {:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower),
          {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
       ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true})
 
-      # Actors.follow(follower, followed)
       {:ok, activity}
     else
       e ->
@@ -225,11 +213,10 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
     object
     #    |> set_sensitive
     #    |> add_hashtags
-    #    |> add_mention_tags
+    |> add_mention_tags
     #    |> add_emoji_tags
     |> add_attributed_to
     #    |> prepare_attachments
-    |> set_conversation
     |> set_reply_to_uri
   end
 
@@ -239,6 +226,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
   """
 
   def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+    Logger.debug("Prepare outgoing for a note creation")
+
     object =
       object
       |> prepare_object
@@ -248,6 +237,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
       |> Map.put("object", object)
       |> Map.put("@context", "https://www.w3.org/ns/activitystreams")
 
+    Logger.debug("Finished prepare outgoing for a note creation")
+
     {:ok, data}
   end
 
@@ -304,22 +295,23 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
   #    end
   #  end
   #
-  #  def add_mention_tags(object) do
-  #    recipients = object["to"] ++ (object["cc"] || [])
-  #
-  #    mentions =
-  #      recipients
-  #      |> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end)
-  #      |> Enum.filter(& &1)
-  #      |> Enum.map(fn user ->
-  #        %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
-  #      end)
-  #
-  #    tags = object["tag"] || []
-  #
-  #    object
-  #    |> Map.put("tag", tags ++ mentions)
-  #  end
+  def add_mention_tags(object) do
+    recipients = object["to"] ++ (object["cc"] || [])
+
+    mentions =
+      recipients
+      |> Enum.map(fn url -> Actors.get_actor_by_url!(url) end)
+      |> Enum.filter(& &1)
+      |> Enum.map(fn actor ->
+        %{"type" => "Mention", "href" => actor.url, "name" => "@#{actor.preferred_username}"}
+      end)
+
+    tags = object["tag"] || []
+
+    object
+    |> Map.put("tag", tags ++ mentions)
+  end
+
   #
   #  # TODO: we should probably send mtime instead of unix epoch time for updated
   #  def add_emoji_tags(object) do
@@ -342,9 +334,6 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
   #    |> Map.put("tag", tags ++ out)
   #  end
   #
-  def set_conversation(object) do
-    Map.put(object, "conversation", object["context"])
-  end
 
   #
   #  def set_sensitive(object) do
@@ -370,84 +359,4 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
   #    object
   #    |> Map.put("attachment", attachments)
   #  end
-  #
-  #  defp user_upgrade_task(user) do
-  #    old_follower_address = User.ap_followers(user)
-  #
-  #    q =
-  #      from(
-  #        u in User,
-  #        where: ^old_follower_address in u.following,
-  #        update: [
-  #          set: [
-  #            following:
-  #              fragment(
-  #                "array_replace(?,?,?)",
-  #                u.following,
-  #                ^old_follower_address,
-  #                ^user.follower_address
-  #              )
-  #          ]
-  #        ]
-  #      )
-  #
-  #    Repo.update_all(q, [])
-  #
-  #    maybe_retire_websub(user.ap_id)
-  #
-  #    # Only do this for recent activties, don't go through the whole db.
-  #    # Only look at the last 1000 activities.
-  #    since = (Repo.aggregate(Activity, :max, :id) || 0) - 1_000
-  #
-  #    q =
-  #      from(
-  #        a in Activity,
-  #        where: ^old_follower_address in a.recipients,
-  #        where: a.id > ^since,
-  #        update: [
-  #          set: [
-  #            recipients:
-  #              fragment(
-  #                "array_replace(?,?,?)",
-  #                a.recipients,
-  #                ^old_follower_address,
-  #                ^user.follower_address
-  #              )
-  #          ]
-  #        ]
-  #      )
-  #
-  #    Repo.update_all(q, [])
-  #  end
-  #
-  #  def upgrade_user_from_ap_id(ap_id, async \\ true) do
-  #    with %User{local: false} = user <- User.get_by_ap_id(ap_id),
-  #         {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
-  #      data =
-  #        data
-  #        |> Map.put(:info, Map.merge(user.info, data[:info]))
-  #
-  #      already_ap = User.ap_enabled?(user)
-  #
-  #      {:ok, user} =
-  #        User.upgrade_changeset(user, data)
-  #        |> Repo.update()
-  #
-  #      if !already_ap do
-  #        # This could potentially take a long time, do it in the background
-  #        if async do
-  #          Task.start(fn ->
-  #            user_upgrade_task(user)
-  #          end)
-  #        else
-  #          user_upgrade_task(user)
-  #        end
-  #      end
-  #
-  #      {:ok, user}
-  #    else
-  #      e -> e
-  #    end
-  #  end
-  #
 end
diff --git a/lib/service/activity_pub/utils.ex b/lib/service/activity_pub/utils.ex
index fdbb4be54..74a645fd2 100644
--- a/lib/service/activity_pub/utils.ex
+++ b/lib/service/activity_pub/utils.ex
@@ -20,16 +20,19 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
   import Ecto.Query
   require Logger
 
+  def make_context(%Activity{data: %{"context" => context}}), do: context
+  def make_context(_), do: generate_context_id()
+
   def make_json_ld_header do
     %{
       "@context" => [
         "https://www.w3.org/ns/activitystreams",
+        "https://litepub.github.io/litepub/context.jsonld",
         %{
-          "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
-          "sensitive" => "as:sensitive",
+          "sc" => "http://schema.org#",
           "Hashtag" => "as:Hashtag",
-          "toot" => "http://joinmastodon.org/ns#",
-          "Emoji" => "toot:Emoji"
+          "category" => "sc:category",
+          "uuid" => "sc:identifier"
         }
       ]
     }
@@ -74,6 +77,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
   Enqueues an activity for federation if it's local
   """
   def maybe_federate(%Activity{local: true} = activity) do
+    Logger.debug("Maybe federate an activity")
+
     priority =
       case activity.data["type"] do
         "Delete" -> 10
@@ -87,6 +92,14 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
 
   def maybe_federate(_), do: :ok
 
+  def remote_actors(%{data: %{"to" => to} = data}) do
+    to = to ++ (data["cc"] || [])
+
+    to
+    |> Enum.map(fn url -> Actors.get_actor_by_url!(url) end)
+    |> Enum.filter(fn actor -> actor && !is_nil(actor.domain) end)
+  end
+
   @doc """
   Adds an id and a published data if they aren't there,
   also adds it to an included object
@@ -123,7 +136,12 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
   @doc """
   Inserts a full object if it is contained in an activity.
   """
-  def insert_full_object(%{"object" => %{"type" => type} = object_data})
+  def insert_full_object(object_data, local \\ false)
+
+  @doc """
+  Inserts a full object if it is contained in an activity.
+  """
+  def insert_full_object(%{"object" => %{"type" => type} = object_data}, _local)
       when is_map(object_data) and type == "Event" do
     with {:ok, _} <- Events.create_event(object_data) do
       :ok
@@ -133,7 +151,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
   @doc """
   Inserts a full object if it is contained in an activity.
   """
-  def insert_full_object(%{"object" => %{"type" => type} = object_data})
+  def insert_full_object(%{"object" => %{"type" => type} = object_data}, local)
       when is_map(object_data) and type == "Note" do
     with {:ok, %Actor{id: actor_id}} <- Actors.get_or_fetch_by_url(object_data["actor"]) do
       data = %{
@@ -142,8 +160,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
         "actor_id" => actor_id,
         "in_reply_to_comment_id" => nil,
         "event_id" => nil,
+        "uuid" => object_data["uuid"],
         # probably
-        "local" => false
+        "local" => local
       }
 
       # We fetch the parent object
@@ -193,7 +212,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
     end
   end
 
-  def insert_full_object(_), do: :ok
+  def insert_full_object(_, _), do: :ok
 
   #  def update_object_in_activities(%{data: %{"id" => id}} = object) do
   #    # TODO
@@ -236,30 +255,31 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
   def make_comment_data(
         actor,
         to,
-        context,
         content_html,
-        attachments,
-        inReplyTo,
-        tags,
+        # attachments,
+        inReplyTo \\ nil,
+        # tags,
         cw \\ nil,
         cc \\ []
       ) do
+    Logger.debug("Making comment data")
+    uuid = Ecto.UUID.generate()
+
     object = %{
       "type" => "Note",
       "to" => to,
-      "cc" => cc,
+      # "cc" => cc,
       "content" => content_html,
-      "summary" => cw,
-      "context" => context,
-      "attachment" => attachments,
+      # "summary" => cw,
+      # "attachment" => attachments,
       "actor" => actor,
-      "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
+      "id" => "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}"
+      # "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()
     }
 
     if inReplyTo do
       object
-      |> Map.put("inReplyTo", inReplyTo.data["object"]["id"])
-      |> Map.put("inReplyToStatusId", inReplyTo.id)
+      |> Map.put("inReplyTo", inReplyTo)
     else
       object
     end
@@ -311,6 +331,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
   Makes a follow activity data for the given follower and followed
   """
   def make_follow_data(%Actor{url: follower_id}, %Actor{url: followed_id}, activity_id) do
+    Logger.debug("Make follow data")
+
     data = %{
       "type" => "Follow",
       "actor" => follower_id,
@@ -319,7 +341,11 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
       "object" => followed_id
     }
 
-    if activity_id, do: Map.put(data, "id", activity_id), else: data
+    Logger.debug(inspect(data))
+
+    if activity_id,
+      do: Map.put(data, "id", "#{MobilizonWeb.Endpoint.url()}/follow/#{activity_id}/activity"),
+      else: data
   end
 
   #  def fetch_latest_follow(%Actor{url: follower_id}, %Actor{url: followed_id}) do
@@ -388,8 +414,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
       "to" => params.to |> Enum.uniq(),
       "actor" => params.actor.url,
       "object" => params.object,
-      "published" => published,
-      "context" => params.context
+      "published" => published
     }
     |> Map.merge(additional)
   end
diff --git a/lib/service/federator.ex b/lib/service/federator.ex
index 24d8abf07..27b676d5c 100644
--- a/lib/service/federator.ex
+++ b/lib/service/federator.ex
@@ -36,7 +36,7 @@ defmodule Mobilizon.Service.Federator do
     Logger.debug(inspect(activity))
     Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
 
-    with actor when not is_nil(actor) <- Actors.get_actor_by_url(activity.data["actor"]) do
+    with actor when not is_nil(actor) <- Actors.get_actor_by_url!(activity.data["actor"]) do
       Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
       ActivityPub.publish(actor, activity)
     end
diff --git a/lib/service/http_signatures/http_signatures.ex b/lib/service/http_signatures/http_signatures.ex
index e6e2f475e..e5e0d927b 100644
--- a/lib/service/http_signatures/http_signatures.ex
+++ b/lib/service/http_signatures/http_signatures.ex
@@ -90,17 +90,21 @@ defmodule Mobilizon.Service.HTTPSignatures do
   end
 
   def sign(%Actor{} = actor, headers) do
-    sigstring = build_signing_string(headers, Map.keys(headers))
-
-    signature = sigstring |> :public_key.sign(:sha256, actor.keys) |> Base.encode64()
-
-    [
-      keyId: actor.url <> "#main-key",
-      algorithm: "rsa-sha256",
-      headers: headers |> Map.keys() |> Enum.join(" "),
-      signature: signature
-    ]
-    |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
-    |> Enum.join(",")
+    with sigstring <- build_signing_string(headers, Map.keys(headers)),
+         {:ok, key} <- actor.keys |> prepare_public_key(),
+         signature <- sigstring |> :public_key.sign(:sha256, key) |> Base.encode64() do
+      [
+        keyId: actor.url <> "#main-key",
+        algorithm: "rsa-sha256",
+        headers: headers |> Map.keys() |> Enum.join(" "),
+        signature: signature
+      ]
+      |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
+      |> Enum.join(",")
+    else
+      err ->
+        Logger.error("Unable to sign headers")
+        Logger.error(inspect(err))
+    end
   end
 end
diff --git a/lib/service/web_finger/web_finger.ex b/lib/service/web_finger/web_finger.ex
index fbde30b09..65217cb0b 100644
--- a/lib/service/web_finger/web_finger.ex
+++ b/lib/service/web_finger/web_finger.ex
@@ -38,7 +38,7 @@ defmodule Mobilizon.Service.WebFinger do
       {:ok, represent_user(user, "JSON")}
     else
       _e ->
-        with user when not is_nil(user) <- Actors.get_actor_by_url(resource) do
+        with user when not is_nil(user) <- Actors.get_actor_by_url!(resource) do
           {:ok, represent_user(user, "JSON")}
         else
           _e ->