import Ecto.{Changeset, Query}
alias Pleroma.{Repo, User, Object, Web, Activity, Notification}
alias Comeonin.Pbkdf2
- alias Pleroma.Web.{OStatus, Websub}
+ alias Pleroma.Web.{OStatus, Websub, OAuth}
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
schema "users" do
field(:info, :map, default: %{})
field(:follower_address, :string)
field(:search_distance, :float, virtual: true)
+ field(:last_refreshed_at, :naive_datetime)
has_many(:notifications, Notification)
timestamps()
following_count: length(user.following) - oneself,
note_count: user.info["note_count"] || 0,
follower_count: user.info["follower_count"] || 0,
- locked: user.info["locked"] || false
+ locked: user.info["locked"] || false,
+ default_scope: user.info["default_scope"] || "public"
}
end
changes =
%User{}
|> cast(params, [:bio, :name, :ap_id, :nickname, :info, :avatar])
- |> validate_required([:name, :ap_id, :nickname])
+ |> validate_required([:name, :ap_id])
|> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: 5000)
end
def upgrade_changeset(struct, params \\ %{}) do
+ params =
+ params
+ |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
+
struct
- |> cast(params, [:bio, :name, :info, :follower_address, :avatar])
+ |> cast(params, [:bio, :name, :info, :follower_address, :avatar, :last_refreshed_at])
|> unique_constraint(:nickname)
|> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/)
|> validate_length(:bio, max: 5000)
|> validate_required([:password, :password_confirmation])
|> validate_confirmation(:password)
+ OAuth.Token.delete_user_tokens(struct)
+ OAuth.Authorization.delete_user_authorizations(struct)
+
if changeset.valid? do
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
end
end
- def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
- user_info = user_info(followed)
+ def needs_update?(%User{local: true}), do: false
- should_direct_follow =
- cond do
- # if the account is locked, don't pre-create the relationship
- user_info[:locked] == true ->
- false
+ def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
- # if the users are blocking each other, we shouldn't even be here, but check for it anyway
- User.blocks?(follower, followed) == true or User.blocks?(followed, follower) == true ->
- false
+ def needs_update?(%User{local: false} = user) do
+ NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86400
+ end
- # if OStatus, then there is no three-way handshake to follow
- User.ap_enabled?(followed) != true ->
- true
+ def needs_update?(_), do: true
- # if there are no other reasons not to, just pre-create the relationship
- true ->
- true
- end
+ def maybe_direct_follow(%User{} = follower, %User{local: true, info: %{"locked" => true}}) do
+ {:ok, follower}
+ end
+
+ def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
+ follow(follower, followed)
+ end
- if should_direct_follow do
+ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
+ if !User.ap_enabled?(followed) do
follow(follower, followed)
else
{:ok, follower}
end
def follow(%User{} = follower, %User{info: info} = followed) do
+ user_config = Application.get_env(:pleroma, :user)
+ deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
+
ap_followers = followed.follower_address
cond do
following?(follower, followed) or info["deactivated"] ->
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
- blocks?(followed, follower) ->
+ deny_follow_blocked and blocks?(followed, follower) ->
{:error, "Could not follow user: #{followed.nickname} blocked you."}
true ->
Enum.map(reqs, fn req -> req.actor end)
|> Enum.uniq()
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
+ |> Enum.filter(fn u -> !following?(u, user) end)
{:ok, users}
end
update_and_set_cache(cs)
end
+ def get_notified_from_activity_query(to) do
+ from(
+ u in User,
+ where: u.ap_id in ^to,
+ where: u.local == true
+ )
+ end
+
+ def get_notified_from_activity(%Activity{recipients: to, data: %{"type" => "Announce"} = data}) do
+ object = Object.normalize(data["object"])
+ actor = User.get_cached_by_ap_id(data["actor"])
+
+ # ensure that the actor who published the announced object appears only once
+ to =
+ if actor.nickname != nil do
+ to ++ [object.data["actor"]]
+ else
+ to
+ end
+ |> Enum.uniq()
+
+ query = get_notified_from_activity_query(to)
+
+ Repo.all(query)
+ end
+
def get_notified_from_activity(%Activity{recipients: to}) do
- query =
- from(
- u in User,
- where: u.ap_id in ^to,
- where: u.local == true
- )
+ query = get_notified_from_activity_query(to)
Repo.all(query)
end
u.nickname,
u.name
)
- }
+ },
+ where: not is_nil(u.nickname)
)
q =
end
def local_user_query() do
- from(u in User, where: u.local == true)
+ from(
+ u in User,
+ where: u.local == true,
+ where: not is_nil(u.nickname)
+ )
+ end
+
+ def moderator_user_query() do
+ from(
+ u in User,
+ where: u.local == true,
+ where: fragment("?->'is_moderator' @> 'true'", u.info)
+ )
end
def deactivate(%User{} = user) do
|> Enum.each(fn activity ->
case activity.data["type"] do
"Create" ->
- ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
+ ActivityPub.delete(Object.normalize(activity.data["object"]))
# TODO: Do something with likes, follows, repeats.
_ ->
:ok
end
+ def html_filter_policy(%User{info: %{"no_rich_text" => true}}) do
+ Pleroma.HTML.Scrubber.TwitterText
+ end
+
+ def html_filter_policy(_), do: nil
+
def get_or_fetch_by_ap_id(ap_id) do
- if user = get_by_ap_id(ap_id) do
+ user = get_by_ap_id(ap_id)
+
+ if !is_nil(user) and !User.needs_update?(user) do
user
else
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
end
end
+ def get_or_create_instance_user do
+ relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
+
+ if user = get_by_ap_id(relay_uri) do
+ user
+ else
+ changes =
+ %User{}
+ |> cast(%{}, [:ap_id, :nickname, :local])
+ |> put_change(:ap_id, relay_uri)
+ |> put_change(:nickname, nil)
+ |> put_change(:local, true)
+ |> put_change(:follower_address, relay_uri <> "/followers")
+
+ {:ok, user} = Repo.insert(changes)
+ user
+ end
+ end
+
# AP style
def public_key_from_info(%{
"source_data" => %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname)
end
+ def ap_enabled?(%User{local: true}), do: true
def ap_enabled?(%User{info: info}), do: info["ap_enabled"]
def ap_enabled?(_), do: false
get_or_fetch_by_nickname(uri_or_nickname)
end
end
+
+ # wait a period of time and return newest version of the User structs
+ # this is because we have synchronous follow APIs and need to simulate them
+ # with an async handshake
+ def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
+ with %User{} = a <- Repo.get(User, a.id),
+ %User{} = b <- Repo.get(User, b.id) do
+ {:ok, a, b}
+ else
+ _e ->
+ :error
+ end
+ end
+
+ def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
+ with :ok <- :timer.sleep(timeout),
+ %User{} = a <- Repo.get(User, a.id),
+ %User{} = b <- Repo.get(User, b.id) do
+ {:ok, a, b}
+ else
+ _e ->
+ :error
+ end
+ end
end