Refactor Core things, including Ecto handling, ActivityPub & Transmogrifier modules

* Data doesn't need anymore to be converted to ActivityStream format to
be saved (this was taken from Pleroma and not at all a good idea here)
* Everything saved when creating an event is inserted into PostgreSQL in
a single transaction
This commit is contained in:
Thomas Citharel
2019-10-25 17:43:37 +02:00
parent 814cfbc8eb
commit cc820d6b63
69 changed files with 1881 additions and 1424 deletions

View File

@@ -8,7 +8,8 @@ defmodule Mobilizon.Service.ActivityPub do
# ActivityPub context.
"""
import Mobilizon.Service.ActivityPub.{Utils, Visibility}
import Mobilizon.Service.ActivityPub.Utils
import Mobilizon.Service.ActivityPub.Visibility
alias Mobilizon.{Actors, Config, Events}
alias Mobilizon.Actors.{Actor, Follower}
@@ -16,17 +17,12 @@ defmodule Mobilizon.Service.ActivityPub do
alias Mobilizon.Service.ActivityPub.{Activity, Converter, Convertible, Relay, Transmogrifier}
alias Mobilizon.Service.{Federator, WebFinger}
alias Mobilizon.Service.HTTPSignatures.Signature
alias MobilizonWeb.API.Utils, as: APIUtils
alias Mobilizon.Service.ActivityPub.Audience
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
require Logger
@doc """
Get recipients for an activity or object
"""
@spec get_recipients(map()) :: list()
def get_recipients(data) do
(data["to"] || []) ++ (data["cc"] || [])
end
@doc """
Wraps an object into an activity
"""
@@ -106,8 +102,8 @@ defmodule Mobilizon.Service.ActivityPub do
@doc """
Getting an actor from url, eventually creating it
"""
@spec get_or_fetch_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
def get_or_fetch_by_url(url, preload \\ false) do
@spec get_or_fetch_actor_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
def get_or_fetch_actor_by_url(url, preload \\ false) do
case Actors.get_actor_by_url(url, preload) do
{:ok, %Actor{} = actor} ->
{:ok, actor}
@@ -126,27 +122,29 @@ defmodule Mobilizon.Service.ActivityPub do
end
@doc """
Create an activity of type "Create"
"""
def create(%{to: to, actor: actor, object: object} = params) do
Logger.debug("creating an activity")
Logger.debug(inspect(params))
Logger.debug(inspect(object))
additional = params[:additional] || %{}
# only accept false as false value
local = !(params[:local] == false)
published = params[:published]
Create an activity of type `Create`
with create_data <-
make_create_data(
%{to: to, actor: actor, published: published, object: object},
additional
),
{:ok, activity} <- create_activity(create_data, local),
{:ok, object} <- insert_full_object(create_data),
* Creates the object, which returns AS data
* Wraps ActivityStreams data into a `Create` activity
* Creates an `Mobilizon.Service.ActivityPub.Activity` from this
* Federates (asynchronously) the activity
* Returns the activity
"""
@spec create(atom(), map(), boolean, map()) :: {:ok, Activity.t(), struct()} | any()
def create(type, args, local \\ false, additional \\ %{}) do
Logger.debug("creating an activity")
Logger.debug(inspect(args))
{:ok, entity, create_data} =
case type do
:event -> create_event(args, additional)
:comment -> create_comment(args, additional)
:group -> create_group(args, additional)
end
with {:ok, activity} <- create_activity(create_data, local),
:ok <- maybe_federate(activity) do
# {:ok, actor} <- Actors.increase_event_count(actor) do
{:ok, activity, object}
{:ok, activity, entity}
else
err ->
Logger.error("Something went wrong while creating an activity")
@@ -155,21 +153,52 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
def accept(%{to: to, actor: actor, object: object} = params, activity_wrapper_id \\ nil) do
# only accept false as false value
local = !(params[:local] == false)
@doc """
Create an activity of type `Update`
with data <- %{
"to" => to,
"type" => "Accept",
"actor" => actor,
"object" => object,
"id" => activity_wrapper_id || get_url(object) <> "/activity"
},
{:ok, activity} <- create_activity(data, local),
{:ok, object} <- insert_full_object(data),
* Updates the object, which returns AS data
* Wraps ActivityStreams data into a `Update` activity
* Creates an `Mobilizon.Service.ActivityPub.Activity` from this
* Federates (asynchronously) the activity
* Returns the activity
"""
@spec update(atom(), struct(), map(), boolean, map()) :: {:ok, Activity.t(), struct()} | any()
def update(type, old_entity, args, local \\ false, additional \\ %{}) do
Logger.debug("updating an activity")
Logger.debug(inspect(args))
{:ok, entity, update_data} =
case type do
:event -> update_event(old_entity, args, additional)
:actor -> update_actor(old_entity, args, additional)
end
with {:ok, activity} <- create_activity(update_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
{:ok, activity, entity}
else
err ->
Logger.error("Something went wrong while creating an activity")
Logger.debug(inspect(err))
err
end
end
def accept(type, entity, args, local \\ false, additional \\ %{}) do
{:ok, entity, update_data} =
case type do
:join -> accept_join(entity, args, additional)
:follow -> accept_follow(entity, args, additional)
end
with {:ok, activity} <- create_activity(update_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity, entity}
else
err ->
Logger.error("Something went wrong while creating an activity")
Logger.debug(inspect(err))
err
end
end
@@ -191,25 +220,6 @@ defmodule Mobilizon.Service.ActivityPub do
end
end
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
# only accept false as false value
local = !(params[:local] == false)
with data <- %{
"to" => to,
"cc" => cc,
"id" => object["url"],
"type" => "Update",
"actor" => actor,
"object" => object
},
{:ok, activity} <- create_activity(data, local),
{:ok, object} <- update_object(object["id"], data),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
end
end
# TODO: This is weird, maybe we shouldn't check here if we can make the activity.
# def like(
# %Actor{url: url} = actor,
@@ -290,15 +300,12 @@ defmodule Mobilizon.Service.ActivityPub do
Make an actor follow another
"""
def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
with {:ok, %Follower{url: follow_url}} <-
with {:ok, %Follower{} = follower} <-
Actors.follow(followed, follower, activity_id, false),
activity_follow_id <-
activity_id || follow_url,
data <- make_follow_data(followed, follower, activity_follow_id),
{:ok, activity} <- create_activity(data, local),
{:ok, object} <- insert_full_object(data),
follower_as_data <- Convertible.model_to_as(follower),
{:ok, activity} <- create_activity(follower_as_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
{:ok, activity, follower}
else
{:error, err, msg} when err in [:already_following, :suspended] ->
{:error, msg}
@@ -310,16 +317,11 @@ defmodule Mobilizon.Service.ActivityPub do
"""
@spec unfollow(Actor.t(), Actor.t(), String.t(), boolean()) :: {:ok, map()} | any()
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
with {:ok, %Follower{id: follow_id}} <- Actors.unfollow(followed, follower),
with {:ok, %Follower{id: follow_id} = follow} <- Actors.unfollow(followed, follower),
# We recreate the follow activity
data <-
make_follow_data(
followed,
follower,
"#{MobilizonWeb.Endpoint.url()}/follow/#{follow_id}/activity"
),
{:ok, follow_activity} <- create_activity(data, local),
{:ok, _object} <- insert_full_object(data),
follow_as_data <-
Convertible.model_to_as(%{follow | actor: follower, target_actor: followed}),
{:ok, follow_activity} <- create_activity(follow_as_data, local),
activity_unfollow_id <-
activity_id || "#{MobilizonWeb.Endpoint.url()}/unfollow/#{follow_id}/activity",
unfollow_data <-
@@ -346,7 +348,7 @@ defmodule Mobilizon.Service.ActivityPub do
"id" => url <> "/delete"
}
with {:ok, _} <- Events.delete_event(event),
with {:ok, %Event{} = event} <- Events.delete_event(event),
{:ok, activity} <- create_activity(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity, event}
@@ -362,11 +364,10 @@ defmodule Mobilizon.Service.ActivityPub do
"to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
}
with {:ok, _} <- Events.delete_comment(comment),
with {:ok, %Comment{} = comment} <- Events.delete_comment(comment),
{:ok, activity} <- create_activity(data, local),
{:ok, object} <- insert_full_object(data),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
{:ok, activity, comment}
end
end
@@ -379,11 +380,10 @@ defmodule Mobilizon.Service.ActivityPub do
"to" => [url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
}
with {:ok, _} <- Actors.delete_actor(actor),
with {:ok, %Actor{} = actor} <- Actors.delete_actor(actor),
{:ok, activity} <- create_activity(data, local),
{:ok, object} <- insert_full_object(data),
:ok <- maybe_federate(activity) do
{:ok, activity, object}
{:ok, activity, actor}
end
end
@@ -434,9 +434,9 @@ defmodule Mobilizon.Service.ActivityPub do
{:ok, _object} <- insert_full_object(join_data),
:ok <- maybe_federate(activity) do
if role === :participant do
accept(
%{to: [actor.url], actor: event.organizer_actor.url, object: join_data["id"]},
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{participant.id}"
accept_join(
participant,
%{}
)
end
@@ -720,9 +720,233 @@ defmodule Mobilizon.Service.ActivityPub do
}
end
# # Whether the Public audience is in the activity's audience
# defp is_public?(activity) do
# "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
# (activity.data["cc"] || []))
# end
# Get recipients for an activity or object
@spec get_recipients(map()) :: list()
defp get_recipients(data) do
(data["to"] || []) ++ (data["cc"] || [])
end
@spec create_event(map(), map()) :: {:ok, map()}
defp create_event(args, additional) do
with args <- prepare_args_for_event(args),
{:ok, %Event{} = event} <- Events.create_event(args),
event_as_data <- Convertible.model_to_as(event),
audience <-
Audience.calculate_to_and_cc_from_mentions(
event.organizer_actor,
args.mentions,
nil,
event.visibility
),
create_data <-
make_create_data(event_as_data, Map.merge(audience, additional)) do
{:ok, event, create_data}
end
end
@spec create_comment(map(), map()) :: {:ok, map()}
defp create_comment(args, additional) do
with args <- prepare_args_for_comment(args),
{:ok, %Comment{} = comment} <- Events.create_comment(args),
comment_as_data <- Convertible.model_to_as(comment),
audience <-
Audience.calculate_to_and_cc_from_mentions(
comment.actor,
args.mentions,
args.in_reply_to_comment,
comment.visibility
),
create_data <-
make_create_data(comment_as_data, Map.merge(audience, additional)) do
{:ok, comment, create_data}
end
end
@spec create_group(map(), map()) :: {:ok, map()}
defp create_group(args, additional) do
with args <- prepare_args_for_group(args),
{:ok, %Actor{type: :Group} = group} <- Actors.create_group(args),
group_as_data <- Convertible.model_to_as(group),
audience <-
Audience.calculate_to_and_cc_from_mentions(
args.creator_actor,
[],
nil,
:public
),
create_data <-
make_create_data(group_as_data, Map.merge(audience, additional)) do
{:ok, group, create_data}
end
end
@spec update_event(Event.t(), map(), map()) ::
{:ok, Event.t(), Activity.t()} | any()
defp update_event(
%Event{} = old_event,
args,
additional
) do
with args <- prepare_args_for_event(args),
{:ok, %Event{} = new_event} <- Events.update_event(old_event, args),
event_as_data <- Convertible.model_to_as(new_event),
audience <-
Audience.calculate_to_and_cc_from_mentions(
new_event.organizer_actor,
Map.get(args, :mentions, []),
nil,
new_event.visibility
),
update_data <- make_update_data(event_as_data, Map.merge(audience, additional)) do
{:ok, new_event, update_data}
else
err ->
Logger.error("Something went wrong while creating an update activity")
Logger.debug(inspect(err))
err
end
end
@spec update_actor(Actor.t(), map(), map()) ::
{:ok, Actor.t(), Activity.t()} | any()
defp update_actor(%Actor{} = old_actor, args, additional) do
with {:ok, %Actor{} = new_actor} <- Actors.update_actor(old_actor, args),
actor_as_data <- Convertible.model_to_as(new_actor),
audience <-
Audience.calculate_to_and_cc_from_mentions(
new_actor,
[],
nil,
:public
),
additional <- Map.merge(additional, %{"actor" => old_actor.url}),
update_data <- make_update_data(actor_as_data, Map.merge(audience, additional)) do
{:ok, new_actor, update_data}
end
end
@spec accept_follow(Follower.t(), map(), map()) ::
{:ok, Follower.t(), Activity.t()} | any()
defp accept_follow(
%Follower{} = follower,
args,
additional
) do
with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, args),
follower_as_data <- Convertible.model_to_as(follower),
audience <-
Audience.calculate_to_and_cc_from_mentions(follower.target_actor),
update_data <-
make_update_data(
follower_as_data,
Map.merge(Map.merge(audience, additional), %{
"id" => "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follower.id}"
})
) do
{:ok, follower, update_data}
else
err ->
Logger.error("Something went wrong while creating an update activity")
Logger.debug(inspect(err))
err
end
end
@spec accept_join(Participant.t(), map(), map()) ::
{:ok, Participant.t(), Activity.t()} | any()
defp accept_join(
%Participant{} = participant,
args,
additional \\ %{}
) do
with {:ok, %Participant{} = participant} <- Events.update_participant(participant, args),
participant_as_data <- Convertible.model_to_as(participant),
audience <-
Audience.calculate_to_and_cc_from_mentions(participant.actor),
update_data <-
make_accept_join_data(
participant_as_data,
Map.merge(Map.merge(audience, additional), %{
"id" => "#{MobilizonWeb.Endpoint.url()}/accept/join/#{participant.id}"
})
) do
{:ok, participant, update_data}
else
err ->
Logger.error("Something went wrong while creating an update activity")
Logger.debug(inspect(err))
err
end
end
# Prepare and sanitize arguments for events
defp prepare_args_for_event(args) do
# If title is not set: we are not updating it
args =
if Map.has_key?(args, :title) && !is_nil(args.title),
do: Map.update(args, :title, "", &String.trim(HtmlSanitizeEx.strip_tags(&1))),
else: args
# If we've been given a description (we might not get one if updating)
# sanitize it, HTML it, and extract tags & mentions from it
args =
if Map.has_key?(args, :description) && !is_nil(args.description) do
{description, mentions, tags} =
APIUtils.make_content_html(
String.trim(args.description),
Map.get(args, :tags, []),
"text/html"
)
mentions = ConverterUtils.fetch_mentions(Map.get(args, :mentions, []) ++ mentions)
Map.merge(args, %{
description: description,
mentions: mentions,
tags: tags
})
else
args
end
Map.update(args, :tags, [], &ConverterUtils.fetch_tags/1)
end
# Prepare and sanitize arguments for comments
defp prepare_args_for_comment(args) do
with in_reply_to_comment <-
args |> Map.get(:in_reply_to_comment_id) |> Events.get_comment(),
args <- Map.update(args, :visibility, :public, & &1),
{text, mentions, tags} <-
APIUtils.make_content_html(
args |> Map.get(:text, "") |> String.trim(),
# Can't put additional tags on a comment
[],
"text/html"
),
tags <- ConverterUtils.fetch_tags(tags),
mentions <- Map.get(args, :mentions, []) ++ ConverterUtils.fetch_mentions(mentions),
args <-
Map.merge(args, %{
actor_id: Map.get(args, :actor_id),
text: text,
mentions: mentions,
tags: tags,
in_reply_to_comment: in_reply_to_comment,
in_reply_to_comment_id:
if(is_nil(in_reply_to_comment), do: nil, else: Map.get(in_reply_to_comment, :id))
}) do
args
end
end
defp prepare_args_for_group(args) do
with preferred_username <-
args |> Map.get(:preferred_username) |> HtmlSanitizeEx.strip_tags() |> String.trim(),
summary <- args |> Map.get(:summary, "") |> String.trim(),
{summary, _mentions, _tags} <-
summary |> String.trim() |> APIUtils.make_content_html([], "text/html") do
%{args | preferred_username: preferred_username, summary: summary}
end
end
end

