98cb255d12
OTP builds to 1.15 Changelog entry Ensure policies are fully loaded Fix :warn use main branch for linkify Fix warn in tests Migrations for phoenix 1.17 Revert "Migrations for phoenix 1.17" This reverts commit 6a3b2f15b74ea5e33150529385215b7a531f3999. Oban upgrade Add default empty whitelist mix format limit test to amd64 OTP 26 tests for 1.15 use OTP_VERSION tag baka just 1.15 Massive deps update Update locale, deps Mix format shell???? multiline??? ? max cases 1 use assert_recieve don't put_env in async tests don't async conn/fs tests mix format FIx some uploader issues Fix tests
581 lines
19 KiB
Elixir
581 lines
19 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Web.CommonAPI do
|
|
alias Pleroma.Activity
|
|
alias Pleroma.Conversation.Participation
|
|
alias Pleroma.Object
|
|
alias Pleroma.ThreadMute
|
|
alias Pleroma.User
|
|
alias Pleroma.UserRelationship
|
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
alias Pleroma.Web.ActivityPub.Builder
|
|
alias Pleroma.Web.ActivityPub.Pipeline
|
|
alias Pleroma.Web.ActivityPub.Utils
|
|
alias Pleroma.Web.ActivityPub.Visibility
|
|
alias Pleroma.Web.CommonAPI.ActivityDraft
|
|
|
|
import Pleroma.Web.Gettext
|
|
import Pleroma.Web.CommonAPI.Utils
|
|
|
|
require Pleroma.Constants
|
|
require Logger
|
|
|
|
def block(blocker, blocked) do
|
|
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
|
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
|
{:ok, block}
|
|
end
|
|
end
|
|
|
|
def unblock(blocker, blocked) do
|
|
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
|
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
|
{:ok, unblock, _} <- Pipeline.common_pipeline(unblock_data, local: true) do
|
|
{:ok, unblock}
|
|
else
|
|
{:fetch_block, nil} ->
|
|
if User.blocks?(blocker, blocked) do
|
|
User.unblock(blocker, blocked)
|
|
{:ok, :no_activity}
|
|
else
|
|
{:error, :not_blocking}
|
|
end
|
|
|
|
e ->
|
|
e
|
|
end
|
|
end
|
|
|
|
def follow(follower, followed) do
|
|
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
|
|
|
with {:ok, follow_data, _} <- Builder.follow(follower, followed),
|
|
{:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
|
|
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
|
|
if activity.data["state"] == "reject" do
|
|
{:error, :rejected}
|
|
else
|
|
{:ok, follower, followed, activity}
|
|
end
|
|
end
|
|
end
|
|
|
|
def unfollow(follower, unfollowed) do
|
|
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
|
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
|
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
|
|
{:ok, follower}
|
|
end
|
|
end
|
|
|
|
def accept_follow_request(follower, followed) do
|
|
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
|
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
|
|
{:ok, _activity, _} <- Pipeline.common_pipeline(accept_data, local: true) do
|
|
{:ok, follower}
|
|
end
|
|
end
|
|
|
|
def reject_follow_request(follower, followed) do
|
|
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
|
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
|
|
{:ok, _activity, _} <- Pipeline.common_pipeline(reject_data, local: true) do
|
|
{:ok, follower}
|
|
end
|
|
end
|
|
|
|
def delete(activity_id, user) do
|
|
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
|
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
|
{_, %Object{} = object, _} <-
|
|
{:find_object, Object.normalize(activity, fetch: false), activity},
|
|
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
|
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
|
|
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
|
|
{:ok, delete}
|
|
else
|
|
{:find_activity, _} ->
|
|
{:error, :not_found}
|
|
|
|
{:find_object, nil, %Activity{data: %{"actor" => actor, "object" => object}}} ->
|
|
# We have the create activity, but not the object, it was probably pruned.
|
|
# Insert a tombstone and try again
|
|
with {:ok, tombstone_data, _} <- Builder.tombstone(actor, object),
|
|
{:ok, _tombstone} <- Object.create(tombstone_data) do
|
|
delete(activity_id, user)
|
|
else
|
|
_ ->
|
|
Logger.error(
|
|
"Could not insert tombstone for missing object on deletion. Object is #{object}."
|
|
)
|
|
|
|
{:error, dgettext("errors", "Could not delete")}
|
|
end
|
|
|
|
_ ->
|
|
{:error, dgettext("errors", "Could not delete")}
|
|
end
|
|
end
|
|
|
|
def repeat(id, user, params \\ %{}) do
|
|
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
|
object = %Object{} <- Object.normalize(activity, fetch: false),
|
|
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
|
|
public = public_announce?(object, params),
|
|
{:ok, announce, _} <- Builder.announce(user, object, public: public),
|
|
{:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
|
|
{:ok, activity}
|
|
else
|
|
{:existing_announce, %Activity{} = announce} ->
|
|
{:ok, announce}
|
|
|
|
_ ->
|
|
{:error, :not_found}
|
|
end
|
|
end
|
|
|
|
def unrepeat(id, user) do
|
|
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
|
{:find_activity, Activity.get_by_id(id)},
|
|
%Object{} = note <- Object.normalize(activity, fetch: false),
|
|
%Activity{} = announce <- Utils.get_existing_announce(user.ap_id, note),
|
|
{:ok, undo, _} <- Builder.undo(user, announce),
|
|
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
|
|
{:ok, activity}
|
|
else
|
|
{:find_activity, _} -> {:error, :not_found}
|
|
_ -> {:error, dgettext("errors", "Could not unrepeat")}
|
|
end
|
|
end
|
|
|
|
@spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()}
|
|
def favorite(%User{} = user, id) do
|
|
case favorite_helper(user, id) do
|
|
{:ok, _} = res ->
|
|
res
|
|
|
|
{:error, :not_found} = res ->
|
|
res
|
|
|
|
{:error, e} ->
|
|
Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}")
|
|
{:error, dgettext("errors", "Could not favorite")}
|
|
end
|
|
end
|
|
|
|
def favorite_helper(user, id) do
|
|
with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
|
|
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
|
|
{_, {:ok, %Activity{} = activity, _meta}} <-
|
|
{:common_pipeline,
|
|
Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
|
|
{:ok, activity}
|
|
else
|
|
{:find_object, _} ->
|
|
{:error, :not_found}
|
|
|
|
{:common_pipeline, {:error, {:validate, {:error, changeset}}}} = e ->
|
|
if {:object, {"already liked by this actor", []}} in changeset.errors do
|
|
{:ok, :already_liked}
|
|
else
|
|
{:error, e}
|
|
end
|
|
|
|
e ->
|
|
{:error, e}
|
|
end
|
|
end
|
|
|
|
def unfavorite(id, user) do
|
|
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
|
{:find_activity, Activity.get_by_id(id)},
|
|
%Object{} = note <- Object.normalize(activity, fetch: false),
|
|
%Activity{} = like <- Utils.get_existing_like(user.ap_id, note),
|
|
{:ok, undo, _} <- Builder.undo(user, like),
|
|
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
|
|
{:ok, activity}
|
|
else
|
|
{:find_activity, _} -> {:error, :not_found}
|
|
_ -> {:error, dgettext("errors", "Could not unfavorite")}
|
|
end
|
|
end
|
|
|
|
def react_with_emoji(id, user, emoji) do
|
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
|
object <- Object.normalize(activity, fetch: false),
|
|
{:ok, emoji_react, _} <- Builder.emoji_react(user, object, emoji),
|
|
{:ok, activity, _} <- Pipeline.common_pipeline(emoji_react, local: true) do
|
|
{:ok, activity}
|
|
else
|
|
_ ->
|
|
{:error, dgettext("errors", "Could not add reaction emoji")}
|
|
end
|
|
end
|
|
|
|
def unreact_with_emoji(id, user, emoji) do
|
|
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
|
|
{:ok, undo, _} <- Builder.undo(user, reaction_activity),
|
|
{:ok, activity, _} <- Pipeline.common_pipeline(undo, local: true) do
|
|
{:ok, activity}
|
|
else
|
|
_ ->
|
|
{:error, dgettext("errors", "Could not remove reaction emoji")}
|
|
end
|
|
end
|
|
|
|
def vote(user, %{data: %{"type" => "Question"}} = object, choices) do
|
|
with :ok <- validate_not_author(object, user),
|
|
:ok <- validate_existing_votes(user, object),
|
|
{:ok, options, choices} <- normalize_and_validate_choices(choices, object) do
|
|
answer_activities =
|
|
Enum.map(choices, fn index ->
|
|
{:ok, answer_object, _meta} =
|
|
Builder.answer(user, object, Enum.at(options, index)["name"])
|
|
|
|
{:ok, activity_data, _meta} = Builder.create(user, answer_object, [])
|
|
|
|
{:ok, activity, _meta} =
|
|
activity_data
|
|
|> Map.put("cc", answer_object["cc"])
|
|
|> Map.put("context", answer_object["context"])
|
|
|> Pipeline.common_pipeline(local: true)
|
|
|
|
# TODO: Do preload of Pleroma.Object in Pipeline
|
|
Activity.normalize(activity.data)
|
|
end)
|
|
|
|
object = Object.get_cached_by_ap_id(object.data["id"])
|
|
{:ok, answer_activities, object}
|
|
end
|
|
end
|
|
|
|
defp validate_not_author(%{data: %{"actor" => ap_id}}, %{ap_id: ap_id}),
|
|
do: {:error, dgettext("errors", "Poll's author can't vote")}
|
|
|
|
defp validate_not_author(_, _), do: :ok
|
|
|
|
defp validate_existing_votes(%{ap_id: ap_id}, object) do
|
|
if Utils.get_existing_votes(ap_id, object) == [] do
|
|
:ok
|
|
else
|
|
{:error, dgettext("errors", "Already voted")}
|
|
end
|
|
end
|
|
|
|
defp get_options_and_max_count(%{data: %{"anyOf" => any_of}})
|
|
when is_list(any_of) and any_of != [],
|
|
do: {any_of, Enum.count(any_of)}
|
|
|
|
defp get_options_and_max_count(%{data: %{"oneOf" => one_of}})
|
|
when is_list(one_of) and one_of != [],
|
|
do: {one_of, 1}
|
|
|
|
defp normalize_and_validate_choices(choices, object) do
|
|
choices = Enum.map(choices, fn i -> if is_binary(i), do: String.to_integer(i), else: i end)
|
|
{options, max_count} = get_options_and_max_count(object)
|
|
count = Enum.count(options)
|
|
|
|
with {_, true} <- {:valid_choice, Enum.all?(choices, &(&1 < count))},
|
|
{_, true} <- {:count_check, Enum.count(choices) <= max_count} do
|
|
{:ok, options, choices}
|
|
else
|
|
{:valid_choice, _} -> {:error, dgettext("errors", "Invalid indices")}
|
|
{:count_check, _} -> {:error, dgettext("errors", "Too many choices")}
|
|
end
|
|
end
|
|
|
|
def public_announce?(_, %{visibility: visibility})
|
|
when visibility in ~w{public unlisted private direct},
|
|
do: visibility in ~w(public unlisted)
|
|
|
|
def public_announce?(object, _) do
|
|
Visibility.is_public?(object)
|
|
end
|
|
|
|
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
|
|
|
|
def get_visibility(%{visibility: visibility}, in_reply_to, _)
|
|
when visibility in ~w{public local unlisted private direct},
|
|
do: {visibility, get_replied_to_visibility(in_reply_to)}
|
|
|
|
def get_visibility(%{visibility: "list:" <> list_id}, in_reply_to, _) do
|
|
visibility = {:list, String.to_integer(list_id)}
|
|
{visibility, get_replied_to_visibility(in_reply_to)}
|
|
end
|
|
|
|
def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do
|
|
visibility = get_replied_to_visibility(in_reply_to)
|
|
{visibility, visibility}
|
|
end
|
|
|
|
def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)}
|
|
|
|
def get_replied_to_visibility(nil), do: nil
|
|
|
|
def get_replied_to_visibility(activity) do
|
|
with %Object{} = object <- Object.normalize(activity, fetch: false) do
|
|
Visibility.get_visibility(object)
|
|
end
|
|
end
|
|
|
|
def get_quoted_visibility(nil), do: nil
|
|
|
|
def get_quoted_visibility(activity), do: get_replied_to_visibility(activity)
|
|
|
|
def check_expiry_date({:ok, nil} = res), do: res
|
|
|
|
def check_expiry_date({:ok, in_seconds}) do
|
|
expiry = DateTime.add(DateTime.utc_now(), in_seconds)
|
|
|
|
if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
|
|
{:ok, expiry}
|
|
else
|
|
{:error, "Expiry date is too soon"}
|
|
end
|
|
end
|
|
|
|
def check_expiry_date(expiry_str) do
|
|
Ecto.Type.cast(:integer, expiry_str)
|
|
|> check_expiry_date()
|
|
end
|
|
|
|
def post(user, %{status: _} = data) do
|
|
with {:ok, draft} <- ActivityDraft.create(user, data) do
|
|
ActivityPub.create(draft.changes, draft.preview?)
|
|
end
|
|
end
|
|
|
|
def update(user, orig_activity, changes) do
|
|
with orig_object <- Object.normalize(orig_activity),
|
|
{:ok, new_object} <- make_update_data(user, orig_object, changes),
|
|
{:ok, update_data, _} <- Builder.update(user, new_object),
|
|
{:ok, update, _} <- Pipeline.common_pipeline(update_data, local: true) do
|
|
{:ok, update}
|
|
else
|
|
_ -> {:error, nil}
|
|
end
|
|
end
|
|
|
|
defp make_update_data(user, orig_object, changes) do
|
|
kept_params = %{
|
|
visibility: Visibility.get_visibility(orig_object),
|
|
in_reply_to_id:
|
|
with replied_id when is_binary(replied_id) <- orig_object.data["inReplyTo"],
|
|
%Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(replied_id) do
|
|
activity_id
|
|
else
|
|
_ -> nil
|
|
end
|
|
}
|
|
|
|
params = Map.merge(changes, kept_params)
|
|
|
|
with {:ok, draft} <- ActivityDraft.create(user, params) do
|
|
change =
|
|
Object.Updater.make_update_object_data(orig_object.data, draft.object, Utils.make_date())
|
|
|
|
{:ok, change}
|
|
else
|
|
_ -> {:error, nil}
|
|
end
|
|
end
|
|
|
|
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
|
def pin(id, %User{} = user) do
|
|
with %Activity{} = activity <- create_activity_by_id(id),
|
|
true <- activity_belongs_to_actor(activity, user.ap_id),
|
|
true <- object_type_is_allowed_for_pin(activity.object),
|
|
true <- activity_is_public(activity),
|
|
{:ok, pin_data, _} <- Builder.pin(user, activity.object),
|
|
{:ok, _pin, _} <-
|
|
Pipeline.common_pipeline(pin_data,
|
|
local: true,
|
|
activity_id: id
|
|
) do
|
|
{:ok, activity}
|
|
else
|
|
{:error, {:side_effects, error}} -> error
|
|
error -> error
|
|
end
|
|
end
|
|
|
|
defp create_activity_by_id(id) do
|
|
with nil <- Activity.create_by_id_with_object(id) do
|
|
{:error, :not_found}
|
|
end
|
|
end
|
|
|
|
defp activity_belongs_to_actor(%{actor: actor}, actor), do: true
|
|
defp activity_belongs_to_actor(_, _), do: {:error, :ownership_error}
|
|
|
|
defp object_type_is_allowed_for_pin(%{data: %{"type" => type}}) do
|
|
with false <- type in ["Note", "Article", "Question"] do
|
|
{:error, :not_allowed}
|
|
end
|
|
end
|
|
|
|
defp activity_is_public(activity) do
|
|
with false <- Visibility.is_public?(activity) do
|
|
{:error, :visibility_error}
|
|
end
|
|
end
|
|
|
|
@spec unpin(String.t(), User.t()) :: {:ok, User.t()} | {:error, term()}
|
|
def unpin(id, user) do
|
|
with %Activity{} = activity <- create_activity_by_id(id),
|
|
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
|
{:ok, _unpin, _} <-
|
|
Pipeline.common_pipeline(unpin_data,
|
|
local: true,
|
|
activity_id: activity.id,
|
|
expires_at: activity.data["expires_at"],
|
|
featured_address: user.featured_address
|
|
) do
|
|
{:ok, activity}
|
|
end
|
|
end
|
|
|
|
def add_mute(user, activity, params \\ %{}) do
|
|
expires_in = Map.get(params, :expires_in, 0)
|
|
|
|
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
|
|
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
|
|
if expires_in > 0 do
|
|
Pleroma.Workers.MuteExpireWorker.enqueue(
|
|
"unmute_conversation",
|
|
%{"user_id" => user.id, "activity_id" => activity.id},
|
|
schedule_in: expires_in
|
|
)
|
|
end
|
|
|
|
{:ok, activity}
|
|
else
|
|
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
|
end
|
|
end
|
|
|
|
def remove_mute(%User{} = user, %Activity{} = activity) do
|
|
ThreadMute.remove_mute(user.id, activity.data["context"])
|
|
{:ok, activity}
|
|
end
|
|
|
|
def remove_mute(user_id, activity_id) do
|
|
with {:user, %User{} = user} <- {:user, User.get_by_id(user_id)},
|
|
{:activity, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)} do
|
|
remove_mute(user, activity)
|
|
else
|
|
{what, result} = error ->
|
|
Logger.warning(
|
|
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
|
|
)
|
|
|
|
{:error, error}
|
|
end
|
|
end
|
|
|
|
def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
|
|
when is_binary(context) do
|
|
ThreadMute.exists?(user_id, context)
|
|
end
|
|
|
|
def thread_muted?(_, _), do: false
|
|
|
|
def report(user, data) do
|
|
with {:ok, account} <- get_reported_account(data.account_id),
|
|
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
|
|
{:ok, statuses} <- get_report_statuses(account, data) do
|
|
ActivityPub.flag(%{
|
|
context: Utils.generate_context_id(),
|
|
actor: user,
|
|
account: account,
|
|
statuses: statuses,
|
|
content: content_html,
|
|
forward: Map.get(data, :forward, false)
|
|
})
|
|
end
|
|
end
|
|
|
|
defp get_reported_account(account_id) do
|
|
case User.get_cached_by_id(account_id) do
|
|
%User{} = account -> {:ok, account}
|
|
_ -> {:error, dgettext("errors", "Account not found")}
|
|
end
|
|
end
|
|
|
|
def update_report_state(activity_ids, state) when is_list(activity_ids) do
|
|
case Utils.update_report_state(activity_ids, state) do
|
|
:ok -> {:ok, activity_ids}
|
|
_ -> {:error, dgettext("errors", "Could not update state")}
|
|
end
|
|
end
|
|
|
|
def update_report_state(activity_id, state) do
|
|
with %Activity{} = activity <- Activity.get_by_id(activity_id) do
|
|
Utils.update_report_state(activity, state)
|
|
else
|
|
nil -> {:error, :not_found}
|
|
_ -> {:error, dgettext("errors", "Could not update state")}
|
|
end
|
|
end
|
|
|
|
def update_activity_scope(activity_id, opts \\ %{}) do
|
|
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
|
|
{:ok, activity} <- toggle_sensitive(activity, opts) do
|
|
set_visibility(activity, opts)
|
|
else
|
|
nil -> {:error, :not_found}
|
|
{:error, reason} -> {:error, reason}
|
|
end
|
|
end
|
|
|
|
defp toggle_sensitive(activity, %{sensitive: sensitive}) when sensitive in ~w(true false) do
|
|
toggle_sensitive(activity, %{sensitive: String.to_existing_atom(sensitive)})
|
|
end
|
|
|
|
defp toggle_sensitive(%Activity{object: object} = activity, %{sensitive: sensitive})
|
|
when is_boolean(sensitive) do
|
|
new_data = Map.put(object.data, "sensitive", sensitive)
|
|
|
|
{:ok, object} =
|
|
object
|
|
|> Object.change(%{data: new_data})
|
|
|> Object.update_and_set_cache()
|
|
|
|
{:ok, Map.put(activity, :object, object)}
|
|
end
|
|
|
|
defp toggle_sensitive(activity, _), do: {:ok, activity}
|
|
|
|
defp set_visibility(activity, %{visibility: visibility}) do
|
|
Utils.update_activity_visibility(activity, visibility)
|
|
end
|
|
|
|
defp set_visibility(activity, _), do: {:ok, activity}
|
|
|
|
def hide_reblogs(%User{} = user, %User{} = target) do
|
|
UserRelationship.create_reblog_mute(user, target)
|
|
end
|
|
|
|
def show_reblogs(%User{} = user, %User{} = target) do
|
|
UserRelationship.delete_reblog_mute(user, target)
|
|
end
|
|
|
|
def get_user(ap_id, fake_record_fallback \\ true) do
|
|
cond do
|
|
user = User.get_cached_by_ap_id(ap_id) ->
|
|
user
|
|
|
|
user = User.get_by_guessed_nickname(ap_id) ->
|
|
user
|
|
|
|
fake_record_fallback ->
|
|
# TODO: refactor (fake records is never a good idea)
|
|
User.error_user(ap_id)
|
|
|
|
true ->
|
|
nil
|
|
end
|
|
end
|
|
end
|