X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fuser.ex;h=28ff08a39f01ba1fdcf1cba223a9a33b674340ff;hb=90b00701ff0e2622825ef4c86409a27691763baf;hp=76712b4bf6bd071571f1fc3da44f0f6b97adf062;hpb=f18b86fd5f01653dd6e860b2d67021bdce684d6d;p=akkoma diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 76712b4bf..28ff08a39 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -4,9 +4,18 @@ defmodule Pleroma.User do import Ecto.{Changeset, Query} alias Pleroma.{Repo, User, Object, Web, Activity, Notification} alias Comeonin.Pbkdf2 + alias Pleroma.Formatter + alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils alias Pleroma.Web.{OStatus, Websub, OAuth} alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} + @type t :: %__MODULE__{} + + @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ + + @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ + @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ + schema "users" do field(:bio, :string) field(:email, :string) @@ -21,6 +30,7 @@ defmodule Pleroma.User do field(:local, :boolean, default: true) field(:follower_address, :string) field(:search_distance, :float, virtual: true) + field(:tags, {:array, :string}, default: []) field(:last_refreshed_at, :naive_datetime) has_many(:notifications, Notification) embeds_one(:info, Pleroma.User.Info) @@ -60,10 +70,6 @@ defmodule Pleroma.User do |> validate_required([:following]) end - def info_changeset(struct, params \\ %{}) do - raise "NOT VALID ANYMORE" - end - def user_info(%User{} = user) do oneself = if user.local, do: 1, else: 0 @@ -76,7 +82,6 @@ defmodule Pleroma.User do } end - @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ def remote_user_creation(params) do params = params @@ -116,7 +121,7 @@ defmodule Pleroma.User do struct |> cast(params, [:bio, :name, :avatar]) |> unique_constraint(:nickname) - |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/) + |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: 5000) |> validate_length(:name, min: 1, max: 100) end @@ -133,7 +138,7 @@ defmodule Pleroma.User do struct |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at]) |> unique_constraint(:nickname) - |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/) + |> validate_format(:nickname, local_nickname_regex()) |> validate_length(:bio, max: 5000) |> validate_length(:name, max: 100) |> put_embed(:info, info_cng) @@ -171,10 +176,11 @@ defmodule Pleroma.User do |> validate_confirmation(:password) |> unique_constraint(:email) |> unique_constraint(:nickname) - |> validate_format(:nickname, ~r/^[a-zA-Z\d]+$/) + |> validate_format(:nickname, local_nickname_regex()) |> validate_format(:email, @email_regex) |> validate_length(:bio, max: 1000) |> validate_length(:name, min: 1, max: 100) + |> put_change(:info, %Pleroma.User.Info{}) if changeset.valid? do hashed = Pbkdf2.hashpwsalt(changeset.changes[:password]) @@ -210,14 +216,14 @@ defmodule Pleroma.User do end def maybe_direct_follow(%User{} = follower, %User{} = followed) do - if !User.ap_enabled?(followed) do + if not User.ap_enabled?(followed) do follow(follower, followed) else {:ok, follower} end end - def maybe_follow(%User{} = follower, %User{info: info} = followed) do + def maybe_follow(%User{} = follower, %User{info: _info} = followed) do if not following?(follower, followed) do follow(follower, followed) else @@ -279,6 +285,7 @@ defmodule Pleroma.User do end end + @spec following?(User.t(), User.t()) :: boolean def following?(%User{} = follower, %User{} = followed) do Enum.member?(follower.following, followed.follower_address) end @@ -287,6 +294,10 @@ defmodule Pleroma.User do user.info.locked || false end + def get_by_id(id) do + Repo.get_by(User, id: id) + end + def get_by_ap_id(ap_id) do Repo.get_by(User, ap_id: ap_id) end @@ -313,11 +324,20 @@ defmodule Pleroma.User do Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end) end + def get_cached_by_id(id) do + key = "id:#{id}" + Cachex.fetch!(:user_cache, key, fn _ -> get_by_id(id) end) + end + def get_cached_by_nickname(nickname) do key = "nickname:#{nickname}" Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end) end + def get_cached_by_nickname_or_id(nickname_or_id) do + get_cached_by_nickname(nickname_or_id) || get_cached_by_id(nickname_or_id) + end + def get_by_nickname(nickname) do Repo.get_by(User, nickname: nickname) end @@ -733,7 +753,8 @@ defmodule Pleroma.User do source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}} }) do key = - :public_key.pem_decode(public_key_pem) + public_key_pem + |> :public_key.pem_decode() |> hd() |> :public_key.pem_entry_decode() @@ -771,13 +792,10 @@ defmodule Pleroma.User do def ap_enabled?(%User{info: info}), do: info.ap_enabled def ap_enabled?(_), do: false - def get_or_fetch(uri_or_nickname) do - if String.starts_with?(uri_or_nickname, "http") do - get_or_fetch_by_ap_id(uri_or_nickname) - else - get_or_fetch_by_nickname(uri_or_nickname) - end - end + @doc "Gets or fetch a user by uri or nickname." + @spec get_or_fetch(String.t()) :: User.t() + def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri) + def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname) # 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 @@ -802,4 +820,70 @@ defmodule Pleroma.User do :error end end + + def parse_bio(bio, user \\ %User{info: %{source_data: %{}}}) + def parse_bio(nil, _user), do: "" + def parse_bio(bio, _user) when bio == "", do: bio + + def parse_bio(bio, user) do + mentions = Formatter.parse_mentions(bio) + tags = Formatter.parse_tags(bio) + + emoji = + (user.info.source_data["tag"] || []) + |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) + |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} -> + {String.trim(name, ":"), url} + end) + + bio + |> CommonUtils.format_input(mentions, tags, "text/plain") + |> Formatter.emojify(emoji) + end + + def tag(user_identifiers, tags) when is_list(user_identifiers) do + Repo.transaction(fn -> + for user_identifier <- user_identifiers, do: tag(user_identifier, tags) + end) + end + + def tag(nickname, tags) when is_binary(nickname), + do: tag(User.get_by_nickname(nickname), tags) + + def tag(%User{} = user, tags), + do: update_tags(user, Enum.uniq(user.tags ++ normalize_tags(tags))) + + def untag(user_identifiers, tags) when is_list(user_identifiers) do + Repo.transaction(fn -> + for user_identifier <- user_identifiers, do: untag(user_identifier, tags) + end) + end + + def untag(nickname, tags) when is_binary(nickname), + do: untag(User.get_by_nickname(nickname), tags) + + def untag(%User{} = user, tags), do: update_tags(user, user.tags -- normalize_tags(tags)) + + defp update_tags(%User{} = user, new_tags) do + {:ok, updated_user} = + user + |> change(%{tags: new_tags}) + |> Repo.update() + + updated_user + end + + defp normalize_tags(tags) do + [tags] + |> List.flatten() + |> Enum.map(&String.downcase(&1)) + end + + defp local_nickname_regex() do + if Pleroma.Config.get([:instance, :extended_nickname_format]) do + @extended_local_nickname_regex + else + @strict_local_nickname_regex + end + end end