View File

@@ -0,0 +1,98 @@
defmodule Mobilizon.Service.ActivityPub.Audience do
@moduledoc """
Tools for calculating content audience
"""
alias Mobilizon.Actors.Actor
@ap_public "https://www.w3.org/ns/activitystreams#Public"
@doc """
Determines the full audience based on mentions for a public audience
Audience is:
* `to` : the mentioned actors, the eventual actor we're replying to and the public
* `cc` : the actor's followers
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :public) do
to = [@ap_public | mentions]
cc = [actor.followers_url]
if in_reply_to do
{Enum.uniq([in_reply_to.actor | to]), cc}
else
{to, cc}
end
end
@doc """
Determines the full audience based on mentions based on a unlisted audience
Audience is:
* `to` : the mentionned actors, actor's followers and the eventual actor we're replying to
* `cc` : public
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :unlisted) do
to = [actor.followers_url | mentions]
cc = [@ap_public]
if in_reply_to do
{Enum.uniq([in_reply_to.actor | to]), cc}
else
{to, cc}
end
end
@doc """
Determines the full audience based on mentions based on a private audience
Audience is:
* `to` : the mentioned actors, actor's followers and the eventual actor we're replying to
* `cc` : none
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, in_reply_to, :private) do
{to, cc} = get_to_and_cc(actor, mentions, in_reply_to, :direct)
{[actor.followers_url | to], cc}
end
@doc """
Determines the full audience based on mentions based on a direct audience
Audience is:
* `to` : the mentioned actors and the eventual actor we're replying to
* `cc` : none
"""
@spec get_to_and_cc(Actor.t(), list(), map(), String.t()) :: {list(), list()}
def get_to_and_cc(_actor, mentions, in_reply_to, :direct) do
if in_reply_to do
{Enum.uniq([in_reply_to.actor | mentions]), []}
else
{mentions, []}
end
end
def get_to_and_cc(_actor, mentions, _in_reply_to, {:list, _}) do
{mentions, []}
end
# def get_addressed_actors(_, to) when is_list(to) do
# Actors.get(to)
# end
def get_addressed_actors(mentioned_users, _), do: mentioned_users
def calculate_to_and_cc_from_mentions(
actor,
mentions \\ [],
in_reply_to \\ nil,
visibility \\ :public
) do
with mentioned_actors <- for({_, mentioned_actor} <- mentions, do: mentioned_actor.url),
addressed_actors <- get_addressed_actors(mentioned_actors, nil),
{to, cc} <- get_to_and_cc(actor, addressed_actors, in_reply_to, visibility) do
%{"to" => to, "cc" => cc}
end
end
end

View File

@@ -37,17 +37,18 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Actor do
"url" => object["image"]["url"]
}
%{
"type" => String.to_existing_atom(object["type"]),
"preferred_username" => object["preferredUsername"],
"summary" => object["summary"],
"url" => object["id"],
"name" => object["name"],
"avatar" => avatar,
"banner" => banner,
"keys" => object["publicKey"]["publicKeyPem"],
"manually_approves_followers" => object["manuallyApprovesFollowers"]
}
{:ok,
%{
"type" => String.to_existing_atom(object["type"]),
"preferred_username" => object["preferredUsername"],
"summary" => object["summary"],
"url" => object["id"],
"name" => object["name"],
"avatar" => avatar,
"banner" => banner,
"keys" => object["publicKey"]["publicKeyPem"],
"manually_approves_followers" => object["manuallyApprovesFollowers"]
}}
end
@doc """

