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.

446 lines
15KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.CommonAPI do
  5. alias Pleroma.Activity
  6. alias Pleroma.ActivityExpiration
  7. alias Pleroma.Conversation.Participation
  8. alias Pleroma.FollowingRelationship
  9. alias Pleroma.Object
  10. alias Pleroma.ThreadMute
  11. alias Pleroma.User
  12. alias Pleroma.UserRelationship
  13. alias Pleroma.Web.ActivityPub.ActivityPub
  14. alias Pleroma.Web.ActivityPub.Utils
  15. alias Pleroma.Web.ActivityPub.Visibility
  16. import Pleroma.Web.Gettext
  17. import Pleroma.Web.CommonAPI.Utils
  18. require Pleroma.Constants
  19. def follow(follower, followed) do
  20. timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
  21. with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
  22. {:ok, activity} <- ActivityPub.follow(follower, followed),
  23. {:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
  24. {:ok, follower, followed, activity}
  25. end
  26. end
  27. def unfollow(follower, unfollowed) do
  28. with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
  29. {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
  30. {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
  31. {:ok, follower}
  32. end
  33. end
  34. def accept_follow_request(follower, followed) do
  35. with {:ok, follower} <- User.follow(follower, followed),
  36. %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
  37. {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
  38. {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
  39. {:ok, _activity} <-
  40. ActivityPub.accept(%{
  41. to: [follower.ap_id],
  42. actor: followed,
  43. object: follow_activity.data["id"],
  44. type: "Accept"
  45. }) do
  46. {:ok, follower}
  47. end
  48. end
  49. def reject_follow_request(follower, followed) do
  50. with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
  51. {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
  52. {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
  53. {:ok, _activity} <-
  54. ActivityPub.reject(%{
  55. to: [follower.ap_id],
  56. actor: followed,
  57. object: follow_activity.data["id"],
  58. type: "Reject"
  59. }) do
  60. {:ok, follower}
  61. end
  62. end
  63. def delete(activity_id, user) do
  64. with {_, %Activity{data: %{"object" => _}} = activity} <-
  65. {:find_activity, Activity.get_by_id_with_object(activity_id)},
  66. %Object{} = object <- Object.normalize(activity),
  67. true <- User.superuser?(user) || user.ap_id == object.data["actor"],
  68. {:ok, _} <- unpin(activity_id, user),
  69. {:ok, delete} <- ActivityPub.delete(object) do
  70. {:ok, delete}
  71. else
  72. {:find_activity, _} -> {:error, :not_found}
  73. _ -> {:error, dgettext("errors", "Could not delete")}
  74. end
  75. end
  76. def repeat(id_or_ap_id, user, params \\ %{}) do
  77. with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
  78. object <- Object.normalize(activity),
  79. announce_activity <- Utils.get_existing_announce(user.ap_id, object),
  80. public <- public_announce?(object, params) do
  81. if announce_activity do
  82. {:ok, announce_activity, object}
  83. else
  84. ActivityPub.announce(user, object, nil, true, public)
  85. end
  86. else
  87. {:find_activity, _} -> {:error, :not_found}
  88. _ -> {:error, dgettext("errors", "Could not repeat")}
  89. end
  90. end
  91. def unrepeat(id_or_ap_id, user) do
  92. with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
  93. object = Object.normalize(activity)
  94. ActivityPub.unannounce(user, object)
  95. else
  96. {:find_activity, _} -> {:error, :not_found}
  97. _ -> {:error, dgettext("errors", "Could not unrepeat")}
  98. end
  99. end
  100. def favorite(id_or_ap_id, user) do
  101. with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
  102. object <- Object.normalize(activity),
  103. like_activity <- Utils.get_existing_like(user.ap_id, object) do
  104. if like_activity do
  105. {:ok, like_activity, object}
  106. else
  107. ActivityPub.like(user, object)
  108. end
  109. else
  110. {:find_activity, _} -> {:error, :not_found}
  111. _ -> {:error, dgettext("errors", "Could not favorite")}
  112. end
  113. end
  114. def unfavorite(id_or_ap_id, user) do
  115. with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
  116. object = Object.normalize(activity)
  117. ActivityPub.unlike(user, object)
  118. else
  119. {:find_activity, _} -> {:error, :not_found}
  120. _ -> {:error, dgettext("errors", "Could not unfavorite")}
  121. end
  122. end
  123. def react_with_emoji(id, user, emoji) do
  124. with %Activity{} = activity <- Activity.get_by_id(id),
  125. object <- Object.normalize(activity) do
  126. ActivityPub.react_with_emoji(user, object, emoji)
  127. else
  128. _ ->
  129. {:error, dgettext("errors", "Could not add reaction emoji")}
  130. end
  131. end
  132. def unreact_with_emoji(id, user, emoji) do
  133. with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji) do
  134. ActivityPub.unreact_with_emoji(user, reaction_activity.data["id"])
  135. else
  136. _ ->
  137. {:error, dgettext("errors", "Could not remove reaction emoji")}
  138. end
  139. end
  140. def vote(user, %{data: %{"type" => "Question"}} = object, choices) do
  141. with :ok <- validate_not_author(object, user),
  142. :ok <- validate_existing_votes(user, object),
  143. {:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
  144. answer_activities =
  145. Enum.map(choices, fn index ->
  146. answer_data = make_answer_data(user, object, Enum.at(options, index)["name"])
  147. {:ok, activity} =
  148. ActivityPub.create(%{
  149. to: answer_data["to"],
  150. actor: user,
  151. context: object.data["context"],
  152. object: answer_data,
  153. additional: %{"cc" => answer_data["cc"]}
  154. })
  155. activity
  156. end)
  157. object = Object.get_cached_by_ap_id(object.data["id"])
  158. {:ok, answer_activities, object}
  159. end
  160. end
  161. defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}),
  162. do: {:error, dgettext("errors", "Poll's author can't vote")}
  163. defp validate_not_author(_, _), do: :ok
  164. defp validate_existing_votes(%{ap_id: ap_id}, object) do
  165. if Utils.get_existing_votes(ap_id, object) == [] do
  166. :ok
  167. else
  168. {:error, dgettext("errors", "Already voted")}
  169. end
  170. end
  171. defp get_options_and_max_count(%{data: %{"anyOf" => any_of}}), do: {any_of, Enum.count(any_of)}
  172. defp get_options_and_max_count(%{data: %{"oneOf" => one_of}}), do: {one_of, 1}
  173. defp normalize_and_validate_choices(choices, object) do
  174. choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
  175. {options, max_count} = get_options_and_max_count(object)
  176. count = Enum.count(options)
  177. with {_, true} <- {:valid_choice, Enum.all?(choices, &(&1 < count))},
  178. {_, true} <- {:count_check, Enum.count(choices) <= max_count} do
  179. {:ok, options, choices}
  180. else
  181. {:valid_choice, _} -> {:error, dgettext("errors", "Invalid indices")}
  182. {:count_check, _} -> {:error, dgettext("errors", "Too many choices")}
  183. end
  184. end
  185. def public_announce?(_, %{"visibility" => visibility})
  186. when visibility in ~w{public unlisted private direct},
  187. do: visibility in ~w(public unlisted)
  188. def public_announce?(object, _) do
  189. Visibility.is_public?(object)
  190. end
  191. def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
  192. def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
  193. when visibility in ~w{public unlisted private direct},
  194. do: {visibility, get_replied_to_visibility(in_reply_to)}
  195. def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to, _) do
  196. visibility = {:list, String.to_integer(list_id)}
  197. {visibility, get_replied_to_visibility(in_reply_to)}
  198. end
  199. def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do
  200. visibility = get_replied_to_visibility(in_reply_to)
  201. {visibility, visibility}
  202. end
  203. def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)}
  204. def get_replied_to_visibility(nil), do: nil
  205. def get_replied_to_visibility(activity) do
  206. with %Object{} = object <- Object.normalize(activity) do
  207. Visibility.get_visibility(object)
  208. end
  209. end
  210. def check_expiry_date({:ok, nil} = res), do: res
  211. def check_expiry_date({:ok, in_seconds}) do
  212. expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
  213. if ActivityExpiration.expires_late_enough?(expiry) do
  214. {:ok, expiry}
  215. else
  216. {:error, "Expiry date is too soon"}
  217. end
  218. end
  219. def check_expiry_date(expiry_str) do
  220. Ecto.Type.cast(:integer, expiry_str)
  221. |> check_expiry_date()
  222. end
  223. def listen(user, %{"title" => _} = data) do
  224. with visibility <- data["visibility"] || "public",
  225. {to, cc} <- get_to_and_cc(user, [], nil, visibility, nil),
  226. listen_data <-
  227. Map.take(data, ["album", "artist", "title", "length"])
  228. |> Map.put("type", "Audio")
  229. |> Map.put("to", to)
  230. |> Map.put("cc", cc)
  231. |> Map.put("actor", user.ap_id),
  232. {:ok, activity} <-
  233. ActivityPub.listen(%{
  234. actor: user,
  235. to: to,
  236. object: listen_data,
  237. context: Utils.generate_context_id(),
  238. additional: %{"cc" => cc}
  239. }) do
  240. {:ok, activity}
  241. end
  242. end
  243. def post(user, %{"status" => _} = data) do
  244. with {:ok, draft} <- Pleroma.Web.CommonAPI.ActivityDraft.create(user, data) do
  245. draft.changes
  246. |> ActivityPub.create(draft.preview?)
  247. |> maybe_create_activity_expiration(draft.expires_at)
  248. end
  249. end
  250. defp maybe_create_activity_expiration({:ok, activity}, %NaiveDateTime{} = expires_at) do
  251. with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
  252. {:ok, activity}
  253. end
  254. end
  255. defp maybe_create_activity_expiration(result, _), do: result
  256. # Updates the emojis for a user based on their profile
  257. def update(user) do
  258. emoji = emoji_from_profile(user)
  259. source_data = Map.put(user.source_data, "tag", emoji)
  260. user =
  261. case User.update_source_data(user, source_data) do
  262. {:ok, user} -> user
  263. _ -> user
  264. end
  265. ActivityPub.update(%{
  266. local: true,
  267. to: [Pleroma.Constants.as_public(), user.follower_address],
  268. cc: [],
  269. actor: user.ap_id,
  270. object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
  271. })
  272. end
  273. def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
  274. with %Activity{
  275. actor: ^user_ap_id,
  276. data: %{"type" => "Create"},
  277. object: %Object{data: %{"type" => object_type}}
  278. } = activity <- get_by_id_or_ap_id(id_or_ap_id),
  279. true <- object_type in ["Note", "Article", "Question"],
  280. true <- Visibility.is_public?(activity),
  281. {:ok, _user} <- User.add_pinnned_activity(user, activity) do
  282. {:ok, activity}
  283. else
  284. {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
  285. _ -> {:error, dgettext("errors", "Could not pin")}
  286. end
  287. end
  288. def unpin(id_or_ap_id, user) do
  289. with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
  290. {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
  291. {:ok, activity}
  292. else
  293. {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
  294. _ -> {:error, dgettext("errors", "Could not unpin")}
  295. end
  296. end
  297. def add_mute(user, activity) do
  298. with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
  299. {:ok, activity}
  300. else
  301. {:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
  302. end
  303. end
  304. def remove_mute(user, activity) do
  305. ThreadMute.remove_mute(user.id, activity.data["context"])
  306. {:ok, activity}
  307. end
  308. def thread_muted?(%{id: nil} = _user, _activity), do: false
  309. def thread_muted?(user, activity) do
  310. ThreadMute.check_muted(user.id, activity.data["context"]) != []
  311. end
  312. def report(user, %{"account_id" => account_id} = data) do
  313. with {:ok, account} <- get_reported_account(account_id),
  314. {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),
  315. {:ok, statuses} <- get_report_statuses(account, data) do
  316. ActivityPub.flag(%{
  317. context: Utils.generate_context_id(),
  318. actor: user,
  319. account: account,
  320. statuses: statuses,
  321. content: content_html,
  322. forward: data["forward"] || false
  323. })
  324. end
  325. end
  326. def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")}
  327. defp get_reported_account(account_id) do
  328. case User.get_cached_by_id(account_id) do
  329. %User{} = account -> {:ok, account}
  330. _ -> {:error, dgettext("errors", "Account not found")}
  331. end
  332. end
  333. def update_report_state(activity_ids, state) when is_list(activity_ids) do
  334. case Utils.update_report_state(activity_ids, state) do
  335. :ok -> {:ok, activity_ids}
  336. _ -> {:error, dgettext("errors", "Could not update state")}
  337. end
  338. end
  339. def update_report_state(activity_id, state) do
  340. with %Activity{} = activity <- Activity.get_by_id(activity_id) do
  341. Utils.update_report_state(activity, state)
  342. else
  343. nil -> {:error, :not_found}
  344. _ -> {:error, dgettext("errors", "Could not update state")}
  345. end
  346. end
  347. def update_activity_scope(activity_id, opts \\ %{}) do
  348. with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
  349. {:ok, activity} <- toggle_sensitive(activity, opts) do
  350. set_visibility(activity, opts)
  351. else
  352. nil -> {:error, :not_found}
  353. {:error, reason} -> {:error, reason}
  354. end
  355. end
  356. defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do
  357. toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)})
  358. end
  359. defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive})
  360. when is_boolean(sensitive) do
  361. new_data = Map.put(object.data, "sensitive", sensitive)
  362. {:ok, object} =
  363. object
  364. |> Object.change(%{data: new_data})
  365. |> Object.update_and_set_cache()
  366. {:ok, Map.put(activity, :object, object)}
  367. end
  368. defp toggle_sensitive(activity, _), do: {:ok, activity}
  369. defp set_visibility(activity, %{"visibility" => visibility}) do
  370. Utils.update_activity_visibility(activity, visibility)
  371. end
  372. defp set_visibility(activity, _), do: {:ok, activity}
  373. def hide_reblogs(%User{} = user, %User{} = target) do
  374. UserRelationship.create_reblog_mute(user, target)
  375. end
  376. def show_reblogs(%User{} = user, %User{} = target) do
  377. UserRelationship.delete_reblog_mute(user, target)
  378. end
  379. end