logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

common_api.ex (22263B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 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.Conversation.Participation
  7. alias Pleroma.Formatter
  8. alias Pleroma.ModerationLog
  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.Builder
  15. alias Pleroma.Web.ActivityPub.Pipeline
  16. alias Pleroma.Web.ActivityPub.Utils
  17. alias Pleroma.Web.ActivityPub.Visibility
  18. alias Pleroma.Web.CommonAPI.ActivityDraft
  19. import Pleroma.Web.Gettext
  20. import Pleroma.Web.CommonAPI.Utils
  21. require Pleroma.Constants
  22. require Logger
  23. def block(blocker, blocked) do
  24. with {:ok, block_data, _} <- Builder.block(blocker, blocked),
  25. {:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
  26. {:ok, block}
  27. end
  28. end
  29. def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
  30. with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
  31. :ok <- validate_chat_attachment_attribution(maybe_attachment, user),
  32. :ok <- validate_chat_content_length(content, !!maybe_attachment),
  33. {_, {:ok, chat_message_data, _meta}} <-
  34. {:build_object,
  35. Builder.chat_message(
  36. user,
  37. recipient.ap_id,
  38. content |> format_chat_content,
  39. attachment: maybe_attachment
  40. )},
  41. {_, {:ok, create_activity_data, _meta}} <-
  42. {:build_create_activity, Builder.create(user, chat_message_data, [recipient.ap_id])},
  43. {_, {:ok, %Activity{} = activity, _meta}} <-
  44. {:common_pipeline,
  45. Pipeline.common_pipeline(create_activity_data,
  46. local: true,
  47. idempotency_key: opts[:idempotency_key]
  48. )} do
  49. {:ok, activity}
  50. else
  51. {:common_pipeline, {:reject, _} = e} -> e
  52. e -> e
  53. end
  54. end
  55. defp format_chat_content(nil), do: nil
  56. defp format_chat_content(content) do
  57. {text, _, _} =
  58. content
  59. |> Formatter.html_escape("text/plain")
  60. |> Formatter.linkify()
  61. |> (fn {text, mentions, tags} ->
  62. {String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
  63. end).()
  64. text
  65. end
  66. defp validate_chat_attachment_attribution(nil, _), do: :ok
  67. defp validate_chat_attachment_attribution(attachment, user) do
  68. with :ok <- Object.authorize_access(attachment, user) do
  69. :ok
  70. else
  71. e ->
  72. e
  73. end
  74. end
  75. defp validate_chat_content_length(_, true), do: :ok
  76. defp validate_chat_content_length(nil, false), do: {:error, :no_content}
  77. defp validate_chat_content_length(content, _) do
  78. if String.length(content) <= Pleroma.Config.get([:instance, :chat_limit]) do
  79. :ok
  80. else
  81. {:error, :content_too_long}
  82. end
  83. end
  84. def unblock(blocker, blocked) do
  85. with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
  86. {:ok, unblock_data, _} <- Builder.undo(blocker, block),
  87. {:ok, unblock, _} <- Pipeline.common_pipeline(unblock_data, local: true) do
  88. {:ok, unblock}
  89. else
  90. {:fetch_block, nil} ->
  91. if User.blocks?(blocker, blocked) do
  92. User.unblock(blocker, blocked)
  93. {:ok, :no_activity}
  94. else
  95. {:error, :not_blocking}
  96. end
  97. e ->
  98. e
  99. end
  100. end
  101. def follow(follower, followed) do
  102. timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
  103. with {:ok, follow_data, _} <- Builder.follow(follower, followed),
  104. {:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
  105. {:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
  106. if activity.data["state"] == "reject" do
  107. {:error, :rejected}
  108. else
  109. {:ok, follower, followed, activity}
  110. end
  111. end
  112. end
  113. def unfollow(follower, unfollowed) do
  114. with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
  115. {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
  116. {:ok, _subscription} <- User.unsubscribe(follower, unfollowed),
  117. {:ok, _endorsement} <- User.unendorse(follower, unfollowed) do
  118. {:ok, follower}
  119. end
  120. end
  121. def accept_follow_request(follower, followed) do
  122. with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
  123. {:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
  124. {:ok, _activity, _} <- Pipeline.common_pipeline(accept_data, local: true) do
  125. {:ok, follower}
  126. end
  127. end
  128. def reject_follow_request(follower, followed) do
  129. with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
  130. {:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
  131. {:ok, _activity, _} <- Pipeline.common_pipeline(reject_data, local: true) do
  132. {:ok, follower}
  133. end
  134. end
  135. def delete(activity_id, user) do
  136. with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
  137. {:find_activity, Activity.get_by_id(activity_id, filter: [])},
  138. {_, %Object{} = object, _} <-
  139. {:find_object, Object.normalize(activity, fetch: false), activity},
  140. true <- User.privileged?(user, :messages_delete) || user.ap_id == object.data["actor"],
  141. {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
  142. {:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
  143. if User.privileged?(user, :messages_delete) and user.ap_id != object.data["actor"] do
  144. action =
  145. if object.data["type"] == "ChatMessage" do
  146. "chat_message_delete"
  147. else
  148. "status_delete"
  149. end
  150. ModerationLog.insert_log(%{
  151. action: action,
  152. actor: user,
  153. subject_id: activity_id
  154. })
  155. end
  156. {:ok, delete}
  157. else
  158. {:find_activity, _} ->
  159. {:error, :not_found}
  160. {:find_object, nil, %Activity{data: %{"actor" => actor, "object" => object}}} ->
  161. # We have the create activity, but not the object, it was probably pruned.
  162. # Insert a tombstone and try again
  163. with {:ok, tombstone_data, _} <- Builder.tombstone(actor, object),
  164. {:ok, _tombstone} <- Object.create(tombstone_data) do
  165. delete(activity_id, user)
  166. else
  167. _ ->
  168. Logger.error(
  169. "Could not insert tombstone for missing object on deletion. Object is #{object}."
  170. )
  171. {:error, dgettext("errors", "Could not delete")}
  172. end
  173. _ ->
  174. {:error, dgettext("errors", "Could not delete")}
  175. end
  176. end
  177. def repeat(id, user, params \\ %{}) do
  178. with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
  179. object = %Object{} <- Object.normalize(activity, fetch: false),
  180. {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
  181. public = public_announce?(object, params),
  182. {:ok, announce, _} <- Builder.announce(user, object, public: public),
  183. {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
  184. {:ok, activity}
  185. else
  186. {:existing_announce, %Activity{} = announce} ->
  187. {:ok, announce}
  188. _ ->
  189. {:error, :not_found}
  190. end
  191. end
  192. def unrepeat(id, user) do
  193. with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
  194. {:find_activity, Activity.get_by_id(id)},
  195. %Object{} = note <- Object.normalize(activity, fetch: false),
  196. %Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note),
  197. {:ok, undo, _} <- Builder.undo(user, announce),
  198. {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
  199. {:ok, activity}
  200. else
  201. {:find_activity, _} -> {:error, :not_found}
  202. _ -> {:error, dgettext("errors", "Could not unrepeat")}
  203. end
  204. end
  205. @spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()}
  206. def favorite(%User{} = user, id) do
  207. case favorite_helper(user, id) do
  208. {:ok, _} = res ->
  209. res
  210. {:error, :not_found} = res ->
  211. res
  212. {:error, e} ->
  213. Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}")
  214. {:error, dgettext("errors", "Could not favorite")}
  215. end
  216. end
  217. def favorite_helper(user, id) do
  218. with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
  219. {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
  220. {_, {:ok, %Activity{} = activity, _meta}} <-
  221. {:common_pipeline,
  222. Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
  223. {:ok, activity}
  224. else
  225. {:find_object, _} ->
  226. {:error, :not_found}
  227. {:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
  228. if {:object, {"already liked by this actor", []}} in changeset.errors do
  229. {:ok, :already_liked}
  230. else
  231. {:error, e}
  232. end
  233. e ->
  234. {:error, e}
  235. end
  236. end
  237. def unfavorite(id, user) do
  238. with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
  239. {:find_activity, Activity.get_by_id(id)},
  240. %Object{} = note <- Object.normalize(activity, fetch: false),
  241. %Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
  242. {:ok, undo, _} <- Builder.undo(user, like),
  243. {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
  244. {:ok, activity}
  245. else
  246. {:find_activity, _} -> {:error, :not_found}
  247. _ -> {:error, dgettext("errors", "Could not unfavorite")}
  248. end
  249. end
  250. def react_with_emoji(id, user, emoji) do
  251. with %Activity{} = activity <- Activity.get_by_id(id),
  252. object <- Object.normalize(activity, fetch: false),
  253. {:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
  254. {:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
  255. {:ok, activity}
  256. else
  257. _ ->
  258. {:error, dgettext("errors", "Could not add reaction emoji")}
  259. end
  260. end
  261. def unreact_with_emoji(id, user, emoji) do
  262. with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
  263. {:ok, undo, _} <- Builder.undo(user, reaction_activity),
  264. {:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
  265. {:ok, activity}
  266. else
  267. _ ->
  268. {:error, dgettext("errors", "Could not remove reaction emoji")}
  269. end
  270. end
  271. def vote(user, %{data: %{"type" => "Question"}} = object, choices) do
  272. with :ok <- validate_not_author(object, user),
  273. :ok <- validate_existing_votes(user, object),
  274. {:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
  275. answer_activities =
  276. Enum.map(choices, fn index ->
  277. {:ok, answer_object, _meta} =
  278. Builder.answer(user, object, Enum.at(options, index)["name"])
  279. {:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
  280. {:ok, activity, _meta} =
  281. activity_data
  282. |> Map.put("cc", answer_object["cc"])
  283. |> Map.put("context", answer_object["context"])
  284. |> Pipeline.common_pipeline(local: true)
  285. # TODO: Do preload of Pleroma.Object in Pipeline
  286. Activity.normalize(activity.data)
  287. end)
  288. object = Object.get_cached_by_ap_id(object.data["id"])
  289. {:ok, answer_activities, object}
  290. end
  291. end
  292. defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}),
  293. do: {:error, dgettext("errors", "Poll's author can't vote")}
  294. defp validate_not_author(_, _), do: :ok
  295. defp validate_existing_votes(%{ap_id: ap_id}, object) do
  296. if Utils.get_existing_votes(ap_id, object) == [] do
  297. :ok
  298. else
  299. {:error, dgettext("errors", "Already voted")}
  300. end
  301. end
  302. defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
  303. when is_list(any_of) and any_of != [],
  304. do: {any_of, Enum.count(any_of)}
  305. defp get_options_and_max_count(%{data: %{"oneOf" => one_of}})
  306. when is_list(one_of) and one_of != [],
  307. do: {one_of, 1}
  308. defp normalize_and_validate_choices(choices, object) do
  309. choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
  310. {options, max_count} = get_options_and_max_count(object)
  311. count = Enum.count(options)
  312. with {_, true} <- {:valid_choice, Enum.all?(choices, &(&1 < count))},
  313. {_, true} <- {:count_check, Enum.count(choices) <= max_count} do
  314. {:ok, options, choices}
  315. else
  316. {:valid_choice, _} -> {:error, dgettext("errors", "Invalid indices")}
  317. {:count_check, _} -> {:error, dgettext("errors", "Too many choices")}
  318. end
  319. end
  320. def public_announce?(_, %{visibility: visibility})
  321. when visibility in ~w{public unlisted private direct},
  322. do: visibility in ~w(public unlisted)
  323. def public_announce?(object, _) do
  324. Visibility.public?(object)
  325. end
  326. def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
  327. def get_visibility(%{visibility: visibility}, in_reply_to, _)
  328. when visibility in ~w{public local unlisted private direct},
  329. do: {visibility, get_replied_to_visibility(in_reply_to)}
  330. def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
  331. visibility = {:list, String.to_integer(list_id)}
  332. {visibility, get_replied_to_visibility(in_reply_to)}
  333. end
  334. def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do
  335. visibility = get_replied_to_visibility(in_reply_to)
  336. {visibility, visibility}
  337. end
  338. def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)}
  339. def get_replied_to_visibility(nil), do: nil
  340. def get_replied_to_visibility(activity) do
  341. with %Object{} = object <- Object.normalize(activity, fetch: false) do
  342. Visibility.get_visibility(object)
  343. end
  344. end
  345. def check_expiry_date({:ok, nil} = res), do: res
  346. def check_expiry_date({:ok, in_seconds}) do
  347. expiry = DateTime.add(DateTime.utc_now(), in_seconds)
  348. if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
  349. {:ok, expiry}
  350. else
  351. {:error, "Expiry date is too soon"}
  352. end
  353. end
  354. def check_expiry_date(expiry_str) do
  355. Ecto.Type.cast(:integer, expiry_str)
  356. |> check_expiry_date()
  357. end
  358. def listen(user, data) do
  359. with {:ok, draft} <- ActivityDraft.listen(user, data) do
  360. ActivityPub.listen(draft.changes)
  361. end
  362. end
  363. def post(user, %{status: _} = data) do
  364. with {:ok, draft} <- ActivityDraft.create(user, data) do
  365. ActivityPub.create(draft.changes, draft.preview?)
  366. end
  367. end
  368. def update(user, orig_activity, changes) do
  369. with orig_object <- Object.normalize(orig_activity),
  370. {:ok, new_object} <- make_update_data(user, orig_object, changes),
  371. {:ok, update_data, _} <- Builder.update(user, new_object),
  372. {:ok, update, _} <- Pipeline.common_pipeline(update_data, local: true) do
  373. {:ok, update}
  374. else
  375. _ -> {:error, nil}
  376. end
  377. end
  378. defp make_update_data(user, orig_object, changes) do
  379. kept_params = %{
  380. visibility: Visibility.get_visibility(orig_object),
  381. in_reply_to_id:
  382. with replied_id when is_binary(replied_id) <- orig_object.data["inReplyTo"],
  383. %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(replied_id) do
  384. activity_id
  385. else
  386. _ -> nil
  387. end
  388. }
  389. params = Map.merge(changes, kept_params)
  390. with {:ok, draft} <- ActivityDraft.create(user, params) do
  391. change =
  392. Object.Updater.make_update_object_data(orig_object.data, draft.object, Utils.make_date())
  393. {:ok, change}
  394. else
  395. _ -> {:error, nil}
  396. end
  397. end
  398. @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
  399. def pin(id, %User{} = user) do
  400. with %Activity{} = activity <- create_activity_by_id(id),
  401. true <- activity_belongs_to_actor(activity, user.ap_id),
  402. true <- object_type_is_allowed_for_pin(activity.object),
  403. true <- activity_is_public(activity),
  404. {:ok, pin_data, _} <- Builder.pin(user, activity.object),
  405. {:ok, _pin, _} <-
  406. Pipeline.common_pipeline(pin_data,
  407. local: true,
  408. activity_id: id
  409. ) do
  410. {:ok, activity}
  411. else
  412. {:error, {:side_effects, error}} -> error
  413. error -> error
  414. end
  415. end
  416. defp create_activity_by_id(id) do
  417. with nil <- Activity.create_by_id_with_object(id) do
  418. {:error, :not_found}
  419. end
  420. end
  421. defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
  422. defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
  423. defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
  424. with false <- type in ["Note", "Article", "Question"] do
  425. {:error, :not_allowed}
  426. end
  427. end
  428. defp activity_is_public(activity) do
  429. with false <- Visibility.public?(activity) do
  430. {:error, :visibility_error}
  431. end
  432. end
  433. @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
  434. def unpin(id, user) do
  435. with %Activity{} = activity <- create_activity_by_id(id),
  436. {:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
  437. {:ok, _unpin, _} <-
  438. Pipeline.common_pipeline(unpin_data,
  439. local: true,
  440. activity_id: activity.id,
  441. expires_at: activity.data["expires_at"],
  442. featured_address: user.featured_address
  443. ) do
  444. {:ok, activity}
  445. end
  446. end
  447. def add_mute(user, activity, params \\ %{}) do
  448. expires_in = Map.get(params, :expires_in, 0)
  449. with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
  450. _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
  451. if expires_in > 0 do
  452. Pleroma.Workers.MuteExpireWorker.enqueue(
  453. "unmute_conversation",
  454. %{"user_id" => user.id, "activity_id" => activity.id},
  455. schedule_in: expires_in
  456. )
  457. end
  458. {:ok, activity}
  459. else
  460. {:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
  461. end
  462. end
  463. def remove_mute(%User{} = user, %Activity{} = activity) do
  464. ThreadMute.remove_mute(user.id, activity.data["context"])
  465. {:ok, activity}
  466. end
  467. def remove_mute(user_id, activity_id) do
  468. with {:user, %User{} = user} <- {:user, User.get_by_id(user_id)},
  469. {:activity, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)} do
  470. remove_mute(user, activity)
  471. else
  472. {what, result} = error ->
  473. Logger.warning(
  474. "CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
  475. )
  476. {:error, error}
  477. end
  478. end
  479. def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
  480. when is_binary(context) do
  481. ThreadMute.exists?(user_id, context)
  482. end
  483. def thread_muted?(_, _), do: false
  484. def report(user, data) do
  485. with {:ok, account} <- get_reported_account(data.account_id),
  486. {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
  487. {:ok, statuses} <- get_report_statuses(account, data) do
  488. ActivityPub.flag(%{
  489. context: Utils.generate_context_id(),
  490. actor: user,
  491. account: account,
  492. statuses: statuses,
  493. content: content_html,
  494. forward: Map.get(data, :forward, false)
  495. })
  496. end
  497. end
  498. defp get_reported_account(account_id) do
  499. case User.get_cached_by_id(account_id) do
  500. %User{} = account -> {:ok, account}
  501. _ -> {:error, dgettext("errors", "Account not found")}
  502. end
  503. end
  504. def update_report_state(activity_ids, state) when is_list(activity_ids) do
  505. case Utils.update_report_state(activity_ids, state) do
  506. :ok -> {:ok, activity_ids}
  507. _ -> {:error, dgettext("errors", "Could not update state")}
  508. end
  509. end
  510. def update_report_state(activity_id, state) do
  511. with %Activity{} = activity <- Activity.get_by_id(activity_id, filter: []) do
  512. Utils.update_report_state(activity, state)
  513. else
  514. nil -> {:error, :not_found}
  515. _ -> {:error, dgettext("errors", "Could not update state")}
  516. end
  517. end
  518. def update_activity_scope(activity_id, opts \\ %{}) do
  519. with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
  520. {:ok, activity} <- toggle_sensitive(activity, opts) do
  521. set_visibility(activity, opts)
  522. else
  523. nil -> {:error, :not_found}
  524. {:error, reason} -> {:error, reason}
  525. end
  526. end
  527. defp toggle_sensitive(activity, %{sensitive: sensitive}) when sensitive in ~w(true false) do
  528. toggle_sensitive(activity, %{sensitive: String.to_existing_atom(sensitive)})
  529. end
  530. defp toggle_sensitive(%Activity{object: object} = activity, %{sensitive: sensitive})
  531. when is_boolean(sensitive) do
  532. new_data = Map.put(object.data, "sensitive", sensitive)
  533. {:ok, object} =
  534. object
  535. |> Object.change(%{data: new_data})
  536. |> Object.update_and_set_cache()
  537. {:ok, Map.put(activity, :object, object)}
  538. end
  539. defp toggle_sensitive(activity, _), do: {:ok, activity}
  540. defp set_visibility(activity, %{visibility: visibility}) do
  541. Utils.update_activity_visibility(activity, visibility)
  542. end
  543. defp set_visibility(activity, _), do: {:ok, activity}
  544. def hide_reblogs(%User{} = user, %User{} = target) do
  545. UserRelationship.create_reblog_mute(user, target)
  546. end
  547. def show_reblogs(%User{} = user, %User{} = target) do
  548. UserRelationship.delete_reblog_mute(user, target)
  549. end
  550. def get_user(ap_id, fake_record_fallback \\ true) do
  551. cond do
  552. user = User.get_cached_by_ap_id(ap_id) ->
  553. user
  554. user = User.get_by_guessed_nickname(ap_id) ->
  555. user
  556. fake_record_fallback ->
  557. # TODO: refactor (fake records is never a good idea)
  558. User.error_user(ap_id)
  559. true ->
  560. nil
  561. end
  562. end
  563. end