View File

@@ -11,6 +11,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
alias Mobilizon.Events.Event
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Converter, Convertible}
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
require Logger
@@ -26,57 +27,67 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
Converts an AP object data to our internal data structure.
"""
@impl Converter
@spec as_to_model_data(map) :: map
@spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
def as_to_model_data(object) do
{:ok, %Actor{id: actor_id}} = ActivityPub.get_or_fetch_by_url(object["actor"])
Logger.debug("Inserting full comment")
Logger.debug("We're converting raw ActivityStream data to a comment entity")
Logger.debug(inspect(object))
data = %{
"text" => object["content"],
"url" => object["id"],
"actor_id" => actor_id,
"in_reply_to_comment_id" => nil,
"event_id" => nil,
"uuid" => object["uuid"]
}
with {:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(object["actor"]),
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
{:mentions, mentions} <- {:mentions, ConverterUtils.fetch_mentions(object["tag"])} do
Logger.debug("Inserting full comment")
Logger.debug(inspect(object))
# We fetch the parent object
Logger.debug("We're fetching the parent object")
data = %{
text: object["content"],
url: object["id"],
actor_id: actor_id,
in_reply_to_comment_id: nil,
event_id: nil,
uuid: object["uuid"],
tags: tags,
mentions: mentions
}
data =
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
object["inReplyTo"] != "" do
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
# We fetch the parent object
Logger.debug("We're fetching the parent object")
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
# Reply to an event (Event)
{:ok, %Event{id: id}} ->
Logger.debug("Parent object is an event")
data |> Map.put("event_id", id)
data =
if Map.has_key?(object, "inReplyTo") && object["inReplyTo"] != nil &&
object["inReplyTo"] != "" do
Logger.debug(fn -> "Object has inReplyTo #{object["inReplyTo"]}" end)
# Reply to a comment (Comment)
{:ok, %CommentModel{id: id} = comment} ->
Logger.debug("Parent object is another comment")
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
# Reply to an event (Event)
{:ok, %Event{id: id}} ->
Logger.debug("Parent object is an event")
data |> Map.put(:event_id, id)
data
|> Map.put("in_reply_to_comment_id", id)
|> Map.put("origin_comment_id", comment |> CommentModel.get_thread_id())
# Reply to a comment (Comment)
{:ok, %CommentModel{id: id} = comment} ->
Logger.debug("Parent object is another comment")
# Anything else is kind of a MP
{:error, parent} ->
Logger.debug("Parent object is something we don't handle")
Logger.debug(inspect(parent))
data
data
|> Map.put(:in_reply_to_comment_id, id)
|> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id())
# Anything else is kind of a MP
{:error, parent} ->
Logger.debug("Parent object is something we don't handle")
Logger.debug(inspect(parent))
data
end
else
Logger.debug("No parent object for this comment")
data
end
else
Logger.debug("No parent object for this comment")
data
end
data
{:ok, data}
else
err ->
{:error, err}
end
end
@doc """
@@ -85,14 +96,22 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Comment do
@impl Converter
@spec model_to_as(CommentModel.t()) :: map
def model_to_as(%CommentModel{} = comment) do
to =
if comment.visibility == :public,
do: ["https://www.w3.org/ns/activitystreams#Public"],
else: [comment.actor.followers_url]
object = %{
"type" => "Note",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"to" => to,
"cc" => [],
"content" => comment.text,
"actor" => comment.actor.url,
"attributedTo" => comment.actor.url,
"uuid" => comment.uuid,
"id" => comment.url
"id" => comment.url,
"tag" =>
ConverterUtils.build_mentions(comment.mentions) ++ ConverterUtils.build_tags(comment.tags)
}
if comment.in_reply_to_comment do

