2020-10-12 18:00:50 +01:00
|
|
|
# Pleroma: A lightweight social networking server
|
2021-01-13 06:49:20 +00:00
|
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
2020-10-12 18:00:50 +01:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2019-10-16 15:16:39 +01: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 13:08:54 +01:00
|
|
|
alias Pleroma.Emoji
|
2019-10-23 11:18:05 +01:00
|
|
|
alias Pleroma.Object
|
|
|
|
alias Pleroma.User
|
2020-05-26 10:47:03 +01:00
|
|
|
alias Pleroma.Web.ActivityPub.Relay
|
2019-10-16 15:16:39 +01:00
|
|
|
alias Pleroma.Web.ActivityPub.Utils
|
|
|
|
alias Pleroma.Web.ActivityPub.Visibility
|
|
|
|
|
2020-05-20 14:44:37 +01:00
|
|
|
require Pleroma.Constants
|
|
|
|
|
2020-08-12 13:48:51 +01:00
|
|
|
def accept_or_reject(actor, activity, type) do
|
2020-08-11 14:13:07 +01:00
|
|
|
data = %{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
2020-08-12 13:48:51 +01:00
|
|
|
"type" => type,
|
|
|
|
"object" => activity.data["id"],
|
|
|
|
"to" => [activity.actor]
|
2020-08-11 14:13:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
{:ok, data, []}
|
|
|
|
end
|
|
|
|
|
2020-08-12 13:48:51 +01: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 14:57:19 +01: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 11:11:46 +01:00
|
|
|
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
|
|
|
|
def emoji_react(actor, object, emoji) do
|
2020-05-08 10:30:31 +01:00
|
|
|
with {:ok, data, meta} <- object_action(actor, object) do
|
2020-05-05 11:11:46 +01:00
|
|
|
data =
|
|
|
|
data
|
|
|
|
|> Map.put("content", emoji)
|
|
|
|
|> Map.put("type", "EmojiReact")
|
|
|
|
|
|
|
|
{:ok, data, meta}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-05 13:17:47 +01: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 18:09:51 +01:00
|
|
|
@spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
|
|
|
|
def delete(actor, object_id) do
|
2021-01-04 12:38:31 +00:00
|
|
|
object = Object.normalize(object_id, fetch: false)
|
2020-04-29 18:09:51 +01:00
|
|
|
|
2020-04-30 14:42:30 +01: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 18:09:51 +01:00
|
|
|
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"object" => object_id,
|
|
|
|
"to" => to,
|
|
|
|
"type" => "Delete"
|
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-04-28 16:29:54 +01:00
|
|
|
def create(actor, object, recipients) do
|
2020-07-02 04:45:19 +01:00
|
|
|
context =
|
|
|
|
if is_map(object) do
|
|
|
|
object["context"]
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2020-04-09 11:44:20 +01:00
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"to" => recipients,
|
2020-04-28 16:29:54 +01:00
|
|
|
"object" => object,
|
2020-04-20 12:14:59 +01:00
|
|
|
"type" => "Create",
|
|
|
|
"published" => DateTime.utc_now() |> DateTime.to_iso8601()
|
2020-07-02 04:45:19 +01:00
|
|
|
}
|
|
|
|
|> Pleroma.Maps.put_if_present("context", context), []}
|
2020-04-09 11:44:20 +01:00
|
|
|
end
|
|
|
|
|
2020-05-06 15:12:36 +01: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 11:44:20 +01:00
|
|
|
end
|
|
|
|
|
2020-06-18 03:05:42 +01:00
|
|
|
def answer(user, object, name) do
|
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"type" => "Answer",
|
|
|
|
"actor" => user.ap_id,
|
2020-06-25 23:07:43 +01:00
|
|
|
"attributedTo" => user.ap_id,
|
2020-06-18 03:05:42 +01: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 14:06:23 +01: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 15:16:39 +01:00
|
|
|
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
|
|
|
def like(actor, object) do
|
2020-05-08 10:30:31 +01:00
|
|
|
with {:ok, data, meta} <- object_action(actor, object) do
|
|
|
|
data =
|
|
|
|
data
|
|
|
|
|> Map.put("type", "Like")
|
|
|
|
|
|
|
|
{:ok, data, meta}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-19 14:30:30 +01: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 10:13:35 +01: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 10:47:03 +01:00
|
|
|
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
2020-05-20 14:44:37 +01:00
|
|
|
def announce(actor, object, options \\ []) do
|
|
|
|
public? = Keyword.get(options, :public, false)
|
2020-05-18 15:45:11 +01:00
|
|
|
|
2020-05-20 14:44:37 +01:00
|
|
|
to =
|
2020-05-26 10:47:03 +01:00
|
|
|
cond do
|
2020-08-18 16:21:34 +01:00
|
|
|
actor.ap_id == Relay.ap_id() ->
|
2020-05-26 10:47:03 +01:00
|
|
|
[actor.follower_address]
|
|
|
|
|
2020-11-11 14:47:57 +00:00
|
|
|
public? and Visibility.is_local_public?(object) ->
|
2020-10-15 16:07:00 +01:00
|
|
|
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_local_public()]
|
2020-10-02 18:00:50 +01:00
|
|
|
|
2020-05-26 10:47:03 +01:00
|
|
|
public? ->
|
|
|
|
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
|
|
|
|
|
|
|
|
true ->
|
|
|
|
[actor.follower_address, object.data["actor"]]
|
2020-05-20 14:44:37 +01:00
|
|
|
end
|
|
|
|
|
2020-05-18 15:45:11 +01:00
|
|
|
{:ok,
|
|
|
|
%{
|
|
|
|
"id" => Utils.generate_activity_id(),
|
|
|
|
"actor" => actor.ap_id,
|
|
|
|
"object" => object.data["id"],
|
|
|
|
"to" => to,
|
|
|
|
"context" => object.data["context"],
|
2020-05-20 14:44:37 +01:00
|
|
|
"type" => "Announce",
|
|
|
|
"published" => Utils.make_date()
|
2020-05-18 15:45:11 +01:00
|
|
|
}, []}
|
|
|
|
end
|
|
|
|
|
2020-05-08 10:30:31 +01:00
|
|
|
@spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
|
|
|
defp object_action(actor, object) do
|
2019-10-16 15:16:39 +01: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
|