Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

309 lines
8.2KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.Builder do
  5. @moduledoc """
  6. This module builds the objects. Meant to be used for creating local objects.
  7. This module encodes our addressing policies and general shape of our objects.
  8. """
  9. alias Pleroma.Emoji
  10. alias Pleroma.Object
  11. alias Pleroma.User
  12. alias Pleroma.Web.ActivityPub.Relay
  13. alias Pleroma.Web.ActivityPub.Utils
  14. alias Pleroma.Web.ActivityPub.Visibility
  15. require Pleroma.Constants
  16. def accept_or_reject(actor, activity, type) do
  17. data = %{
  18. "id" => Utils.generate_activity_id(),
  19. "actor" => actor.ap_id,
  20. "type" => type,
  21. "object" => activity.data["id"],
  22. "to" => [activity.actor]
  23. }
  24. {:ok, data, []}
  25. end
  26. @spec reject(User.t(), Activity.t()) :: {:ok, map(), keyword()}
  27. def reject(actor, rejected_activity) do
  28. accept_or_reject(actor, rejected_activity, "Reject")
  29. end
  30. @spec accept(User.t(), Activity.t()) :: {:ok, map(), keyword()}
  31. def accept(actor, accepted_activity) do
  32. accept_or_reject(actor, accepted_activity, "Accept")
  33. end
  34. @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
  35. def follow(follower, followed) do
  36. data = %{
  37. "id" => Utils.generate_activity_id(),
  38. "actor" => follower.ap_id,
  39. "type" => "Follow",
  40. "object" => followed.ap_id,
  41. "to" => [followed.ap_id]
  42. }
  43. {:ok, data, []}
  44. end
  45. @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
  46. def emoji_react(actor, object, emoji) do
  47. with {:ok, data, meta} <- object_action(actor, object) do
  48. data =
  49. data
  50. |> Map.put("content", emoji)
  51. |> Map.put("type", "EmojiReact")
  52. {:ok, data, meta}
  53. end
  54. end
  55. @spec undo(User.t(), Activity.t()) :: {:ok, map(), keyword()}
  56. def undo(actor, object) do
  57. {:ok,
  58. %{
  59. "id" => Utils.generate_activity_id(),
  60. "actor" => actor.ap_id,
  61. "type" => "Undo",
  62. "object" => object.data["id"],
  63. "to" => object.data["to"] || [],
  64. "cc" => object.data["cc"] || []
  65. }, []}
  66. end
  67. @spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
  68. def delete(actor, object_id) do
  69. object = Object.normalize(object_id, fetch: false)
  70. user = !object && User.get_cached_by_ap_id(object_id)
  71. to =
  72. case {object, user} do
  73. {%Object{}, _} ->
  74. # We are deleting an object, address everyone who was originally mentioned
  75. (object.data["to"] || []) ++ (object.data["cc"] || [])
  76. {_, %User{follower_address: follower_address}} ->
  77. # We are deleting a user, address the followers of that user
  78. [follower_address]
  79. end
  80. {:ok,
  81. %{
  82. "id" => Utils.generate_activity_id(),
  83. "actor" => actor.ap_id,
  84. "object" => object_id,
  85. "to" => to,
  86. "type" => "Delete"
  87. }, []}
  88. end
  89. def create(actor, object, recipients) do
  90. context =
  91. if is_map(object) do
  92. object["context"]
  93. else
  94. nil
  95. end
  96. {:ok,
  97. %{
  98. "id" => Utils.generate_activity_id(),
  99. "actor" => actor.ap_id,
  100. "to" => recipients,
  101. "object" => object,
  102. "type" => "Create",
  103. "published" => DateTime.utc_now() |> DateTime.to_iso8601()
  104. }
  105. |> Pleroma.Maps.put_if_present("context", context), []}
  106. end
  107. def chat_message(actor, recipient, content, opts \\ []) do
  108. basic = %{
  109. "id" => Utils.generate_object_id(),
  110. "actor" => actor.ap_id,
  111. "type" => "ChatMessage",
  112. "to" => [recipient],
  113. "content" => content,
  114. "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
  115. "emoji" => Emoji.Formatter.get_emoji_map(content)
  116. }
  117. case opts[:attachment] do
  118. %Object{data: attachment_data} ->
  119. {
  120. :ok,
  121. Map.put(basic, "attachment", attachment_data),
  122. []
  123. }
  124. _ ->
  125. {:ok, basic, []}
  126. end
  127. end
  128. def answer(user, object, name) do
  129. {:ok,
  130. %{
  131. "type" => "Answer",
  132. "actor" => user.ap_id,
  133. "attributedTo" => user.ap_id,
  134. "cc" => [object.data["actor"]],
  135. "to" => [],
  136. "name" => name,
  137. "inReplyTo" => object.data["id"],
  138. "context" => object.data["context"],
  139. "published" => DateTime.utc_now() |> DateTime.to_iso8601(),
  140. "id" => Utils.generate_object_id()
  141. }, []}
  142. end
  143. @spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
  144. def tombstone(actor, id) do
  145. {:ok,
  146. %{
  147. "id" => id,
  148. "actor" => actor,
  149. "type" => "Tombstone"
  150. }, []}
  151. end
  152. @spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
  153. def like(actor, object) do
  154. with {:ok, data, meta} <- object_action(actor, object) do
  155. data =
  156. data
  157. |> Map.put("type", "Like")
  158. {:ok, data, meta}
  159. end
  160. end
  161. # Retricted to user updates for now, always public
  162. @spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
  163. def update(actor, object) do
  164. to = [Pleroma.Constants.as_public(), actor.follower_address]
  165. {:ok,
  166. %{
  167. "id" => Utils.generate_activity_id(),
  168. "type" => "Update",
  169. "actor" => actor.ap_id,
  170. "object" => object,
  171. "to" => to
  172. }, []}
  173. end
  174. @spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
  175. def block(blocker, blocked) do
  176. {:ok,
  177. %{
  178. "id" => Utils.generate_activity_id(),
  179. "type" => "Block",
  180. "actor" => blocker.ap_id,
  181. "object" => blocked.ap_id,
  182. "to" => [blocked.ap_id]
  183. }, []}
  184. end
  185. @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
  186. def announce(actor, object, options \\ []) do
  187. public? = Keyword.get(options, :public, false)
  188. to =
  189. cond do
  190. actor.ap_id == Relay.ap_id() ->
  191. [actor.follower_address]
  192. public? and Visibility.is_local_public?(object) ->
  193. [actor.follower_address, object.data["actor"], Utils.as_local_public()]
  194. public? ->
  195. [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()]
  196. true ->
  197. [actor.follower_address, object.data["actor"]]
  198. end
  199. {:ok,
  200. %{
  201. "id" => Utils.generate_activity_id(),
  202. "actor" => actor.ap_id,
  203. "object" => object.data["id"],
  204. "to" => to,
  205. "context" => object.data["context"],
  206. "type" => "Announce",
  207. "published" => Utils.make_date()
  208. }, []}
  209. end
  210. @spec object_action(User.t(), Object.t()) :: {:ok, map(), keyword()}
  211. defp object_action(actor, object) do
  212. object_actor = User.get_cached_by_ap_id(object.data["actor"])
  213. # Address the actor of the object, and our actor's follower collection if the post is public.
  214. to =
  215. if Visibility.is_public?(object) do
  216. [actor.follower_address, object.data["actor"]]
  217. else
  218. [object.data["actor"]]
  219. end
  220. # CC everyone who's been addressed in the object, except ourself and the object actor's
  221. # follower collection
  222. cc =
  223. (object.data["to"] ++ (object.data["cc"] || []))
  224. |> List.delete(actor.ap_id)
  225. |> List.delete(object_actor.follower_address)
  226. {:ok,
  227. %{
  228. "id" => Utils.generate_activity_id(),
  229. "actor" => actor.ap_id,
  230. "object" => object.data["id"],
  231. "to" => to,
  232. "cc" => cc,
  233. "context" => object.data["context"]
  234. }, []}
  235. end
  236. @spec pin(User.t(), Object.t()) :: {:ok, map(), keyword()}
  237. def pin(%User{} = user, object) do
  238. {:ok,
  239. %{
  240. "id" => Utils.generate_activity_id(),
  241. "target" => pinned_url(user.nickname),
  242. "object" => object.data["id"],
  243. "actor" => user.ap_id,
  244. "type" => "Add",
  245. "to" => [Pleroma.Constants.as_public()],
  246. "cc" => [user.follower_address]
  247. }, []}
  248. end
  249. @spec unpin(User.t(), Object.t()) :: {:ok, map, keyword()}
  250. def unpin(%User{} = user, object) do
  251. {:ok,
  252. %{
  253. "id" => Utils.generate_activity_id(),
  254. "target" => pinned_url(user.nickname),
  255. "object" => object.data["id"],
  256. "actor" => user.ap_id,
  257. "type" => "Remove",
  258. "to" => [Pleroma.Constants.as_public()],
  259. "cc" => [user.follower_address]
  260. }, []}
  261. end
  262. defp pinned_url(nickname) when is_binary(nickname) do
  263. Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname)
  264. end
  265. end