View File

@@ -6,15 +6,17 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
internal one, and back.
"""
alias Mobilizon.{Actors, Addresses, Events, Media}
alias Mobilizon.{Addresses, Media}
alias Mobilizon.Actors.Actor
alias Mobilizon.Addresses.Address
alias Mobilizon.Events.Event, as: EventModel
alias Mobilizon.Events.{EventOptions, Tag}
alias Mobilizon.Events.EventOptions
alias Mobilizon.Media.Picture
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils}
alias Mobilizon.Service.ActivityPub.Converter.Address, as: AddressConverter
alias Mobilizon.Service.ActivityPub.Converter.Picture, as: PictureConverter
alias Mobilizon.Service.ActivityPub.Converter.Utils, as: ConverterUtils
require Logger
@@ -30,16 +32,16 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
Converts an AP object data to our internal data structure.
"""
@impl Converter
@spec as_to_model_data(map) :: map
@spec as_to_model_data(map) :: {:ok, map()} | {:error, any()}
def as_to_model_data(object) do
Logger.debug("event as_to_model_data")
Logger.debug(inspect(object))
with {:actor, {:ok, %Actor{id: actor_id}}} <-
{:actor, Actors.get_actor_by_url(object["actor"])},
{:actor, ActivityPub.get_or_fetch_actor_by_url(object["actor"])},
{:address, address_id} <-
{:address, get_address(object["location"])},
{:tags, tags} <- {:tags, fetch_tags(object["tag"])},
{:tags, tags} <- {:tags, ConverterUtils.fetch_tags(object["tag"])},
{:visibility, visibility} <- {:visibility, get_visibility(object)},
{:options, options} <- {:options, get_options(object)} do
picture_id =
@@ -58,26 +60,27 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
end
entity = %{
"title" => object["name"],
"description" => object["content"],
"organizer_actor_id" => actor_id,
"picture_id" => picture_id,
"begins_on" => object["startTime"],
"ends_on" => object["endTime"],
"category" => object["category"],
"visibility" => visibility,
"join_options" => object["joinOptions"],
"status" => object["status"],
"online_address" => object["onlineAddress"],
"phone_address" => object["phoneAddress"],
"draft" => object["draft"] || false,
"url" => object["id"],
"uuid" => object["uuid"],
"tags" => tags,
"physical_address_id" => address_id
title: object["name"],
description: object["content"],
organizer_actor_id: actor_id,
picture_id: picture_id,
begins_on: object["startTime"],
ends_on: object["endTime"],
category: object["category"],
visibility: visibility,
join_options: Map.get(object, "joinOptions", "free"),
options: options,
status: object["status"],
online_address: object["onlineAddress"],
phone_address: object["phoneAddress"],
draft: object["draft"] || false,
url: object["id"],
uuid: object["uuid"],
tags: tags,
physical_address_id: address_id
}
{:ok, Map.put(entity, "options", options)}
{:ok, entity}
else
error ->
{:error, error}
@@ -111,7 +114,7 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
"startTime" => event.begins_on |> date_to_string(),
"joinOptions" => to_string(event.join_options),
"endTime" => event.ends_on |> date_to_string(),
"tag" => event.tags |> build_tags(),
"tag" => event.tags |> ConverterUtils.build_tags(),
"draft" => event.draft,
"id" => event.url,
"url" => event.url
@@ -181,32 +184,6 @@ defmodule Mobilizon.Service.ActivityPub.Converter.Event do
end
end
@spec fetch_tags([String.t()]) :: [String.t()]
defp fetch_tags(tags) do
Logger.debug("fetching tags")
Enum.reduce(tags, [], fn tag, acc ->
with true <- tag["type"] == "Hashtag",
{:ok, %Tag{} = tag} <- Events.get_or_create_tag(tag) do
acc ++ [tag]
else
_err ->
acc
end
end)
end
@spec build_tags([String.t()]) :: String.t()
defp build_tags(tags) do
Enum.map(tags, fn %Tag{} = tag ->
%{
"href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag.slug}",
"name" => "##{tag.title}",
"type" => "Hashtag"
}
end)
end
@ap_public "https://www.w3.org/ns/activitystreams#Public"
defp get_visibility(object) do

