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.

891 lines
25KB

  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.Utils do
  5. alias Ecto.Changeset
  6. alias Ecto.UUID
  7. alias Pleroma.Activity
  8. alias Pleroma.Config
  9. alias Pleroma.Maps
  10. alias Pleroma.Notification
  11. alias Pleroma.Object
  12. alias Pleroma.Repo
  13. alias Pleroma.User
  14. alias Pleroma.Web.ActivityPub.ActivityPub
  15. alias Pleroma.Web.ActivityPub.Visibility
  16. alias Pleroma.Web.AdminAPI.AccountView
  17. alias Pleroma.Web.Endpoint
  18. alias Pleroma.Web.Router.Helpers
  19. import Ecto.Query
  20. require Logger
  21. require Pleroma.Constants
  22. @supported_object_types [
  23. "Article",
  24. "Note",
  25. "Event",
  26. "Video",
  27. "Page",
  28. "Question",
  29. "Answer",
  30. "Audio"
  31. ]
  32. @strip_status_report_states ~w(closed resolved)
  33. @supported_report_states ~w(open closed resolved)
  34. @valid_visibilities ~w(public unlisted private direct)
  35. def as_local_public, do: Endpoint.url() <> "/#Public"
  36. # Some implementations send the actor URI as the actor field, others send the entire actor object,
  37. # so figure out what the actor's URI is based on what we have.
  38. def get_ap_id(%{"id" => id} = _), do: id
  39. def get_ap_id(id), do: id
  40. def normalize_params(params) do
  41. Map.put(params, "actor", get_ap_id(params["actor"]))
  42. end
  43. @spec determine_explicit_mentions(map()) :: [any]
  44. def determine_explicit_mentions(%{"tag" => tag}) when is_list(tag) do
  45. Enum.flat_map(tag, fn
  46. %{"type" => "Mention", "href" => href} -> [href]
  47. _ -> []
  48. end)
  49. end
  50. def determine_explicit_mentions(%{"tag" => tag} = object) when is_map(tag) do
  51. object
  52. |> Map.put("tag", [tag])
  53. |> determine_explicit_mentions()
  54. end
  55. def determine_explicit_mentions(_), do: []
  56. @spec label_in_collection?(any(), any()) :: boolean()
  57. defp label_in_collection?(ap_id, coll) when is_binary(coll), do: ap_id == coll
  58. defp label_in_collection?(ap_id, coll) when is_list(coll), do: ap_id in coll
  59. defp label_in_collection?(_, _), do: false
  60. @spec label_in_message?(String.t(), map()) :: boolean()
  61. def label_in_message?(label, params),
  62. do:
  63. [params["to"], params["cc"], params["bto"], params["bcc"]]
  64. |> Enum.any?(&label_in_collection?(label, &1))
  65. @spec unaddressed_message?(map()) :: boolean()
  66. def unaddressed_message?(params),
  67. do:
  68. [params["to"], params["cc"], params["bto"], params["bcc"]]
  69. |> Enum.all?(&is_nil(&1))
  70. @spec recipient_in_message(User.t(), User.t(), map()) :: boolean()
  71. def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
  72. do:
  73. label_in_message?(ap_id, params) || unaddressed_message?(params) ||
  74. User.following?(recipient, actor)
  75. defp extract_list(target) when is_binary(target), do: [target]
  76. defp extract_list(lst) when is_list(lst), do: lst
  77. defp extract_list(_), do: []
  78. def maybe_splice_recipient(ap_id, params) do
  79. need_splice? =
  80. !label_in_collection?(ap_id, params["to"]) &&
  81. !label_in_collection?(ap_id, params["cc"])
  82. if need_splice? do
  83. cc = [ap_id | extract_list(params["cc"])]
  84. params
  85. |> Map.put("cc", cc)
  86. |> Maps.safe_put_in(["object", "cc"], cc)
  87. else
  88. params
  89. end
  90. end
  91. def make_json_ld_header do
  92. %{
  93. "@context" => [
  94. "https://www.w3.org/ns/activitystreams",
  95. "#{Endpoint.url()}/schemas/litepub-0.1.jsonld",
  96. %{
  97. "@language" => "und"
  98. }
  99. ]
  100. }
  101. end
  102. def make_date do
  103. DateTime.utc_now() |> DateTime.to_iso8601()
  104. end
  105. def generate_activity_id do
  106. generate_id("activities")
  107. end
  108. def generate_context_id do
  109. generate_id("contexts")
  110. end
  111. def generate_object_id do
  112. Helpers.o_status_url(Endpoint, :object, UUID.generate())
  113. end
  114. def generate_id(type) do
  115. "#{Endpoint.url()}/#{type}/#{UUID.generate()}"
  116. end
  117. def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do
  118. fake_create_activity = %{
  119. "to" => object["to"],
  120. "cc" => object["cc"],
  121. "type" => "Create",
  122. "object" => object
  123. }
  124. get_notified_from_object(fake_create_activity)
  125. end
  126. def get_notified_from_object(object) do
  127. Notification.get_notified_from_activity(%Activity{data: object}, false)
  128. end
  129. def create_context(context) do
  130. context = context || generate_id("contexts")
  131. # Ecto has problems accessing the constraint inside the jsonb,
  132. # so we explicitly check for the existed object before insert
  133. object = Object.get_cached_by_ap_id(context)
  134. with true <- is_nil(object),
  135. changeset <- Object.context_mapping(context),
  136. {:ok, inserted_object} <- Repo.insert(changeset) do
  137. inserted_object
  138. else
  139. _ ->
  140. object
  141. end
  142. end
  143. @doc """
  144. Enqueues an activity for federation if it's local
  145. """
  146. @spec maybe_federate(any()) :: :ok
  147. def maybe_federate(%Activity{local: true, data: %{"type" => type}} = activity) do
  148. outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
  149. with true <- Config.get!([:instance, :federating]),
  150. true <- type != "Block" || outgoing_blocks,
  151. false <- Visibility.is_local_public?(activity) do
  152. Pleroma.Web.Federator.publish(activity)
  153. end
  154. :ok
  155. end
  156. def maybe_federate(_), do: :ok
  157. @doc """
  158. Adds an id and a published data if they aren't there,
  159. also adds it to an included object
  160. """
  161. @spec lazy_put_activity_defaults(map(), boolean) :: map()
  162. def lazy_put_activity_defaults(map, fake? \\ false)
  163. def lazy_put_activity_defaults(map, true) do
  164. map
  165. |> Map.put_new("id", "pleroma:fakeid")
  166. |> Map.put_new_lazy("published", &make_date/0)
  167. |> Map.put_new("context", "pleroma:fakecontext")
  168. |> Map.put_new("context_id", -1)
  169. |> lazy_put_object_defaults(true)
  170. end
  171. def lazy_put_activity_defaults(map, _fake?) do
  172. %{data: %{"id" => context}, id: context_id} = create_context(map["context"])
  173. map
  174. |> Map.put_new_lazy("id", &generate_activity_id/0)
  175. |> Map.put_new_lazy("published", &make_date/0)
  176. |> Map.put_new("context", context)
  177. |> Map.put_new("context_id", context_id)
  178. |> lazy_put_object_defaults(false)
  179. end
  180. # Adds an id and published date if they aren't there.
  181. #
  182. @spec lazy_put_object_defaults(map(), boolean()) :: map()
  183. defp lazy_put_object_defaults(%{"object" => map} = activity, true)
  184. when is_map(map) do
  185. object =
  186. map
  187. |> Map.put_new("id", "pleroma:fake_object_id")
  188. |> Map.put_new_lazy("published", &make_date/0)
  189. |> Map.put_new("context", activity["context"])
  190. |> Map.put_new("context_id", activity["context_id"])
  191. |> Map.put_new("fake", true)
  192. %{activity | "object" => object}
  193. end
  194. defp lazy_put_object_defaults(%{"object" => map} = activity, _)
  195. when is_map(map) do
  196. object =
  197. map
  198. |> Map.put_new_lazy("id", &generate_object_id/0)
  199. |> Map.put_new_lazy("published", &make_date/0)
  200. |> Map.put_new("context", activity["context"])
  201. |> Map.put_new("context_id", activity["context_id"])
  202. %{activity | "object" => object}
  203. end
  204. defp lazy_put_object_defaults(activity, _), do: activity
  205. @doc """
  206. Inserts a full object if it is contained in an activity.
  207. """
  208. def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)
  209. when type in @supported_object_types do
  210. with {:ok, object} <- Object.create(object_data) do
  211. map = Map.put(map, "object", object.data["id"])
  212. {:ok, map, object}
  213. end
  214. end
  215. def insert_full_object(map), do: {:ok, map, nil}
  216. #### Like-related helpers
  217. @doc """
  218. Returns an existing like if a user already liked an object
  219. """
  220. @spec get_existing_like(String.t(), map()) :: Activity.t() | nil
  221. def get_existing_like(actor, %{data: %{"id" => id}}) do
  222. actor
  223. |> Activity.Queries.by_actor()
  224. |> Activity.Queries.by_object_id(id)
  225. |> Activity.Queries.by_type("Like")
  226. |> limit(1)
  227. |> Repo.one()
  228. end
  229. @doc """
  230. Returns like activities targeting an object
  231. """
  232. def get_object_likes(%{data: %{"id" => id}}) do
  233. id
  234. |> Activity.Queries.by_object_id()
  235. |> Activity.Queries.by_type("Like")
  236. |> Repo.all()
  237. end
  238. @spec make_like_data(User.t(), map(), String.t()) :: map()
  239. def make_like_data(
  240. %User{ap_id: ap_id} = actor,
  241. %{data: %{"actor" => object_actor_id, "id" => id}} = object,
  242. activity_id
  243. ) do
  244. object_actor = User.get_cached_by_ap_id(object_actor_id)
  245. to =
  246. if Visibility.is_public?(object) do
  247. [actor.follower_address, object.data["actor"]]
  248. else
  249. [object.data["actor"]]
  250. end
  251. cc =
  252. (object.data["to"] ++ (object.data["cc"] || []))
  253. |> List.delete(actor.ap_id)
  254. |> List.delete(object_actor.follower_address)
  255. %{
  256. "type" => "Like",
  257. "actor" => ap_id,
  258. "object" => id,
  259. "to" => to,
  260. "cc" => cc,
  261. "context" => object.data["context"]
  262. }
  263. |> Maps.put_if_present("id", activity_id)
  264. end
  265. def make_emoji_reaction_data(user, object, emoji, activity_id) do
  266. make_like_data(user, object, activity_id)
  267. |> Map.put("type", "EmojiReact")
  268. |> Map.put("content", emoji)
  269. end
  270. @spec update_element_in_object(String.t(), list(any), Object.t(), integer() | nil) ::
  271. {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
  272. def update_element_in_object(property, element, object, count \\ nil) do
  273. length =
  274. count ||
  275. length(element)
  276. data =
  277. Map.merge(
  278. object.data,
  279. %{"#{property}_count" => length, "#{property}s" => element}
  280. )
  281. object
  282. |> Changeset.change(data: data)
  283. |> Object.update_and_set_cache()
  284. end
  285. @spec add_emoji_reaction_to_object(Activity.t(), Object.t()) ::
  286. {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
  287. def add_emoji_reaction_to_object(
  288. %Activity{data: %{"content" => emoji, "actor" => actor}},
  289. object
  290. ) do
  291. reactions = get_cached_emoji_reactions(object)
  292. new_reactions =
  293. case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
  294. nil ->
  295. reactions ++ [[emoji, [actor]]]
  296. index ->
  297. List.update_at(
  298. reactions,
  299. index,
  300. fn [emoji, users] -> [emoji, Enum.uniq([actor | users])] end
  301. )
  302. end
  303. count = emoji_count(new_reactions)
  304. update_element_in_object("reaction", new_reactions, object, count)
  305. end
  306. def emoji_count(reactions_list) do
  307. Enum.reduce(reactions_list, 0, fn [_, users], acc -> acc + length(users) end)
  308. end
  309. def remove_emoji_reaction_from_object(
  310. %Activity{data: %{"content" => emoji, "actor" => actor}},
  311. object
  312. ) do
  313. reactions = get_cached_emoji_reactions(object)
  314. new_reactions =
  315. case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do
  316. nil ->
  317. reactions
  318. index ->
  319. List.update_at(
  320. reactions,
  321. index,
  322. fn [emoji, users] -> [emoji, List.delete(users, actor)] end
  323. )
  324. |> Enum.reject(fn [_, users] -> Enum.empty?(users) end)
  325. end
  326. count = emoji_count(new_reactions)
  327. update_element_in_object("reaction", new_reactions, object, count)
  328. end
  329. def get_cached_emoji_reactions(object) do
  330. if is_list(object.data["reactions"]) do
  331. object.data["reactions"]
  332. else
  333. []
  334. end
  335. end
  336. @spec add_like_to_object(Activity.t(), Object.t()) ::
  337. {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
  338. def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
  339. [actor | fetch_likes(object)]
  340. |> Enum.uniq()
  341. |> update_likes_in_object(object)
  342. end
  343. @spec remove_like_from_object(Activity.t(), Object.t()) ::
  344. {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
  345. def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do
  346. object
  347. |> fetch_likes()
  348. |> List.delete(actor)
  349. |> update_likes_in_object(object)
  350. end
  351. defp update_likes_in_object(likes, object) do
  352. update_element_in_object("like", likes, object)
  353. end
  354. defp fetch_likes(object) do
  355. if is_list(object.data["likes"]) do
  356. object.data["likes"]
  357. else
  358. []
  359. end
  360. end
  361. #### Follow-related helpers
  362. @doc """
  363. Updates a follow activity's state (for locked accounts).
  364. """
  365. @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity | nil}
  366. def update_follow_state_for_all(
  367. %Activity{data: %{"actor" => actor, "object" => object}} = activity,
  368. state
  369. ) do
  370. "Follow"
  371. |> Activity.Queries.by_type()
  372. |> Activity.Queries.by_actor(actor)
  373. |> Activity.Queries.by_object_id(object)
  374. |> where(fragment("data->>'state' = 'pending'"))
  375. |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
  376. |> Repo.update_all([])
  377. activity = Activity.get_by_id(activity.id)
  378. {:ok, activity}
  379. end
  380. def update_follow_state(
  381. %Activity{} = activity,
  382. state
  383. ) do
  384. new_data = Map.put(activity.data, "state", state)
  385. changeset = Changeset.change(activity, data: new_data)
  386. with {:ok, activity} <- Repo.update(changeset) do
  387. {:ok, activity}
  388. end
  389. end
  390. @doc """
  391. Makes a follow activity data for the given follower and followed
  392. """
  393. def make_follow_data(
  394. %User{ap_id: follower_id},
  395. %User{ap_id: followed_id} = _followed,
  396. activity_id
  397. ) do
  398. %{
  399. "type" => "Follow",
  400. "actor" => follower_id,
  401. "to" => [followed_id],
  402. "cc" => [Pleroma.Constants.as_public()],
  403. "object" => followed_id,
  404. "state" => "pending"
  405. }
  406. |> Maps.put_if_present("id", activity_id)
  407. end
  408. def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
  409. "Follow"
  410. |> Activity.Queries.by_type()
  411. |> where(actor: ^follower_id)
  412. # this is to use the index
  413. |> Activity.Queries.by_object_id(followed_id)
  414. |> order_by([activity], fragment("? desc nulls last", activity.id))
  415. |> limit(1)
  416. |> Repo.one()
  417. end
  418. def fetch_latest_undo(%User{ap_id: ap_id}) do
  419. "Undo"
  420. |> Activity.Queries.by_type()
  421. |> where(actor: ^ap_id)
  422. |> order_by([activity], fragment("? desc nulls last", activity.id))
  423. |> limit(1)
  424. |> Repo.one()
  425. end
  426. def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
  427. %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id)
  428. "EmojiReact"
  429. |> Activity.Queries.by_type()
  430. |> where(actor: ^ap_id)
  431. |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji))
  432. |> Activity.Queries.by_object_id(object_ap_id)
  433. |> order_by([activity], fragment("? desc nulls last", activity.id))
  434. |> limit(1)
  435. |> Repo.one()
  436. end
  437. #### Announce-related helpers
  438. @doc """
  439. Returns an existing announce activity if the notice has already been announced
  440. """
  441. @spec get_existing_announce(String.t(), map()) :: Activity.t() | nil
  442. def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do
  443. "Announce"
  444. |> Activity.Queries.by_type()
  445. |> where(actor: ^actor)
  446. # this is to use the index
  447. |> Activity.Queries.by_object_id(ap_id)
  448. |> Repo.one()
  449. end
  450. @doc """
  451. Make announce activity data for the given actor and object
  452. """
  453. # for relayed messages, we only want to send to subscribers
  454. def make_announce_data(
  455. %User{ap_id: ap_id} = user,
  456. %Object{data: %{"id" => id}} = object,
  457. activity_id,
  458. false
  459. ) do
  460. %{
  461. "type" => "Announce",
  462. "actor" => ap_id,
  463. "object" => id,
  464. "to" => [user.follower_address],
  465. "cc" => [],
  466. "context" => object.data["context"]
  467. }
  468. |> Maps.put_if_present("id", activity_id)
  469. end
  470. def make_announce_data(
  471. %User{ap_id: ap_id} = user,
  472. %Object{data: %{"id" => id}} = object,
  473. activity_id,
  474. true
  475. ) do
  476. %{
  477. "type" => "Announce",
  478. "actor" => ap_id,
  479. "object" => id,
  480. "to" => [user.follower_address, object.data["actor"]],
  481. "cc" => [Pleroma.Constants.as_public()],
  482. "context" => object.data["context"]
  483. }
  484. |> Maps.put_if_present("id", activity_id)
  485. end
  486. def make_undo_data(
  487. %User{ap_id: actor, follower_address: follower_address},
  488. %Activity{
  489. data: %{"id" => undone_activity_id, "context" => context},
  490. actor: undone_activity_actor
  491. },
  492. activity_id \\ nil
  493. ) do
  494. %{
  495. "type" => "Undo",
  496. "actor" => actor,
  497. "object" => undone_activity_id,
  498. "to" => [follower_address, undone_activity_actor],
  499. "cc" => [Pleroma.Constants.as_public()],
  500. "context" => context
  501. }
  502. |> Maps.put_if_present("id", activity_id)
  503. end
  504. @spec add_announce_to_object(Activity.t(), Object.t()) ::
  505. {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
  506. def add_announce_to_object(
  507. %Activity{data: %{"actor" => actor}},
  508. object
  509. ) do
  510. unless actor |> User.get_cached_by_ap_id() |> User.invisible?() do
  511. announcements = take_announcements(object)
  512. with announcements <- Enum.uniq([actor | announcements]) do
  513. update_element_in_object("announcement", announcements, object)
  514. end
  515. else
  516. {:ok, object}
  517. end
  518. end
  519. def add_announce_to_object(_, object), do: {:ok, object}
  520. @spec remove_announce_from_object(Activity.t(), Object.t()) ::
  521. {:ok, Object.t()} | {:error, Ecto.Changeset.t()}
  522. def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do
  523. with announcements <- List.delete(take_announcements(object), actor) do
  524. update_element_in_object("announcement", announcements, object)
  525. end
  526. end
  527. defp take_announcements(%{data: %{"announcements" => announcements}} = _)
  528. when is_list(announcements),
  529. do: announcements
  530. defp take_announcements(_), do: []
  531. #### Unfollow-related helpers
  532. def make_unfollow_data(follower, followed, follow_activity, activity_id) do
  533. %{
  534. "type" => "Undo",
  535. "actor" => follower.ap_id,
  536. "to" => [followed.ap_id],
  537. "object" => follow_activity.data
  538. }
  539. |> Maps.put_if_present("id", activity_id)
  540. end
  541. #### Block-related helpers
  542. @spec fetch_latest_block(User.t(), User.t()) :: Activity.t() | nil
  543. def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do
  544. "Block"
  545. |> Activity.Queries.by_type()
  546. |> where(actor: ^blocker_id)
  547. # this is to use the index
  548. |> Activity.Queries.by_object_id(blocked_id)
  549. |> order_by([activity], fragment("? desc nulls last", activity.id))
  550. |> limit(1)
  551. |> Repo.one()
  552. end
  553. def make_block_data(blocker, blocked, activity_id) do
  554. %{
  555. "type" => "Block",
  556. "actor" => blocker.ap_id,
  557. "to" => [blocked.ap_id],
  558. "object" => blocked.ap_id
  559. }
  560. |> Maps.put_if_present("id", activity_id)
  561. end
  562. #### Create-related helpers
  563. def make_create_data(params, additional) do
  564. published = params.published || make_date()
  565. %{
  566. "type" => "Create",
  567. "to" => params.to |> Enum.uniq(),
  568. "actor" => params.actor.ap_id,
  569. "object" => params.object,
  570. "published" => published,
  571. "context" => params.context
  572. }
  573. |> Map.merge(additional)
  574. end
  575. #### Listen-related helpers
  576. def make_listen_data(params, additional) do
  577. published = params.published || make_date()
  578. %{
  579. "type" => "Listen",
  580. "to" => params.to |> Enum.uniq(),
  581. "actor" => params.actor.ap_id,
  582. "object" => params.object,
  583. "published" => published,
  584. "context" => params.context
  585. }
  586. |> Map.merge(additional)
  587. end
  588. #### Flag-related helpers
  589. @spec make_flag_data(map(), map()) :: map()
  590. def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do
  591. %{
  592. "type" => "Flag",
  593. "actor" => actor.ap_id,
  594. "content" => content,
  595. "object" => build_flag_object(params),
  596. "context" => context,
  597. "state" => "open"
  598. }
  599. |> Map.merge(additional)
  600. end
  601. def make_flag_data(_, _), do: %{}
  602. defp build_flag_object(%{account: account, statuses: statuses}) do
  603. [account.ap_id | build_flag_object(%{statuses: statuses})]
  604. end
  605. defp build_flag_object(%{statuses: statuses}) do
  606. Enum.map(statuses || [], &build_flag_object/1)
  607. end
  608. defp build_flag_object(%Activity{data: %{"id" => id}, object: %{data: data}}) do
  609. activity_actor = User.get_by_ap_id(data["actor"])
  610. %{
  611. "type" => "Note",
  612. "id" => id,
  613. "content" => data["content"],
  614. "published" => data["published"],
  615. "actor" =>
  616. AccountView.render(
  617. "show.json",
  618. %{user: activity_actor, skip_visibility_check: true}
  619. )
  620. }
  621. end
  622. defp build_flag_object(act) when is_map(act) or is_binary(act) do
  623. id =
  624. case act do
  625. %Activity{} = act -> act.data["id"]
  626. act when is_map(act) -> act["id"]
  627. act when is_binary(act) -> act
  628. end
  629. case Activity.get_by_ap_id_with_object(id) do
  630. %Activity{} = activity ->
  631. build_flag_object(activity)
  632. nil ->
  633. if activity = Activity.get_by_object_ap_id_with_object(id) do
  634. build_flag_object(activity)
  635. else
  636. %{"id" => id, "deleted" => true}
  637. end
  638. end
  639. end
  640. defp build_flag_object(_), do: []
  641. #### Report-related helpers
  642. def get_reports(params, page, page_size) do
  643. params =
  644. params
  645. |> Map.put(:type, "Flag")
  646. |> Map.put(:skip_preload, true)
  647. |> Map.put(:preload_report_notes, true)
  648. |> Map.put(:total, true)
  649. |> Map.put(:limit, page_size)
  650. |> Map.put(:offset, (page - 1) * page_size)
  651. ActivityPub.fetch_activities([], params, :offset)
  652. end
  653. def update_report_state(%Activity{} = activity, state)
  654. when state in @strip_status_report_states do
  655. {:ok, stripped_activity} = strip_report_status_data(activity)
  656. new_data =
  657. activity.data
  658. |> Map.put("state", state)
  659. |> Map.put("object", stripped_activity.data["object"])
  660. activity
  661. |> Changeset.change(data: new_data)
  662. |> Repo.update()
  663. end
  664. def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do
  665. new_data = Map.put(activity.data, "state", state)
  666. activity
  667. |> Changeset.change(data: new_data)
  668. |> Repo.update()
  669. end
  670. def update_report_state(activity_ids, state) when state in @supported_report_states do
  671. activities_num = length(activity_ids)
  672. from(a in Activity, where: a.id in ^activity_ids)
  673. |> update(set: [data: fragment("jsonb_set(data, '{state}', ?)", ^state)])
  674. |> Repo.update_all([])
  675. |> case do
  676. {^activities_num, _} -> :ok
  677. _ -> {:error, activity_ids}
  678. end
  679. end
  680. def update_report_state(_, _), do: {:error, "Unsupported state"}
  681. def strip_report_status_data(activity) do
  682. [actor | reported_activities] = activity.data["object"]
  683. stripped_activities =
  684. Enum.map(reported_activities, fn
  685. act when is_map(act) -> act["id"]
  686. act when is_binary(act) -> act
  687. end)
  688. new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
  689. {:ok, %{activity | data: new_data}}
  690. end
  691. def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do
  692. [to, cc, recipients] =
  693. activity
  694. |> get_updated_targets(visibility)
  695. |> Enum.map(&Enum.uniq/1)
  696. object_data =
  697. activity.object.data
  698. |> Map.put("to", to)
  699. |> Map.put("cc", cc)
  700. {:ok, object} =
  701. activity.object
  702. |> Object.change(%{data: object_data})
  703. |> Object.update_and_set_cache()
  704. activity_data =
  705. activity.data
  706. |> Map.put("to", to)
  707. |> Map.put("cc", cc)
  708. activity
  709. |> Map.put(:object, object)
  710. |> Activity.change(%{data: activity_data, recipients: recipients})
  711. |> Repo.update()
  712. end
  713. def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"}
  714. defp get_updated_targets(
  715. %Activity{data: %{"to" => to} = data, recipients: recipients},
  716. visibility
  717. ) do
  718. cc = Map.get(data, "cc", [])
  719. follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
  720. public = Pleroma.Constants.as_public()
  721. case visibility do
  722. "public" ->
  723. to = [public | List.delete(to, follower_address)]
  724. cc = [follower_address | List.delete(cc, public)]
  725. recipients = [public | recipients]
  726. [to, cc, recipients]
  727. "private" ->
  728. to = [follower_address | List.delete(to, public)]
  729. cc = List.delete(cc, public)
  730. recipients = List.delete(recipients, public)
  731. [to, cc, recipients]
  732. "unlisted" ->
  733. to = [follower_address | List.delete(to, public)]
  734. cc = [public | List.delete(cc, follower_address)]
  735. recipients = recipients ++ [follower_address, public]
  736. [to, cc, recipients]
  737. _ ->
  738. [to, cc, recipients]
  739. end
  740. end
  741. def get_existing_votes(actor, %{data: %{"id" => id}}) do
  742. actor
  743. |> Activity.Queries.by_actor()
  744. |> Activity.Queries.by_type("Create")
  745. |> Activity.with_preloaded_object()
  746. |> where([a, object: o], fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(id)))
  747. |> where([a, object: o], fragment("(?)->>'type' = 'Answer'", o.data))
  748. |> Repo.all()
  749. end
  750. end