2020-01-22 22:40:40 +01:00
|
|
|
defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
2019-05-22 14:12:11 +02:00
|
|
|
@moduledoc """
|
2019-09-22 16:26:23 +02:00
|
|
|
Event 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-01-28 20:15:59 +01:00
|
|
|
alias Mobilizon.Addresses
|
2019-09-22 16:26:23 +02:00
|
|
|
alias Mobilizon.Addresses.Address
|
2019-05-22 14:12:11 +02:00
|
|
|
alias Mobilizon.Events.Event, as: EventModel
|
2020-11-26 11:41:13 +01:00
|
|
|
alias Mobilizon.Medias.Media
|
2020-01-22 02:14:42 +01:00
|
|
|
|
2020-01-22 22:40:40 +01:00
|
|
|
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
|
|
|
alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter
|
2020-11-26 11:41:13 +01:00
|
|
|
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
|
2020-07-09 17:24:28 +02:00
|
|
|
|
|
|
|
import Mobilizon.Federation.ActivityStream.Converter.Utils,
|
|
|
|
only: [
|
|
|
|
fetch_tags: 1,
|
|
|
|
fetch_mentions: 1,
|
|
|
|
build_tags: 1,
|
|
|
|
maybe_fetch_actor_and_attributed_to_id: 1
|
|
|
|
]
|
2019-05-22 14:12:11 +02:00
|
|
|
|
2019-07-30 10:35:29 +02:00
|
|
|
require Logger
|
|
|
|
|
2019-09-22 16:26:23 +02:00
|
|
|
@behaviour Converter
|
|
|
|
|
2019-09-22 18:29:13 +02:00
|
|
|
defimpl Convertible, for: EventModel do
|
2020-01-22 22:40:40 +01:00
|
|
|
alias Mobilizon.Federation.ActivityStream.Converter.Event, as: EventConverter
|
2019-09-22 18:29:13 +02:00
|
|
|
|
|
|
|
defdelegate model_to_as(event), to: EventConverter
|
|
|
|
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
|
2019-10-25 17:43:37 +02:00
|
|
|
@spec as_to_model_data(map) :: {:ok, map()} | {:error, any()}
|
2019-05-22 14:12:11 +02:00
|
|
|
def as_to_model_data(object) do
|
2020-07-09 17:24:28 +02:00
|
|
|
with {%Actor{id: actor_id, domain: actor_domain}, attributed_to} <-
|
|
|
|
maybe_fetch_actor_and_attributed_to_id(object),
|
2019-07-30 10:35:29 +02:00
|
|
|
{:address, address_id} <-
|
|
|
|
{:address, get_address(object["location"])},
|
2020-07-09 17:24:28 +02:00
|
|
|
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
|
|
|
|
{:mentions, mentions} <- {:mentions, fetch_mentions(object["tag"])},
|
2019-09-20 19:43:29 +02:00
|
|
|
{:visibility, visibility} <- {:visibility, get_visibility(object)},
|
2019-08-28 11:28:27 +02:00
|
|
|
{:options, options} <- {:options, get_options(object)} do
|
2020-02-07 18:41:36 +01:00
|
|
|
attachments =
|
|
|
|
object
|
|
|
|
|> Map.get("attachment", [])
|
|
|
|
|> Enum.filter(fn attachment -> Map.get(attachment, "type", "Document") == "Document" end)
|
|
|
|
|
2019-05-22 14:12:11 +02:00
|
|
|
picture_id =
|
2020-02-07 18:41:36 +01:00
|
|
|
with true <- length(attachments) > 0,
|
2020-11-26 11:41:13 +01:00
|
|
|
{:ok, %Media{id: picture_id}} <-
|
2020-02-07 18:41:36 +01:00
|
|
|
attachments
|
|
|
|
|> hd()
|
2020-11-26 11:41:13 +01:00
|
|
|
|> MediaConverter.find_or_create_media(actor_id) do
|
2019-05-22 14:12:11 +02:00
|
|
|
picture_id
|
|
|
|
else
|
2019-12-03 11:29:51 +01:00
|
|
|
_err ->
|
|
|
|
nil
|
2019-05-22 14:12:11 +02:00
|
|
|
end
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
%{
|
2019-10-25 17:43:37 +02:00
|
|
|
title: object["name"],
|
|
|
|
description: object["content"],
|
|
|
|
organizer_actor_id: actor_id,
|
2020-07-09 17:24:28 +02:00
|
|
|
attributed_to_id: if(is_nil(attributed_to), do: nil, else: attributed_to.id),
|
2019-10-25 17:43:37 +02:00
|
|
|
picture_id: picture_id,
|
|
|
|
begins_on: object["startTime"],
|
|
|
|
ends_on: object["endTime"],
|
|
|
|
category: object["category"],
|
|
|
|
visibility: visibility,
|
2019-12-03 11:29:51 +01:00
|
|
|
join_options: Map.get(object, "joinMode", "free"),
|
|
|
|
local: is_nil(actor_domain),
|
2019-10-25 17:43:37 +02:00
|
|
|
options: options,
|
2019-12-03 11:29:51 +01:00
|
|
|
status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(),
|
2020-02-07 18:41:36 +01:00
|
|
|
online_address: object |> Map.get("attachment", []) |> get_online_address(),
|
2019-10-25 17:43:37 +02:00
|
|
|
phone_address: object["phoneAddress"],
|
2019-12-03 11:29:51 +01:00
|
|
|
draft: false,
|
2019-10-25 17:43:37 +02:00
|
|
|
url: object["id"],
|
|
|
|
uuid: object["uuid"],
|
|
|
|
tags: tags,
|
2019-12-03 11:29:51 +01:00
|
|
|
mentions: mentions,
|
|
|
|
physical_address_id: address_id,
|
|
|
|
updated_at: object["updated"],
|
|
|
|
publish_at: object["published"]
|
2019-08-28 11:28:27 +02:00
|
|
|
}
|
2020-06-11 19:13:21 +02:00
|
|
|
else
|
|
|
|
{:ok, %Actor{suspended: true}} ->
|
|
|
|
:error
|
2019-07-30 10:35:29 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-09-22 18:29:13 +02:00
|
|
|
@doc """
|
|
|
|
Convert an event struct to an ActivityStream representation.
|
|
|
|
"""
|
|
|
|
@impl Converter
|
|
|
|
@spec model_to_as(EventModel.t()) :: map
|
|
|
|
def model_to_as(%EventModel{} = event) do
|
|
|
|
to =
|
|
|
|
if event.visibility == :public,
|
|
|
|
do: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
|
|
else: [event.organizer_actor.followers_url]
|
|
|
|
|
2020-09-02 08:59:59 +02:00
|
|
|
%{
|
2019-09-22 18:29:13 +02:00
|
|
|
"type" => "Event",
|
|
|
|
"to" => to,
|
|
|
|
"cc" => [],
|
2020-07-09 17:24:28 +02:00
|
|
|
"attributedTo" =>
|
2020-09-02 08:59:59 +02:00
|
|
|
if(is_nil(event.attributed_to) or not Ecto.assoc_loaded?(event.attributed_to),
|
|
|
|
do: nil,
|
|
|
|
else: event.attributed_to.url
|
|
|
|
) ||
|
2020-07-09 17:24:28 +02:00
|
|
|
event.organizer_actor.url,
|
2019-09-22 18:29:13 +02:00
|
|
|
"name" => event.title,
|
2020-09-02 08:59:59 +02:00
|
|
|
"actor" =>
|
|
|
|
if(Ecto.assoc_loaded?(event.organizer_actor), do: event.organizer_actor.url, else: nil),
|
2019-09-22 18:29:13 +02:00
|
|
|
"uuid" => event.uuid,
|
|
|
|
"category" => event.category,
|
|
|
|
"content" => event.description,
|
2019-12-03 11:29:51 +01:00
|
|
|
"published" => (event.publish_at || event.inserted_at) |> date_to_string(),
|
|
|
|
"updated" => event.updated_at |> date_to_string(),
|
2019-09-22 18:29:13 +02:00
|
|
|
"mediaType" => "text/html",
|
|
|
|
"startTime" => event.begins_on |> date_to_string(),
|
2019-12-03 11:29:51 +01:00
|
|
|
"joinMode" => to_string(event.join_options),
|
2019-09-22 18:29:13 +02:00
|
|
|
"endTime" => event.ends_on |> date_to_string(),
|
2020-07-09 17:24:28 +02:00
|
|
|
"tag" => event.tags |> build_tags(),
|
2019-12-03 11:29:51 +01:00
|
|
|
"maximumAttendeeCapacity" => event.options.maximum_attendee_capacity,
|
|
|
|
"repliesModerationOption" => event.options.comment_moderation,
|
2019-12-16 11:46:19 +01:00
|
|
|
"commentsEnabled" => event.options.comment_moderation == :allow_all,
|
2019-12-20 13:04:34 +01:00
|
|
|
"anonymousParticipationEnabled" => event.options.anonymous_participation,
|
2020-02-07 18:41:36 +01:00
|
|
|
"attachment" => [],
|
2019-12-03 11:29:51 +01:00
|
|
|
# "draft" => event.draft,
|
|
|
|
"ical:status" => event.status |> to_string |> String.upcase(),
|
2019-09-22 18:29:13 +02:00
|
|
|
"id" => event.url,
|
|
|
|
"url" => event.url
|
|
|
|
}
|
2020-09-02 08:59:59 +02:00
|
|
|
|> maybe_add_physical_address(event)
|
|
|
|
|> maybe_add_event_picture(event)
|
|
|
|
|> maybe_add_online_address(event)
|
2019-09-22 18:29:13 +02:00
|
|
|
end
|
|
|
|
|
2019-08-28 11:28:27 +02:00
|
|
|
# Get only elements that we have in EventOptions
|
2019-09-22 18:29:13 +02:00
|
|
|
@spec get_options(map) :: map
|
2019-08-28 11:28:27 +02:00
|
|
|
defp get_options(object) do
|
2019-12-03 11:29:51 +01:00
|
|
|
%{
|
|
|
|
maximum_attendee_capacity: object["maximumAttendeeCapacity"],
|
2019-12-20 13:04:34 +01:00
|
|
|
anonymous_participation: object["anonymousParticipationEnabled"],
|
2019-12-16 11:46:19 +01:00
|
|
|
comment_moderation:
|
|
|
|
Map.get(
|
|
|
|
object,
|
|
|
|
"repliesModerationOption",
|
|
|
|
if(Map.get(object, "commentsEnabled", true), do: :allow_all, else: :closed)
|
|
|
|
)
|
2019-12-03 11:29:51 +01:00
|
|
|
}
|
2019-08-28 11:28:27 +02:00
|
|
|
end
|
|
|
|
|
2019-09-22 18:29:13 +02:00
|
|
|
@spec get_address(map | binary | nil) :: integer | nil
|
2019-08-22 15:57:44 +02:00
|
|
|
defp get_address(address_url) when is_bitstring(address_url) do
|
|
|
|
get_address(%{"id" => address_url})
|
|
|
|
end
|
|
|
|
|
2019-07-30 10:35:29 +02:00
|
|
|
defp get_address(%{"id" => url} = map) when is_map(map) and is_binary(url) do
|
|
|
|
Logger.debug("Address with an URL, let's check against our own database")
|
|
|
|
|
|
|
|
case Addresses.get_address_by_url(url) do
|
|
|
|
%Address{id: address_id} ->
|
|
|
|
address_id
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
Logger.debug("not in our database, let's try to create it")
|
|
|
|
map = Map.put(map, "url", map["id"])
|
|
|
|
do_get_address(map)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp get_address(map) when is_map(map) do
|
|
|
|
do_get_address(map)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp get_address(nil), do: nil
|
|
|
|
|
2019-09-22 18:29:13 +02:00
|
|
|
@spec do_get_address(map) :: integer | nil
|
2019-07-30 10:35:29 +02:00
|
|
|
defp do_get_address(map) do
|
2020-01-22 02:14:42 +01:00
|
|
|
map = AddressConverter.as_to_model_data(map)
|
2019-07-30 10:35:29 +02:00
|
|
|
|
|
|
|
case Addresses.create_address(map) do
|
|
|
|
{:ok, %Address{id: address_id}} ->
|
|
|
|
address_id
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
nil
|
2019-05-22 14:12:11 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-09-20 19:43:29 +02:00
|
|
|
@ap_public "https://www.w3.org/ns/activitystreams#Public"
|
|
|
|
|
2019-12-03 11:29:51 +01:00
|
|
|
defp get_visibility(object), do: if(@ap_public in object["to"], do: :public, else: :unlisted)
|
2019-09-20 19:43:29 +02:00
|
|
|
|
2019-09-22 18:29:13 +02:00
|
|
|
@spec date_to_string(DateTime.t() | nil) :: String.t()
|
2019-07-30 16:40:59 +02:00
|
|
|
defp date_to_string(nil), do: nil
|
2019-09-22 18:29:13 +02:00
|
|
|
defp date_to_string(%DateTime{} = date), do: DateTime.to_iso8601(date)
|
2020-02-07 18:41:36 +01:00
|
|
|
|
|
|
|
defp get_online_address(attachments) do
|
2020-06-09 11:13:22 +02:00
|
|
|
Enum.find_value(attachments, fn attachment ->
|
2020-02-07 18:41:36 +01:00
|
|
|
case attachment do
|
|
|
|
%{
|
|
|
|
"type" => "Link",
|
|
|
|
"href" => url,
|
|
|
|
"mediaType" => "text/html",
|
|
|
|
"name" => "Website"
|
|
|
|
} ->
|
|
|
|
url
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
2020-09-02 08:59:59 +02:00
|
|
|
|
|
|
|
@spec maybe_add_physical_address(map(), Event.t()) :: map()
|
|
|
|
defp maybe_add_physical_address(res, event) do
|
|
|
|
if is_nil(event.physical_address),
|
|
|
|
do: res,
|
|
|
|
else: Map.put(res, "location", AddressConverter.model_to_as(event.physical_address))
|
|
|
|
end
|
|
|
|
|
|
|
|
@spec maybe_add_event_picture(map(), Event.t()) :: map()
|
|
|
|
defp maybe_add_event_picture(res, event) do
|
|
|
|
if is_nil(event.picture),
|
|
|
|
do: res,
|
|
|
|
else:
|
|
|
|
Map.update(
|
|
|
|
res,
|
|
|
|
"attachment",
|
|
|
|
[],
|
2020-11-26 11:41:13 +01:00
|
|
|
&(&1 ++ [MediaConverter.model_to_as(event.picture)])
|
2020-09-02 08:59:59 +02:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
@spec maybe_add_online_address(map(), Event.t()) :: map()
|
|
|
|
defp maybe_add_online_address(res, event) do
|
|
|
|
if is_nil(event.online_address),
|
|
|
|
do: res,
|
|
|
|
else:
|
|
|
|
Map.update(
|
|
|
|
res,
|
|
|
|
"attachment",
|
|
|
|
[],
|
|
|
|
&(&1 ++
|
|
|
|
[
|
|
|
|
%{
|
|
|
|
"type" => "Link",
|
|
|
|
"href" => event.online_address,
|
|
|
|
"mediaType" => "text/html",
|
|
|
|
"name" => "Website"
|
|
|
|
}
|
|
|
|
])
|
|
|
|
)
|
|
|
|
end
|
2019-05-22 14:12:11 +02:00
|
|
|
end
|