View File

@@ -0,0 +1,36 @@
defmodule Mobilizon.Service.ActivityPub.Converter.Follower do
@moduledoc """
Participant converter.
This module allows to convert followers from ActivityStream format to our own
internal one, and back.
"""
alias Mobilizon.Actors.Follower, as: FollowerModel
alias Mobilizon.Service.ActivityPub.Convertible
alias Mobilizon.Actors.Actor
defimpl Convertible, for: FollowerModel do
alias Mobilizon.Service.ActivityPub.Converter.Follower, as: FollowerConverter
defdelegate model_to_as(follower), to: FollowerConverter
end
@doc """
Convert an follow struct to an ActivityStream representation.
"""
@spec model_to_as(FollowerModel.t()) :: map
def model_to_as(
%FollowerModel{actor: %Actor{} = actor, target_actor: %Actor{} = target_actor} = follower
) do
%{
"type" => "Follow",
"actor" => actor.url,
"to" => [target_actor.url],
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => target_actor.url,
"id" => follower.url
}
end
end

View File

@@ -0,0 +1,100 @@
defmodule Mobilizon.Service.ActivityPub.Converter.Utils do
@moduledoc """
Various utils for converters
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Tag
alias Mobilizon.Mention
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Storage.Repo
require Logger
@spec fetch_tags([String.t()]) :: [Tag.t()]
def fetch_tags(tags) when is_list(tags) do
Logger.debug("fetching tags")
Enum.reduce(tags, [], &fetch_tag/2)
end
@spec fetch_mentions([map()]) :: [map()]
def fetch_mentions(mentions) when is_list(mentions) do
Logger.debug("fetching mentions")
Enum.reduce(mentions, [], fn mention, acc -> create_mention(mention, acc) end)
end
def fetch_address(%{id: id}) do
with {id, ""} <- Integer.parse(id) do
%{id: id}
end
end
def fetch_address(address) when is_map(address) do
address
end
@spec build_tags([Tag.t()]) :: [Map.t()]
def build_tags(tags) do
Enum.map(tags, fn %Tag{} = tag ->
%{
"href" => MobilizonWeb.Endpoint.url() <> "/tags/#{tag.slug}",
"name" => "##{tag.title}",
"type" => "Hashtag"
}
end)
end
def build_mentions(mentions) do
Enum.map(mentions, fn %Mention{} = mention ->
if Ecto.assoc_loaded?(mention.actor) do
build_mention(mention.actor)
else
build_mention(Repo.preload(mention, [:actor]).actor)
end
end)
end
defp build_mention(%Actor{} = actor) do
%{
"href" => actor.url,
"name" => "@#{Mobilizon.Actors.Actor.preferred_username_and_domain(actor)}",
"type" => "Mention"
}
end
defp fetch_tag(tag, acc) when is_map(tag) do
case tag["type"] do
"Hashtag" ->
acc ++ [%{title: tag}]
_err ->
acc
end
end
defp fetch_tag(tag, acc) when is_bitstring(tag) do
acc ++ [%{title: tag}]
end
@spec create_mention(map(), list()) :: list()
defp create_mention(%Actor{id: actor_id} = _mention, acc) do
acc ++ [%{actor_id: actor_id}]
end
@spec create_mention(map(), list()) :: list()
defp create_mention(mention, acc) when is_map(mention) do
with true <- mention["type"] == "Mention",
{:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(mention["href"]) do
acc ++ [%{actor_id: actor_id}]
else
_err ->
acc
end
end
@spec create_mention({String.t(), map()}, list()) :: list()
defp create_mention({_, mention}, acc) when is_map(mention) do
create_mention(mention, acc)
end
end

View File

@@ -26,7 +26,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
def follow(target_instance) do
with %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
{:ok, activity} <- Follows.follow(local_actor, target_actor) do
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity}
@@ -39,7 +39,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
def unfollow(target_instance) do
with %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
{:ok, activity} <- Follows.unfollow(local_actor, target_actor) do
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity}
@@ -52,7 +52,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
def accept(target_instance) do
with %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance),
{:ok, activity} <- Follows.accept(target_actor, local_actor) do
{:ok, activity}
end
@@ -60,7 +60,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
# def reject(target_instance) do
# with %Actor{} = local_actor <- get_actor(),
# {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_by_url(target_instance),
# {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_actor_by_url(target_instance),
# {:ok, activity} <- Follows.reject(target_actor, local_actor) do
# {:ok, activity}
# end

View File

@@ -13,9 +13,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
alias Mobilizon.Events
alias Mobilizon.Events.{Comment, Event, Participant}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.{Converter, Convertible, Utils, Visibility}
alias Mobilizon.Service.ActivityPub.{Activity, Converter, Convertible, Utils}
alias MobilizonWeb.Email.Participation
import Mobilizon.Service.ActivityPub.Utils
require Logger
def get_actor(%{"actor" => actor}) when is_binary(actor) do
@@ -138,59 +140,65 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
@doc """
Handles a `Create` activity for `Note` (comments) objects
The following actions are performed
* Fetch the author of the activity
* Convert the ActivityStream data to the comment model format (it also finds and inserts tags)
* Get (by it's URL) or create the comment with this data
* Insert eventual mentions in the database
* Convert the comment back in ActivityStreams data
* Wrap this data back into a `Create` activity
* Return the activity and the comment object
"""
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object}) do
Logger.info("Handle incoming to create notes")
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(data["actor"]) do
Logger.debug("found actor")
Logger.debug(inspect(actor))
params = %{
to: data["to"],
object: object |> fix_object,
actor: actor,
local: false,
published: data["published"],
additional:
Map.take(data, [
"cc",
"id"
])
}
ActivityPub.create(params)
with {:ok, object_data} <-
object |> fix_object() |> Converter.Comment.as_to_model_data(),
{:existing_comment, {:error, :comment_not_found}} <-
{:existing_comment, Events.get_comment_from_url_with_preload(object_data.url)},
{:ok, %Activity{} = activity, %Comment{} = comment} <-
ActivityPub.create(:comment, object_data, false) do
{:ok, activity, comment}
else
{:existing_comment, {:ok, %Comment{} = comment}} ->
{:ok, nil, comment}
end
end
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object} = data) do
@doc """
Handles a `Create` activity for `Event` objects
The following actions are performed
* Fetch the author of the activity
* Convert the ActivityStream data to the event model format (it also finds and inserts tags)
* Get (by it's URL) or create the event with this data
* Insert eventual mentions in the database
* Convert the event back in ActivityStreams data
* Wrap this data back into a `Create` activity
* Return the activity and the event object
"""
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object}) do
Logger.info("Handle incoming to create event")
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(data["actor"]) do
Logger.debug("found actor")
Logger.debug(inspect(actor))
params = %{
to: data["to"],
object: object |> fix_object,
actor: actor,
local: false,
published: data["published"],
additional:
Map.take(data, [
"cc",
"id"
])
}
ActivityPub.create(params)
with {:ok, object_data} <-
object |> fix_object() |> Converter.Event.as_to_model_data(),
{:existing_event, nil} <- {:existing_event, Events.get_event_by_url(object_data.url)},
{:ok, %Activity{} = activity, %Event{} = event} <-
ActivityPub.create(:event, object_data, false) do
{:ok, activity, event}
else
{:existing_event, %Event{} = event} -> {:ok, nil, event}
end
end
def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
) do
with {:ok, %Actor{} = followed} <- ActivityPub.get_or_fetch_by_url(followed, true),
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_by_url(follower),
with {:ok, %Actor{} = followed} <- ActivityPub.get_or_fetch_actor_by_url(followed, true),
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_actor_by_url(follower),
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
{:ok, activity, object}
else
@@ -209,8 +217,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
} = data
) do
with actor_url <- get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor_url),
{:object_not_found, {:ok, activity, object}} <-
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url),
{:object_not_found, {:ok, %Activity{} = activity, object}} <-
{:object_not_found,
do_handle_incoming_accept_following(accepted_object, actor) ||
do_handle_incoming_accept_join(accepted_object, actor)} do
@@ -238,7 +246,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
) do
with actor_url <- get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor_url),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url),
{:object_not_found, {:ok, activity, object}} <-
{:object_not_found,
do_handle_incoming_reject_following(rejected_object, actor) ||
@@ -278,13 +286,20 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
# end
# #
def handle_incoming(
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => _id} = data
) do
with actor <- get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor),
# TODO: Is the following line useful?
{:ok, %Actor{} = _actor} <- ActivityPub.get_or_fetch_actor_by_url(actor),
:ok <- Logger.debug("Fetching contained object"),
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
public <- Visibility.is_public?(data),
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false, public) do
:ok <- Logger.debug("Handling contained object"),
create_data <-
make_create_data(object),
:ok <- Logger.debug(inspect(object)),
{:ok, _activity, object} <- handle_incoming(create_data),
:ok <- Logger.debug("Finished processing contained object"),
{:ok, activity} <- ActivityPub.create_activity(data, false) do
{:ok, activity, object}
else
e ->
@@ -293,21 +308,19 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => _actor_id} =
data
)
def handle_incoming(%{
"type" => "Update",
"object" => %{"type" => object_type} = object,
"actor" => _actor_id
})
when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
case Actors.get_actor_by_url(object["id"]) do
{:ok, %Actor{url: actor_url}} ->
ActivityPub.update(%{
local: false,
to: data["to"] || [],
cc: data["cc"] || [],
object: object,
actor: actor_url
})
with {:ok, %Actor{} = old_actor} <- Actors.get_actor_by_url(object["id"]),
{:ok, object_data} <-
object |> fix_object() |> Converter.Actor.as_to_model_data(),
{:ok, %Activity{} = activity, %Actor{} = new_actor} <-
ActivityPub.update(:actor, old_actor, object_data, false) do
{:ok, activity, new_actor}
else
e ->
Logger.debug(inspect(e))
:error
@@ -315,24 +328,19 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => actor} =
%{"type" => "Update", "object" => %{"type" => "Event"} = object, "actor" => _actor} =
_update
) do
with {:ok, %{"actor" => existing_organizer_actor_url} = existing_event_data} <-
fetch_obj_helper_as_activity_streams(object),
object <- Map.merge(existing_event_data, object),
{:ok, %Actor{url: actor_url}} <- actor |> Utils.get_url() |> Actors.get_actor_by_url(),
true <- Utils.get_url(existing_organizer_actor_url) == actor_url do
ActivityPub.update(%{
local: false,
to: object["to"] || [],
cc: object["cc"] || [],
object: object,
actor: actor_url
})
with %Event{} = old_event <-
Events.get_event_by_url(object["id"]),
{:ok, object_data} <-
object |> fix_object() |> Converter.Event.as_to_model_data(),
{:ok, %Activity{} = activity, %Event{} = new_event} <-
ActivityPub.update(:event, old_event, object_data, false) do
{:ok, activity, new_event}
else
e ->
Logger.debug(inspect(e))
Logger.error(inspect(e))
:error
end
end
@@ -350,7 +358,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
} = data
) do
with actor <- get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor),
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
{:ok, activity, object} <-
ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do
@@ -454,7 +462,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
# } = data
# ) do
# with actor <- get_actor(data),
# %Actor{} = actor <- ActivityPub.get_or_fetch_by_url(actor),
# %Actor{} = actor <- ActivityPub.get_or_fetch_actor_by_url(actor),
# {:ok, object} <- fetch_obj_helper(object_id) || fetch_obj_helper(object_id),
# {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
# {:ok, activity}
@@ -472,23 +480,16 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
Handle incoming `Accept` activities wrapping a `Follow` activity
"""
def do_handle_incoming_accept_following(follow_object, %Actor{} = actor) do
with {:follow,
{:ok,
%Follower{approved: false, actor: follower, id: follow_id, target_actor: followed} =
follow}} <-
with {:follow, {:ok, %Follower{approved: false, target_actor: followed} = follow}} <-
{:follow, get_follow(follow_object)},
{:same_actor, true} <- {:same_actor, actor.id == followed.id},
{:ok, activity, _} <-
{:ok, %Activity{} = activity, %Follower{approved: true} = follow} <-
ActivityPub.accept(
%{
to: [follower.url],
actor: actor.url,
object: follow_object,
local: false
},
"#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}"
),
{:ok, %Follower{approved: true}} <- Actors.update_follower(follow, %{"approved" => true}) do
:follow,
follow,
%{approved: true},
false
) do
{:ok, activity, follow}
else
{:follow, _} ->
@@ -546,26 +547,18 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
# Handle incoming `Accept` activities wrapping a `Join` activity on an event
defp do_handle_incoming_accept_join(join_object, %Actor{} = actor_accepting) do
with {:join_event,
{:ok,
%Participant{role: :not_approved, actor: actor, id: join_id, event: event} =
participant}} <-
with {:join_event, {:ok, %Participant{role: :not_approved, event: event} = participant}} <-
{:join_event, get_participant(join_object)},
# TODO: The actor that accepts the Join activity may another one that the event organizer ?
# Or maybe for groups it's the group that sends the Accept activity
{:same_actor, true} <- {:same_actor, actor_accepting.id == event.organizer_actor_id},
{:ok, activity, _} <-
{:ok, %Activity{} = activity, %Participant{role: :participant} = participant} <-
ActivityPub.accept(
%{
to: [actor.url],
actor: actor_accepting.url,
object: join_object,
local: false
},
"#{MobilizonWeb.Endpoint.url()}/accept/join/#{join_id}"
:join,
participant,
%{role: :participant},
false
),
{:ok, %Participant{role: :participant}} <-
Events.update_participant(participant, %{"role" => :participant}),
:ok <-
Participation.send_emails_to_local_user(participant) do
{:ok, activity, participant}
@@ -684,7 +677,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
def prepare_object(object) do
object
# |> set_sensitive
|> add_hashtags
# |> add_hashtags
|> add_mention_tags
# |> add_emoji_tags
|> add_attributed_to
@@ -781,6 +774,9 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
def add_mention_tags(object) do
Logger.debug("add mention tags")
Logger.debug(inspect(object))
recipients =
(object["to"] ++ (object["cc"] || [])) -- ["https://www.w3.org/ns/activitystreams#Public"]
@@ -795,7 +791,11 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end)
|> Enum.filter(& &1)
|> Enum.map(fn actor ->
%{"type" => "Mention", "href" => actor.url, "name" => "@#{actor.preferred_username}"}
%{
"type" => "Mention",
"href" => actor.url,
"name" => "@#{Actor.preferred_username_and_domain(actor)}"
}
end)
tags = object["tag"] || []
@@ -854,6 +854,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
@spec fetch_obj_helper(map() | String.t()) :: Event.t() | Comment.t() | Actor.t() | any()
def fetch_obj_helper(object) do
Logger.debug("fetch_obj_helper")
Logger.debug("Fetching object #{inspect(object)}")
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
@@ -867,6 +868,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
end
def fetch_obj_helper_as_activity_streams(object) do
Logger.debug("fetch_obj_helper_as_activity_streams")
with {:ok, object} <- fetch_obj_helper(object) do
{:ok, Convertible.model_to_as(object)}
end

