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.

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