2020-01-22 22:40:40 +01:00
|
|
|
defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
2019-05-22 14:12:11 +02:00
|
|
|
@moduledoc """
|
2019-09-22 16:26:23 +02:00
|
|
|
Comment converter.
|
2019-05-22 14:12:11 +02:00
|
|
|
|
2019-09-22 16:26:23 +02:00
|
|
|
This module allows to convert events from ActivityStream format to our own
|
|
|
|
internal one, and back.
|
2019-05-22 14:12:11 +02:00
|
|
|
"""
|
2019-09-22 16:26:23 +02:00
|
|
|
|
2019-05-22 14:12:11 +02:00
|
|
|
alias Mobilizon.Actors.Actor
|
2020-07-09 17:24:28 +02:00
|
|
|
alias Mobilizon.Discussions
|
|
|
|
alias Mobilizon.Discussions.Comment, as: CommentModel
|
|
|
|
alias Mobilizon.Discussions.Discussion
|
2019-05-22 14:12:11 +02:00
|
|
|
alias Mobilizon.Events.Event
|
2020-01-22 02:14:42 +01:00
|
|
|
alias Mobilizon.Federation.ActivityPub
|
|
|
|
alias Mobilizon.Federation.ActivityPub.Visibility
|
2020-01-22 22:40:40 +01:00
|
|
|
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
2020-07-09 17:24:28 +02:00
|
|
|
alias Mobilizon.Federation.ActivityStream.Converter.Comment, as: CommentConverter
|
|
|
|
alias Mobilizon.Tombstone, as: TombstoneModel
|
|
|
|
|
|
|
|
import Mobilizon.Federation.ActivityStream.Converter.Utils,
|
|
|
|
only: [
|
|
|
|
fetch_tags: 1,
|
|
|
|
fetch_mentions: 1,
|
|
|
|
build_tags: 1,
|
|
|
|
build_mentions: 1,
|
|
|
|
maybe_fetch_actor_and_attributed_to_id: 1
|
|
|
|
]
|
2020-01-22 02:14:42 +01:00
|
|
|
|
2019-05-22 14:12:11 +02:00
|
|
|
require Logger
|
|
|
|
|
|
|
|
@behaviour Converter
|
|
|
|
|
2019-09-22 18:29:13 +02:00
|
|
|
defimpl Convertible, for: CommentModel do
|
|
|
|
defdelegate model_to_as(comment), to: CommentConverter
|
|
|
|
end
|
|
|
|
|
2019-05-22 14:12:11 +02:00
|
|
|
@doc """
|
2019-09-22 18:29:13 +02:00
|
|
|
Converts an AP object data to our internal data structure.
|
2019-05-22 14:12:11 +02:00
|
|
|
"""
|
|
|
|
@impl Converter
|
2021-09-24 16:46:42 +02:00
|
|
|
@spec as_to_model_data(map) :: map | {:error, atom}
|
2019-05-22 14:12:11 +02:00
|
|
|
def as_to_model_data(object) do
|
2019-10-25 17:43:37 +02:00
|
|
|
Logger.debug("We're converting raw ActivityStream data to a comment entity")
|
2019-05-22 14:12:11 +02:00
|
|
|
Logger.debug(inspect(object))
|
|
|
|
|
2021-09-24 16:46:42 +02:00
|
|
|
tag_object = Map.get(object, "tag", [])
|
|
|
|
|
|
|
|
case maybe_fetch_actor_and_attributed_to_id(object) do
|
|
|
|
{:ok, %Actor{id: actor_id, domain: actor_domain}, attributed_to} ->
|
|
|
|
data = %{
|
|
|
|
text: object["content"],
|
|
|
|
url: object["id"],
|
|
|
|
# Will be used in conversations, ignored in basic comments
|
|
|
|
title: object["name"],
|
|
|
|
context: object["context"],
|
|
|
|
actor_id: actor_id,
|
|
|
|
attributed_to_id: if(is_nil(attributed_to), do: nil, else: attributed_to.id),
|
|
|
|
in_reply_to_comment_id: nil,
|
|
|
|
event_id: nil,
|
|
|
|
uuid: object["uuid"],
|
|
|
|
discussion_id: get_discussion_id(object),
|
|
|
|
tags: fetch_tags(tag_object),
|
|
|
|
mentions: fetch_mentions(tag_object),
|
|
|
|
local: is_nil(actor_domain),
|
|
|
|
visibility: if(Visibility.is_public?(object), do: :public, else: :private),
|
|
|
|
published_at: object["published"],
|
|
|
|
is_announcement: Map.get(object, "isAnnouncement", false)
|
|
|
|
}
|
|
|
|
|
2023-10-17 16:41:31 +02:00
|
|
|
maybe_fetch_parent_object(object, data)
|
2021-09-24 16:46:42 +02:00
|
|
|
|
|
|
|
{:error, err} ->
|
|
|
|
{:error, err}
|
2019-10-25 17:43:37 +02:00
|
|
|
end
|
2019-05-22 14:12:11 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
|
|
|
Make an AS comment object from an existing `Comment` structure.
|
2020-11-06 15:43:38 +01:00
|
|
|
|
|
|
|
A "soft-deleted" comment is a tombstone
|
2019-05-22 14:12:11 +02:00
|
|
|
"""
|
|
|
|
@impl Converter
|
2019-09-22 18:29:13 +02:00
|
|
|
@spec model_to_as(CommentModel.t()) :: map
|
2021-09-24 16:46:42 +02:00
|
|
|
def model_to_as(
|
|
|
|
%CommentModel{
|
|
|
|
deleted_at: nil,
|
|
|
|
attributed_to: attributed_to,
|
|
|
|
actor: %Actor{url: comment_actor_url}
|
|
|
|
} = comment
|
|
|
|
) do
|
2020-07-09 17:24:28 +02:00
|
|
|
to = determine_to(comment)
|
2019-10-25 17:43:37 +02:00
|
|
|
|
2021-09-24 16:46:42 +02:00
|
|
|
attributed_to =
|
|
|
|
if is_nil(attributed_to),
|
|
|
|
do: comment_actor_url,
|
|
|
|
else: Map.get(attributed_to, :url, comment_actor_url)
|
|
|
|
|
2019-05-22 14:12:11 +02:00
|
|
|
object = %{
|
|
|
|
"type" => "Note",
|
2019-10-25 17:43:37 +02:00
|
|
|
"to" => to,
|
|
|
|
"cc" => [],
|
2019-05-22 14:12:11 +02:00
|
|
|
"content" => comment.text,
|
2019-12-03 11:29:51 +01:00
|
|
|
"mediaType" => "text/html",
|
2019-05-22 14:12:11 +02:00
|
|
|
"actor" => comment.actor.url,
|
2021-09-24 16:46:42 +02:00
|
|
|
"attributedTo" => attributed_to,
|
2019-05-22 14:12:11 +02:00
|
|
|
"uuid" => comment.uuid,
|
2019-10-25 17:43:37 +02:00
|
|
|
"id" => comment.url,
|
2020-08-14 11:32:23 +02:00
|
|
|
"tag" => build_mentions(comment.mentions) ++ build_tags(comment.tags),
|
2021-06-01 18:08:03 +02:00
|
|
|
"published" => comment.published_at |> DateTime.to_iso8601(),
|
|
|
|
"isAnnouncement" => comment.is_announcement
|
2019-05-22 14:12:11 +02:00
|
|
|
}
|
|
|
|
|
2020-07-09 17:24:28 +02:00
|
|
|
object =
|
|
|
|
if comment.discussion_id,
|
|
|
|
do: Map.put(object, "context", comment.discussion.url),
|
|
|
|
else: object
|
|
|
|
|
2019-12-03 11:29:51 +01:00
|
|
|
cond do
|
|
|
|
comment.in_reply_to_comment ->
|
|
|
|
Map.put(object, "inReplyTo", comment.in_reply_to_comment.url)
|
|
|
|
|
|
|
|
comment.event ->
|
|
|
|
Map.put(object, "inReplyTo", comment.event.url)
|
|
|
|
|
|
|
|
true ->
|
|
|
|
object
|
2019-05-22 14:12:11 +02:00
|
|
|
end
|
|
|
|
end
|
2019-11-15 18:36:47 +01:00
|
|
|
|
2020-07-09 17:24:28 +02:00
|
|
|
@impl Converter
|
2019-11-15 18:36:47 +01:00
|
|
|
def model_to_as(%CommentModel{} = comment) do
|
2019-12-03 11:29:51 +01:00
|
|
|
Convertible.model_to_as(%TombstoneModel{
|
|
|
|
uri: comment.url,
|
2020-08-27 11:53:24 +02:00
|
|
|
actor: comment.actor,
|
2019-12-03 11:29:51 +01:00
|
|
|
inserted_at: comment.deleted_at
|
|
|
|
})
|
2019-11-15 18:36:47 +01:00
|
|
|
end
|
2020-07-09 17:24:28 +02:00
|
|
|
|
|
|
|
@spec determine_to(CommentModel.t()) :: [String.t()]
|
2023-10-17 16:41:31 +02:00
|
|
|
defp determine_to(%CommentModel{visibility: :private, mentions: mentions} = _comment) do
|
|
|
|
Enum.map(mentions, fn mention -> mention.actor.url end)
|
|
|
|
end
|
2020-07-09 17:24:28 +02:00
|
|
|
|
2023-10-17 16:41:31 +02:00
|
|
|
defp determine_to(%CommentModel{visibility: :public} = comment) do
|
|
|
|
if is_nil(comment.attributed_to) do
|
|
|
|
["https://www.w3.org/ns/activitystreams#Public"]
|
|
|
|
else
|
|
|
|
[comment.attributed_to.url]
|
2020-07-09 17:24:28 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-10-17 16:41:31 +02:00
|
|
|
defp determine_to(%CommentModel{} = comment) do
|
|
|
|
[comment.actor.followers_url]
|
|
|
|
end
|
|
|
|
|
2020-07-09 17:24:28 +02:00
|
|
|
defp maybe_fetch_parent_object(object, data) do
|
|
|
|
# We fetch the parent object
|
|
|
|
Logger.debug("We're fetching the parent object")
|
|
|
|
|
|
|
|
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
|
|
|
|
object["inReplyTo"] != "" do
|
|
|
|
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
|
|
|
|
|
|
|
|
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
|
|
|
|
# Reply to an event (Event)
|
2023-10-17 16:41:31 +02:00
|
|
|
{:ok, %Event{id: id} = event} ->
|
2020-07-09 17:24:28 +02:00
|
|
|
Logger.debug("Parent object is an event")
|
2023-10-17 16:41:31 +02:00
|
|
|
|
|
|
|
data
|
|
|
|
|> Map.put(:event_id, id)
|
|
|
|
|> Map.put(:event, event)
|
2020-07-09 17:24:28 +02:00
|
|
|
|
|
|
|
# Reply to a comment (Comment)
|
|
|
|
{:ok, %CommentModel{id: id} = comment} ->
|
|
|
|
Logger.debug("Parent object is another comment")
|
|
|
|
|
|
|
|
data
|
|
|
|
|> Map.put(:in_reply_to_comment_id, id)
|
|
|
|
|> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id())
|
|
|
|
|> Map.put(:event_id, comment.event_id)
|
2023-10-17 16:41:31 +02:00
|
|
|
|> Map.put(:conversation_id, comment.conversation_id)
|
2020-07-09 17:24:28 +02:00
|
|
|
|
|
|
|
# Reply to a discucssion (Discussion)
|
|
|
|
{:ok,
|
|
|
|
%Discussion{
|
|
|
|
id: discussion_id,
|
|
|
|
last_comment: %CommentModel{id: last_comment_id, origin_comment_id: origin_comment_id}
|
|
|
|
} = _discussion} ->
|
|
|
|
Logger.debug("Parent object is a discussion")
|
|
|
|
|
|
|
|
data
|
|
|
|
|> Map.put(:in_reply_to_comment_id, last_comment_id)
|
|
|
|
|> Map.put(:origin_comment_id, origin_comment_id)
|
|
|
|
|> Map.put(:discussion_id, discussion_id)
|
|
|
|
|
2021-11-19 17:40:42 +01:00
|
|
|
# Reply to a deleted entity
|
|
|
|
{:ok, %Mobilizon.Tombstone{}} ->
|
|
|
|
data
|
|
|
|
|
2020-07-09 17:24:28 +02:00
|
|
|
# Anything else is kind of a MP
|
|
|
|
{:error, parent} ->
|
2023-08-02 09:59:09 +02:00
|
|
|
Logger.warning("Parent object is something we don't handle")
|
2020-07-09 17:24:28 +02:00
|
|
|
Logger.debug(inspect(parent))
|
|
|
|
data
|
|
|
|
end
|
|
|
|
else
|
|
|
|
Logger.debug("No parent object for this comment")
|
|
|
|
|
|
|
|
data
|
|
|
|
end
|
|
|
|
end
|
2021-09-24 16:46:42 +02:00
|
|
|
|
|
|
|
defp get_discussion_id(%{"context" => context}) do
|
|
|
|
case Discussions.get_discussion_by_url(context) do
|
|
|
|
%Discussion{id: discussion_id} -> discussion_id
|
|
|
|
nil -> nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp get_discussion_id(_object), do: nil
|
2019-05-22 14:12:11 +02:00
|
|
|
end
|