View File

@@ -238,8 +238,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
@doc """
Save picture data from %Plug.Upload{} and return AS Link data.
"""
def make_picture_data(%Plug.Upload{} = picture) do
case MobilizonWeb.Upload.store(picture) do
def make_picture_data(%Plug.Upload{} = picture, opts) do
case MobilizonWeb.Upload.store(picture, opts) do
{:ok, picture} ->
picture
@@ -636,18 +636,39 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
Make create activity data
"""
@spec make_create_data(map(), map()) :: map()
def make_create_data(params, additional \\ %{}) do
def make_create_data(object, additional \\ %{}) do
Logger.debug("Making create data")
Logger.debug(inspect(params))
published = params.published || make_date()
Logger.debug(inspect(object))
Logger.debug(inspect(additional))
%{
"type" => "Create",
"to" => params.to |> Enum.uniq(),
"actor" => params.actor.url,
"object" => params.object,
"published" => published,
"id" => params.object["id"] <> "/activity"
"to" => object["to"],
"cc" => object["cc"],
"actor" => object["actor"],
"object" => object,
"published" => make_date(),
"id" => object["id"] <> "/activity"
}
|> Map.merge(additional)
end
@doc """
Make update activity data
"""
@spec make_update_data(map(), map()) :: map()
def make_update_data(object, additional \\ %{}) do
Logger.debug("Making update data")
Logger.debug(inspect(object))
Logger.debug(inspect(additional))
%{
"type" => "Update",
"to" => object["to"],
"cc" => object["cc"],
"actor" => object["actor"],
"object" => object,
"id" => object["id"] <> "/activity"
}
|> Map.merge(additional)
end
@@ -688,6 +709,22 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
}
end
@doc """
Make accept join activity data
"""
@spec make_accept_join_data(map(), map()) :: map()
def make_accept_join_data(object, additional \\ %{}) do
%{
"type" => "Accept",
"to" => object["to"],
"cc" => object["cc"],
"actor" => object["actor"],
"object" => object,
"id" => object["id"] <> "/activity"
}
|> Map.merge(additional)
end
@doc """
Converts PEM encoded keys to a public key representation
"""

View File

@@ -53,7 +53,7 @@ defmodule Mobilizon.Service.Federator do
Logger.debug(inspect(params))
case Transmogrifier.handle_incoming(params) do
{:ok, activity, _} ->
{:ok, activity, _data} ->
{:ok, activity}
%Activity{} ->

View File

@@ -52,7 +52,7 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
@spec get_public_key_for_url(String.t()) ::
{:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error}
def get_public_key_for_url(url) do
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_by_url(url),
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url),
{:ok, public_key} <- prepare_public_key(keys) do
{:ok, public_key}
else