2020-10-12 19:00:50 +02:00
|
|
|
# Pleroma: A lightweight social networking server
|
|
|
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2019-10-16 16:16:39 +02:00
|
|
|
defmodule Pleroma.Web.ActivityPub.Builder do
|
|
|
|
@moduledoc """
|
|
|
|
This module builds the objects. Meant to be used for creating local objects.
|
|
|
|
|
|
|
|
This module encodes our addressing policies and general shape of our objects.
|
|
|
|
"""
|
|
|
|
|
2020-04-20 14:08:54 +02:00
|
|
|
alias Pleroma.Emoji
|
2019-10-23 12:18:05 +02:00
|
|
|
alias Pleroma.Object
|
|
|
|
alias Pleroma.User
|
2020-05-26 11:47:03 +02:00
|
|
|
alias Pleroma.Web.ActivityPub.Relay
|
2019-10-16 16:16:39 +02:00
|
|
|
alias Pleroma.Web.ActivityPub.Utils
|
|
|
|
alias Pleroma.Web.ActivityPub.Visibility
|
|
|
|
|
2020-05-20 15:44:37 +02:00
|
|
|
require Pleroma.Constants
|
|
|
|
|
2020-08-12 14:48:51 +02:00
|
|
|
def accept_or_reject(actor, activity, type) do
|
2020-08-11 15:13:07 +02:00
|
|
|
data = %{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
2020-08-12 14:48:51 +02:00
|
|
|
"type" => type,
|
|
|
|
"object" => activity.data["id"],
|
|
|
|
"to" => [activity.actor]
|
2020-08-11 15:13:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
{:ok, data, []}
|
|
|
|
end
|
|
|
|
|
2020-08-12 14:48:51 +02:00
|
|
|
@spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
|
|
|
|
def reject(actor, rejected_activity) do
|
|
|
|
accept_or_reject(actor, rejected_activity, "Reject")
|
|
|
|
end
|
|
|
|
|
|
|
|
@spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
|
|
|
|
def accept(actor, accepted_activity) do
|
|
|
|
accept_or_reject(actor, accepted_activity, "Accept")
|
|
|
|
end
|
|
|
|
|
2020-07-06 15:57:19 +02:00
|
|
|
@spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
|
|
|
|
def follow(follower, followed) do
|
|
|
|
data = %{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => follower.ap_id,
|
|
|
|
"type" => "Follow",
|
|
|
|
"object" => followed.ap_id,
|
|
|
|
"to" => [followed.ap_id]
|
|
|
|
}
|
|
|
|
|
|
|
|
{:ok, data, []}
|
|
|
|
end
|
|
|
|
|
2020-05-05 12:11:46 +02:00
|
|
|
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
|
|
|
def emoji_react(actor, object, emoji) do
|
2020-05-08 11:30:31 +02:00
|
|
|
with {:ok, data, meta} <- object_action(actor, object) do
|
2020-05-05 12:11:46 +02:00
|
|
|
data =
|
|
|
|
data
|
|
|
|
|> Map.put("content", emoji)
|
|
|
|
|> Map.put("type", "EmojiReact")
|
|
|
|
|
|
|
|
{:ok, data, meta}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-05 14:17:47 +02:00
|
|
|
@spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
|
|
|
|
def undo(actor, object) do
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"type" => "Undo",
|
|
|
|
"object" => object.data["id"],
|
|
|
|
"to" => object.data["to"] || [],
|
|
|
|
"cc" => object.data["cc"] || []
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-04-29 19:09:51 +02:00
|
|
|
@spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
|
|
|
|
def delete(actor, object_id) do
|
2020-04-30 15:42:30 +02:00
|
|
|
object = Object.normalize(object_id, false)
|
2020-04-29 19:09:51 +02:00
|
|
|
|
2020-04-30 15:42:30 +02:00
|
|
|
user = !object && User.get_cached_by_ap_id(object_id)
|
|
|
|
|
|
|
|
to =
|
|
|
|
case {object, user} do
|
|
|
|
{%Object{}, _} ->
|
|
|
|
# We are deleting an object, address everyone who was originally mentioned
|
|
|
|
(object.data["to"] || []) ++ (object.data["cc"] || [])
|
|
|
|
|
|
|
|
{_, %User{follower_address: follower_address}} ->
|
|
|
|
# We are deleting a user, address the followers of that user
|
|
|
|
[follower_address]
|
|
|
|
end
|
2020-04-29 19:09:51 +02:00
|
|
|
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"object" => object_id,
|
|
|
|
"to" => to,
|
|
|
|
"type" => "Delete"
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-04-28 17:29:54 +02:00
|
|
|
def create(actor, object, recipients) do
|
2020-07-02 05:45:19 +02:00
|
|
|
context =
|
|
|
|
if is_map(object) do
|
|
|
|
object["context"]
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2020-04-09 12:44:20 +02:00
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"to" => recipients,
|
2020-04-28 17:29:54 +02:00
|
|
|
"object" => object,
|
2020-04-20 13:14:59 +02:00
|
|
|
"type" => "Create",
|
|
|
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
|
2020-07-02 05:45:19 +02:00
|
|
|
}
|
|
|
|
|> Pleroma.Maps.put_if_present("context", context), []}
|
2020-04-09 12:44:20 +02:00
|
|
|
end
|
|
|
|
|
2020-05-06 16:12:36 +02:00
|
|
|
def chat_message(actor, recipient, content, opts \\ []) do
|
|
|
|
basic = %{
|
|
|
|
"id" => Utils.generate_object_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"type" => "ChatMessage",
|
|
|
|
"to" => [recipient],
|
|
|
|
"content" => content,
|
|
|
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
|
|
|
"emoji" => Emoji.Formatter.get_emoji_map(content)
|
|
|
|
}
|
|
|
|
|
|
|
|
case opts[:attachment] do
|
|
|
|
%Object{data: attachment_data} ->
|
|
|
|
{
|
|
|
|
:ok,
|
|
|
|
Map.put(basic, "attachment", attachment_data),
|
|
|
|
[]
|
|
|
|
}
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
{:ok, basic, []}
|
|
|
|
end
|
2020-04-09 12:44:20 +02:00
|
|
|
end
|
|
|
|
|
2020-06-18 04:05:42 +02:00
|
|
|
def answer(user, object, name) do
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"type" => "Answer",
|
|
|
|
"actor" => user.ap_id,
|
2020-06-26 00:07:43 +02:00
|
|
|
"attributedTo" => user.ap_id,
|
2020-06-18 04:05:42 +02:00
|
|
|
"cc" => [object.data["actor"]],
|
|
|
|
"to" => [],
|
|
|
|
"name" => name,
|
|
|
|
"inReplyTo" => object.data["id"],
|
|
|
|
"context" => object.data["context"],
|
|
|
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
|
|
|
|
"id" => Utils.generate_object_id()
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-05-11 15:06:23 +02:00
|
|
|
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
|
|
|
|
def tombstone(actor, id) do
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => id,
|
|
|
|
"actor" => actor,
|
|
|
|
"type" => "Tombstone"
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2019-10-16 16:16:39 +02:00
|
|
|
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
|
|
|
def like(actor, object) do
|
2020-05-08 11:30:31 +02:00
|
|
|
with {:ok, data, meta} <- object_action(actor, object) do
|
|
|
|
data =
|
|
|
|
data
|
|
|
|
|> Map.put("type", "Like")
|
|
|
|
|
|
|
|
{:ok, data, meta}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-19 15:30:30 +02:00
|
|
|
# Retricted to user updates for now, always public
|
|
|
|
@spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
|
|
|
def update(actor, object) do
|
|
|
|
to = [Pleroma.Constants.as_public(), actor.follower_address]
|
|
|
|
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"type" => "Update",
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"object" => object,
|
|
|
|
"to" => to
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-06-25 11:13:35 +02:00
|
|
|
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
|
|
|
|
def block(blocker, blocked) do
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"type" => "Block",
|
|
|
|
"actor" => blocker.ap_id,
|
|
|
|
"object" => blocked.ap_id,
|
|
|
|
"to" => [blocked.ap_id]
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-05-26 11:47:03 +02:00
|
|
|
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
2020-05-20 15:44:37 +02:00
|
|
|
def announce(actor, object, options \\ []) do
|
|
|
|
public? = Keyword.get(options, :public, false)
|
2020-05-18 16:45:11 +02:00
|
|
|
|
2020-05-20 15:44:37 +02:00
|
|
|
to =
|
2020-05-26 11:47:03 +02:00
|
|
|
cond do
|
2020-08-18 17:21:34 +02:00
|
|
|
actor.ap_id == Relay.ap_id() ->
|
2020-05-26 11:47:03 +02:00
|
|
|
[actor.follower_address]
|
|
|
|
|
2020-10-02 19:00:50 +02:00
|
|
|
public? and Pleroma.Activity.local_only?(object) ->
|
2020-10-15 17:07:00 +02:00
|
|
|
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
|
2020-10-02 19:00:50 +02:00
|
|
|
|
2020-05-26 11:47:03 +02:00
|
|
|
public? ->
|
|
|
|
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
|
|
|
|
|
|
|
true ->
|
|
|
|
[actor.follower_address, object.data["actor"]]
|
2020-05-20 15:44:37 +02:00
|
|
|
end
|
|
|
|
|
2020-05-18 16:45:11 +02:00
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"object" => object.data["id"],
|
|
|
|
"to" => to,
|
|
|
|
"context" => object.data["context"],
|
2020-05-20 15:44:37 +02:00
|
|
|
"type" => "Announce",
|
|
|
|
"published" => Utils.make_date()
|
2020-05-18 16:45:11 +02:00
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-05-08 11:30:31 +02:00
|
|
|
@spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
|
|
|
defp object_action(actor, object) do
|
2019-10-16 16:16:39 +02:00
|
|
|
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
|
|
|
|
|
|
|
# Address the actor of the object, and our actor's follower collection if the post is public.
|
|
|
|
to =
|
|
|
|
if Visibility.is_public?(object) do
|
|
|
|
[actor.follower_address, object.data["actor"]]
|
|
|
|
else
|
|
|
|
[object.data["actor"]]
|
|
|
|
end
|
|
|
|
|
|
|
|
# CC everyone who's been addressed in the object, except ourself and the object actor's
|
|
|
|
# follower collection
|
|
|
|
cc =
|
|
|
|
(object.data["to"] ++ (object.data["cc"] || []))
|
|
|
|
|> List.delete(actor.ap_id)
|
|
|
|
|> List.delete(object_actor.follower_address)
|
|
|
|
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"object" => object.data["id"],
|
|
|
|
"to" => to,
|
|
|
|
"cc" => cc,
|
|
|
|
"context" => object.data["context"]
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
end
|