aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/benchmark.ex25
-rw-r--r--lib/mix/tasks/pleroma/user.ex4
-rw-r--r--lib/pleroma/activity.ex7
-rw-r--r--lib/pleroma/bbs/authenticator.ex16
-rw-r--r--lib/pleroma/bbs/handler.ex147
-rw-r--r--lib/pleroma/formatter.ex15
-rw-r--r--lib/pleroma/html.ex2
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex2
-rw-r--r--lib/pleroma/plugs/oauth_plug.ex10
-rw-r--r--lib/pleroma/repo.ex28
-rw-r--r--lib/pleroma/user.ex51
-rw-r--r--lib/pleroma/user/info.ex1
-rw-r--r--lib/pleroma/user_invite_token.ex2
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex14
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex7
-rw-r--r--lib/pleroma/web/common_api/common_api.ex9
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex29
-rw-r--r--lib/pleroma/web/oauth/app.ex1
-rw-r--r--lib/pleroma/web/oauth/authorization.ex8
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex159
-rw-r--r--lib/pleroma/web/oauth/token.ex81
-rw-r--r--lib/pleroma/web/oauth/token/strategy/refresh_token.ex54
-rw-r--r--lib/pleroma/web/oauth/token/strategy/revoke.ex22
-rw-r--r--lib/pleroma/web/oauth/token/utils.ex30
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex2
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex18
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex9
27 files changed, 612 insertions, 141 deletions
diff --git a/lib/mix/tasks/benchmark.ex b/lib/mix/tasks/benchmark.ex
new file mode 100644
index 000000000..0fbb4dbb1
--- /dev/null
+++ b/lib/mix/tasks/benchmark.ex
@@ -0,0 +1,25 @@
+defmodule Mix.Tasks.Pleroma.Benchmark do
+ use Mix.Task
+ alias Mix.Tasks.Pleroma.Common
+
+ def run(["search"]) do
+ Common.start_pleroma()
+
+ Benchee.run(%{
+ "search" => fn ->
+ Pleroma.Web.MastodonAPI.MastodonAPIController.status_search(nil, "cofe")
+ end
+ })
+ end
+
+ def run(["tag"]) do
+ Common.start_pleroma()
+
+ Benchee.run(%{
+ "tag" => fn ->
+ %{"type" => "Create", "tag" => "cofe"}
+ |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
+ end
+ })
+ end
+end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 9e2523b18..6a83a8c0d 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -163,7 +163,7 @@ defmodule Mix.Tasks.Pleroma.User do
Common.start_pleroma()
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
- User.delete(user)
+ User.perform(:delete, user)
Mix.shell().info("User #{nickname} deleted.")
else
_ ->
@@ -380,7 +380,7 @@ defmodule Mix.Tasks.Pleroma.User do
Common.start_pleroma()
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
- User.delete_user_activities(user)
+ {:ok, _} = User.delete_user_activities(user)
Mix.shell().info("User #{nickname} statuses deleted.")
else
_ ->
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 4a2ded518..73e63bb14 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -14,6 +14,8 @@ defmodule Pleroma.Activity do
import Ecto.Query
@type t :: %__MODULE__{}
+ @type actor :: String.t()
+
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@@ -260,4 +262,9 @@ defmodule Pleroma.Activity do
|> where([s], s.actor == ^actor)
|> Repo.all()
end
+
+ @spec query_by_actor(actor()) :: Ecto.Query.t()
+ def query_by_actor(actor) do
+ from(a in Activity, where: a.actor == ^actor)
+ end
end
diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex
new file mode 100644
index 000000000..a2c153720
--- /dev/null
+++ b/lib/pleroma/bbs/authenticator.ex
@@ -0,0 +1,16 @@
+defmodule Pleroma.BBS.Authenticator do
+ use Sshd.PasswordAuthenticator
+ alias Comeonin.Pbkdf2
+ alias Pleroma.User
+
+ def authenticate(username, password) do
+ username = to_string(username)
+ password = to_string(password)
+
+ with %User{} = user <- User.get_by_nickname(username) do
+ Pbkdf2.checkpw(password, user.password_hash)
+ else
+ _e -> false
+ end
+ end
+end
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
new file mode 100644
index 000000000..106fe5d18
--- /dev/null
+++ b/lib/pleroma/bbs/handler.ex
@@ -0,0 +1,147 @@
+defmodule Pleroma.BBS.Handler do
+ use Sshd.ShellHandler
+ alias Pleroma.Activity
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+
+ def on_shell(username, _pubkey, _ip, _port) do
+ :ok = IO.puts("Welcome to #{Pleroma.Config.get([:instance, :name])}!")
+ user = Pleroma.User.get_cached_by_nickname(to_string(username))
+ Logger.debug("#{inspect(user)}")
+ loop(run_state(user: user))
+ end
+
+ def on_connect(username, ip, port, method) do
+ Logger.debug(fn ->
+ """
+ Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
+ inspect(port)
+ } using #{inspect(method)}
+ """
+ end)
+ end
+
+ def on_disconnect(username, ip, port) do
+ Logger.debug(fn ->
+ "Disconnecting SSH shell for #{username} from #{inspect(ip)}:#{inspect(port)}"
+ end)
+ end
+
+ defp loop(state) do
+ self_pid = self()
+ counter = state.counter
+ prefix = state.prefix
+ user = state.user
+
+ input = spawn(fn -> io_get(self_pid, prefix, counter, user.nickname) end)
+ wait_input(state, input)
+ end
+
+ def puts_activity(activity) do
+ status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity})
+ IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
+ IO.puts(HtmlSanitizeEx.strip_tags(status.content))
+ IO.puts("")
+ end
+
+ def handle_command(state, "help") do
+ IO.puts("Available commands:")
+ IO.puts("help - This help")
+ IO.puts("home - Show the home timeline")
+ IO.puts("p <text> - Post the given text")
+ IO.puts("r <id> <text> - Reply to the post with the given id")
+ IO.puts("quit - Quit")
+
+ state
+ end
+
+ def handle_command(%{user: user} = state, "r " <> text) do
+ text = String.trim(text)
+ [activity_id, rest] = String.split(text, " ", parts: 2)
+
+ with %Activity{} <- Activity.get_by_id(activity_id),
+ {:ok, _activity} <-
+ CommonAPI.post(user, %{"status" => rest, "in_reply_to_status_id" => activity_id}) do
+ IO.puts("Replied!")
+ else
+ _e -> IO.puts("Could not reply...")
+ end
+
+ state
+ end
+
+ def handle_command(%{user: user} = state, "p " <> text) do
+ text = String.trim(text)
+
+ with {:ok, _activity} <- CommonAPI.post(user, %{"status" => text}) do
+ IO.puts("Posted!")
+ else
+ _e -> IO.puts("Could not post...")
+ end
+
+ state
+ end
+
+ def handle_command(state, "home") do
+ user = state.user
+
+ params =
+ %{}
+ |> Map.put("type", ["Create"])
+ |> Map.put("blocking_user", user)
+ |> Map.put("muting_user", user)
+ |> Map.put("user", user)
+
+ activities =
+ [user.ap_id | user.following]
+ |> ActivityPub.fetch_activities(params)
+ |> ActivityPub.contain_timeline(user)
+
+ Enum.each(activities, fn activity ->
+ puts_activity(activity)
+ end)
+
+ state
+ end
+
+ def handle_command(state, command) do
+ IO.puts("Unknown command '#{command}'")
+ state
+ end
+
+ defp wait_input(state, input) do
+ receive do
+ {:input, ^input, "quit\n"} ->
+ IO.puts("Exiting...")
+
+ {:input, ^input, code} when is_binary(code) ->
+ code = String.trim(code)
+
+ state = handle_command(state, code)
+
+ loop(%{state | counter: state.counter + 1})
+
+ {:error, :interrupted} ->
+ IO.puts("Caught Ctrl+C...")
+ loop(%{state | counter: state.counter + 1})
+
+ {:input, ^input, msg} ->
+ :ok = Logger.warn("received unknown message: #{inspect(msg)}")
+ loop(%{state | counter: state.counter + 1})
+ end
+ end
+
+ defp run_state(opts) do
+ %{prefix: "pleroma", counter: 1, user: opts[:user]}
+ end
+
+ defp io_get(pid, prefix, counter, username) do
+ prompt = prompt(prefix, counter, username)
+ send(pid, {:input, self(), IO.gets(:stdio, prompt)})
+ end
+
+ defp prompt(prefix, counter, username) do
+ prompt = "#{username}@#{prefix}:#{counter}>"
+ prompt <> " "
+ end
+end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index dab8910c1..3d7c36d21 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -113,9 +113,7 @@ defmodule Pleroma.Formatter do
html =
if not strip do
- "<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
- MediaProxy.url(file)
- }' />"
+ "<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
else
""
end
@@ -130,12 +128,23 @@ defmodule Pleroma.Formatter do
def demojify(text, nil), do: text
+ @doc "Outputs a list of the emoji-shortcodes in a text"
def get_emoji(text) when is_binary(text) do
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
end
def get_emoji(_), do: []
+ @doc "Outputs a list of the emoji-Maps in a text"
+ def get_emoji_map(text) when is_binary(text) do
+ get_emoji(text)
+ |> Enum.reduce(%{}, fn {name, file, _group}, acc ->
+ Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
+ end)
+ end
+
+ def get_emoji_map(_), do: []
+
def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags}
end
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 726c370ad..d1da746de 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -151,6 +151,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
Meta.allow_tag_with_these_attributes("img", [
"width",
"height",
+ "class",
"title",
"alt"
])
@@ -221,6 +222,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("img", [
"width",
"height",
+ "class",
"title",
"alt"
])
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index f701aaaa5..a476f1d49 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -35,7 +35,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
defp csp_string do
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
static_url = Pleroma.Web.Endpoint.static_url()
- websocket_url = String.replace(static_url, "http", "ws")
+ websocket_url = Pleroma.Web.Endpoint.websocket_url()
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index 5888d596a..9d43732eb 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -16,6 +16,16 @@ defmodule Pleroma.Plugs.OAuthPlug do
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+ def call(%{params: %{"access_token" => access_token}} = conn, _) do
+ with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:user, user)
+ else
+ _ -> conn
+ end
+ end
+
def call(conn, _) do
with {:ok, token_str} <- fetch_token_str(conn),
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index aa5d427ae..f57e088bc 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -19,4 +19,32 @@ defmodule Pleroma.Repo do
def init(_, opts) do
{:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))}
end
+
+ @doc "find resource based on prepared query"
+ @spec find_resource(Ecto.Query.t()) :: {:ok, struct()} | {:error, :not_found}
+ def find_resource(%Ecto.Query{} = query) do
+ case __MODULE__.one(query) do
+ nil -> {:error, :not_found}
+ resource -> {:ok, resource}
+ end
+ end
+
+ def find_resource(_query), do: {:error, :not_found}
+
+ @doc """
+ Gets association from cache or loads if need
+
+ ## Examples
+
+ iex> Repo.get_assoc(token, :user)
+ %User{}
+
+ """
+ @spec get_assoc(struct(), atom()) :: {:ok, struct()} | {:error, :not_found}
+ def get_assoc(resource, association) do
+ case __MODULE__.preload(resource, association) do
+ %{^association => assoc} when not is_nil(assoc) -> {:ok, assoc}
+ _ -> {:error, :not_found}
+ end
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 1c62f238e..fd2ce81ad 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -11,7 +11,6 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Pleroma.Activity
alias Pleroma.Bookmark
- alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Registration
@@ -1165,7 +1164,12 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
- def delete(%User{} = user) do
+ @spec delete(User.t()) :: :ok
+ def delete(%User{} = user),
+ do: PleromaJobQueue.enqueue(:background, __MODULE__, [:delete, user])
+
+ @spec perform(atom(), User.t()) :: {:ok, User.t()}
+ def perform(:delete, %User{} = user) do
{:ok, user} = User.deactivate(user)
# Remove all relationships
@@ -1181,22 +1185,23 @@ defmodule Pleroma.User do
end
def delete_user_activities(%User{ap_id: ap_id} = user) do
- Activity
- |> where(actor: ^ap_id)
- |> Activity.with_preloaded_object()
- |> Repo.all()
- |> Enum.each(fn
- %{data: %{"type" => "Create"}} = activity ->
- activity |> Object.normalize() |> ActivityPub.delete()
+ stream =
+ ap_id
+ |> Activity.query_by_actor()
+ |> Activity.with_preloaded_object()
+ |> Repo.stream()
- # TODO: Do something with likes, follows, repeats.
- _ ->
- "Doing nothing"
- end)
+ Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
{:ok, user}
end
+ defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
+ Object.normalize(activity) |> ActivityPub.delete()
+ end
+
+ defp delete_activity(_activity), do: "Doing nothing"
+
def html_filter_policy(%User{info: %{no_rich_text: true}}) do
Pleroma.HTML.Scrubber.TwitterText
end
@@ -1331,18 +1336,15 @@ defmodule Pleroma.User do
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) when is_binary(bio) and bio != "" do
+ bio
+ |> CommonUtils.format_input("text/plain", mentions_format: :full)
+ |> elem(0)
+ end
- def parse_bio(bio, user) do
- 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)
+ def parse_bio(_), do: ""
+ def parse_bio(bio, user) when is_binary(bio) and bio != "" do
# TODO: get profile URLs other than user.ap_id
profile_urls = [user.ap_id]
@@ -1352,9 +1354,10 @@ defmodule Pleroma.User do
rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
)
|> elem(0)
- |> Formatter.emojify(emoji)
end
+ def parse_bio(_, _), do: ""
+
def tag(user_identifiers, tags) when is_list(user_identifiers) do
Repo.transaction(fn ->
for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index a3658d57f..1b81619ce 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -41,6 +41,7 @@ defmodule Pleroma.User.Info do
field(:hide_favorites, :boolean, default: true)
field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil)
+ field(:emoji, {:array, :map}, default: [])
field(:notification_settings, :map,
default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex
index 86f0a5486..fadc89891 100644
--- a/lib/pleroma/user_invite_token.ex
+++ b/lib/pleroma/user_invite_token.ex
@@ -24,7 +24,7 @@ defmodule Pleroma.UserInviteToken do
timestamps()
end
- @spec create_invite(map()) :: UserInviteToken.t()
+ @spec create_invite(map()) :: {:ok, UserInviteToken.t()}
def create_invite(params \\ %{}) do
%UserInviteToken{}
|> cast(params, [:max_use, :expires_at])
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index b774c2afa..508f3532f 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -856,10 +856,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("tag", tags ++ mentions)
end
+ def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
+ user_info = add_emoji_tags(user_info)
+
+ object
+ |> Map.put(:info, user_info)
+ end
+
# TODO: we should probably send mtime instead of unix epoch time for updated
- def add_emoji_tags(object) do
+ def add_emoji_tags(%{"emoji" => emoji} = object) do
tags = object["tag"] || []
- emoji = object["emoji"] || []
out =
emoji
@@ -877,6 +883,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("tag", tags ++ out)
end
+ def add_emoji_tags(object) do
+ object
+ end
+
def set_conversation(object) do
Map.put(object, "conversation", object["context"])
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 5926a3294..1254fdf6c 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -69,6 +69,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
endpoints = render("endpoints.json", %{user: user})
+ user_tags =
+ user
+ |> Transmogrifier.add_emoji_tags()
+ |> Map.get("tag", [])
+
%{
"id" => user.ap_id,
"type" => "Person",
@@ -87,7 +92,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"publicKeyPem" => public_key
},
"endpoints" => endpoints,
- "tag" => user.info.source_data["tag"] || []
+ "tag" => (user.info.source_data["tag"] || []) ++ user_tags
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index ecd183110..b53869c75 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -151,8 +151,8 @@ defmodule Pleroma.Web.CommonAPI do
),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to),
- cw <- data["spoiler_text"],
- full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
+ cw <- data["spoiler_text"] || "",
+ full_payload <- String.trim(status <> cw),
length when length in 1..limit <- String.length(full_payload),
object <-
make_note_data(
@@ -170,10 +170,7 @@ defmodule Pleroma.Web.CommonAPI do
Map.put(
object,
"emoji",
- (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
- |> Enum.reduce(%{}, fn {name, file, _}, acc ->
- Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
- end)
+ Formatter.get_emoji_map(full_payload)
) do
res =
ActivityPub.create(
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 754946624..be60e5e3c 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Config
alias Pleroma.Conversation.Participation
alias Pleroma.Filter
+ alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Fetcher
@@ -88,7 +89,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
user_params =
%{}
|> add_if_present(params, "display_name", :name)
- |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value)} end)
+ |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
|> add_if_present(params, "avatar", :avatar, fn value ->
with %Plug.Upload{} <- value,
{:ok, object} <- ActivityPub.upload(value, type: :avatar) do
@@ -98,6 +99,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end)
+ emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
+
+ user_info_emojis =
+ ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
+ |> Enum.dedup()
+
info_params =
[:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
|> Enum.reduce(%{}, fn key, acc ->
@@ -114,6 +121,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
_ -> :error
end
end)
+ |> Map.put(:emoji, user_info_emojis)
info_cng = User.Info.profile_update(user.info, info_params)
@@ -706,7 +714,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def favourited_by(conn, %{"id" => id}) do
+ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^likes)
@@ -714,13 +722,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> put_view(AccountView)
- |> render(AccountView, "accounts.json", %{users: users, as: :user})
+ |> render(AccountView, "accounts.json", %{for: user, users: users, as: :user})
else
_ -> json(conn, [])
end
end
- def reblogged_by(conn, %{"id" => id}) do
+ def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^announces)
@@ -728,7 +736,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> put_view(AccountView)
- |> render("accounts.json", %{users: users, as: :user})
+ |> render("accounts.json", %{for: user, users: users, as: :user})
else
_ -> json(conn, [])
end
@@ -785,7 +793,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> add_link_headers(:followers, followers, user)
|> put_view(AccountView)
- |> render("accounts.json", %{users: followers, as: :user})
+ |> render("accounts.json", %{for: for_user, users: followers, as: :user})
end
end
@@ -802,7 +810,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> add_link_headers(:following, followers, user)
|> put_view(AccountView)
- |> render("accounts.json", %{users: followers, as: :user})
+ |> render("accounts.json", %{for: for_user, users: followers, as: :user})
end
end
@@ -810,7 +818,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
with {:ok, follow_requests} <- User.get_follow_requests(followed) do
conn
|> put_view(AccountView)
- |> render("accounts.json", %{users: follow_requests, as: :user})
+ |> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
end
end
@@ -1237,7 +1245,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
{:ok, users} = Pleroma.List.get_following(list) do
conn
|> put_view(AccountView)
- |> render("accounts.json", %{users: users, as: :user})
+ |> render("accounts.json", %{for: user, users: users, as: :user})
end
end
@@ -1297,8 +1305,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
initial_state =
%{
meta: %{
- streaming_api_base_url:
- String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
+ streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
access_token: token,
locale: "en",
domain: Pleroma.Web.Endpoint.host(),
diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex
index 3476da484..bccc2ac96 100644
--- a/lib/pleroma/web/oauth/app.ex
+++ b/lib/pleroma/web/oauth/app.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.App do
use Ecto.Schema
import Ecto.Changeset
+ @type t :: %__MODULE__{}
schema "apps" do
field(:client_name, :string)
field(:redirect_uris, :string)
diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex
index 3461f9983..ca3901cc4 100644
--- a/lib/pleroma/web/oauth/authorization.ex
+++ b/lib/pleroma/web/oauth/authorization.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
import Ecto.Changeset
import Ecto.Query
+ @type t :: %__MODULE__{}
schema "oauth_authorizations" do
field(:token, :string)
field(:scopes, {:array, :string}, default: [])
@@ -63,4 +64,11 @@ defmodule Pleroma.Web.OAuth.Authorization do
)
|> Repo.delete_all()
end
+
+ @doc "gets auth for app by token"
+ @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
+ def get_by_token(%App{id: app_id} = _app, token) do
+ from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
+ |> Repo.find_resource()
+ end
end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 688eaca11..e3c01217d 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -13,11 +13,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
+ alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
+ @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
+
plug(:fetch_session)
plug(:fetch_flash)
@@ -138,25 +142,33 @@ defmodule Pleroma.Web.OAuth.OAuthController do
Authenticator.handle_error(conn, error)
end
+ @doc "Renew access_token with refresh_token"
+ def token_exchange(
+ conn,
+ %{"grant_type" => "refresh_token", "refresh_token" => token} = params
+ ) do
+ with %App{} = app <- get_app_from_request(conn, params),
+ {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
+ {:ok, token} <- RefreshToken.grant(token) do
+ response_attrs = %{created_at: Token.Utils.format_created_at(token)}
+
+ json(conn, response_token(user, token, response_attrs))
+ else
+ _error ->
+ put_status(conn, 400)
+ |> json(%{error: "Invalid credentials"})
+ end
+ end
+
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
with %App{} = app <- get_app_from_request(conn, params),
- fixed_token = fix_padding(params["code"]),
- %Authorization{} = auth <-
- Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
+ fixed_token = Token.Utils.fix_padding(params["code"]),
+ {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id),
- {:ok, token} <- Token.exchange_token(app, auth),
- {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
- response = %{
- token_type: "Bearer",
- access_token: token.token,
- refresh_token: token.refresh_token,
- created_at: DateTime.to_unix(inserted_at),
- expires_in: 60 * 10,
- scope: Enum.join(token.scopes, " "),
- me: user.ap_id
- }
-
- json(conn, response)
+ {:ok, token} <- Token.exchange_token(app, auth) do
+ response_attrs = %{created_at: Token.Utils.format_created_at(token)}
+
+ json(conn, response_token(user, token, response_attrs))
else
_error ->
put_status(conn, 400)
@@ -177,16 +189,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
true <- Enum.any?(scopes),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
- response = %{
- token_type: "Bearer",
- access_token: token.token,
- refresh_token: token.refresh_token,
- expires_in: 60 * 10,
- scope: Enum.join(token.scopes, " "),
- me: user.ap_id
- }
-
- json(conn, response)
+ json(conn, response_token(user, token))
else
{:auth_active, false} ->
# Per https://github.com/tootsuite/mastodon/blob/
@@ -218,10 +221,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
token_exchange(conn, params)
end
- def token_revoke(conn, %{"token" => token} = params) do
+ # Bad request
+ def token_exchange(conn, params), do: bad_request(conn, params)
+
+ def token_revoke(conn, %{"token" => _token} = params) do
with %App{} = app <- get_app_from_request(conn, params),
- %Token{} = token <- Repo.get_by(Token, token: token, app_id: app.id),
- {:ok, %Token{}} <- Repo.delete(token) do
+ {:ok, _token} <- RevokeToken.revoke(app, params) do
json(conn, %{})
else
_error ->
@@ -230,6 +235,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
+ def token_revoke(conn, params), do: bad_request(conn, params)
+
+ # Response for bad request
+ defp bad_request(conn, _) do
+ conn
+ |> put_status(500)
+ |> json(%{error: "Bad request"})
+ end
+
@doc "Prepares OAuth request to provider for Ueberauth"
def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do
scope =
@@ -278,25 +292,22 @@ defmodule Pleroma.Web.OAuth.OAuthController do
params = callback_params(params)
with {:ok, registration} <- Authenticator.get_registration(conn) do
- user = Repo.preload(registration, :user).user
auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
- if user do
- create_authorization(
- conn,
- %{"authorization" => auth_attrs},
- user: user
- )
- else
- registration_params =
- Map.merge(auth_attrs, %{
- "nickname" => Registration.nickname(registration),
- "email" => Registration.email(registration)
- })
+ case Repo.get_assoc(registration, :user) do
+ {:ok, user} ->
+ create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
- conn
- |> put_session(:registration_id, registration.id)
- |> registration_details(%{"authorization" => registration_params})
+ _ ->
+ registration_params =
+ Map.merge(auth_attrs, %{
+ "nickname" => Registration.nickname(registration),
+ "email" => Registration.email(registration)
+ })
+
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> registration_details(%{"authorization" => registration_params})
end
else
_ ->
@@ -399,36 +410,30 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
- # decoding it. Investigate sometime.
- defp fix_padding(token) do
- token
- |> URI.decode()
- |> Base.url_decode64!(padding: false)
- |> Base.url_encode64(padding: false)
+ defp get_app_from_request(conn, params) do
+ conn
+ |> fetch_client_credentials(params)
+ |> fetch_client
end
- defp get_app_from_request(conn, params) do
- # Per RFC 6749, HTTP Basic is preferred to body params
- {client_id, client_secret} =
- with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
- {:ok, decoded} <- Base.decode64(encoded),
- [id, secret] <-
- String.split(decoded, ":")
- |> Enum.map(fn s -> URI.decode_www_form(s) end) do
- {id, secret}
- else
- _ -> {params["client_id"], params["client_secret"]}
- end
+ defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
+ Repo.get_by(App, client_id: id, client_secret: secret)
+ end
- if client_id && client_secret do
- Repo.get_by(
- App,
- client_id: client_id,
- client_secret: client_secret
- )
+ defp fetch_client({_id, _secret}), do: nil
+
+ defp fetch_client_credentials(conn, params) do
+ # Per RFC 6749, HTTP Basic is preferred to body params
+ with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
+ {:ok, decoded} <- Base.decode64(encoded),
+ [id, secret] <-
+ Enum.map(
+ String.split(decoded, ":"),
+ fn s -> URI.decode_www_form(s) end
+ ) do
+ {id, secret}
else
- nil
+ _ -> {params["client_id"], params["client_secret"]}
end
end
@@ -441,4 +446,16 @@ defmodule Pleroma.Web.OAuth.OAuthController do
defp put_session_registration_id(conn, registration_id),
do: put_session(conn, :registration_id, registration_id)
+
+ defp response_token(%User{} = user, token, opts \\ %{}) do
+ %{
+ token_type: "Bearer",
+ access_token: token.token,
+ refresh_token: token.refresh_token,
+ expires_in: @expires_in,
+ scope: Enum.join(token.scopes, " "),
+ me: user.ap_id
+ }
+ |> Map.merge(opts)
+ end
end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
index 399140003..4e5d1d118 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/oauth/token.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.Token do
use Ecto.Schema
import Ecto.Query
+ import Ecto.Changeset
alias Pleroma.Repo
alias Pleroma.User
@@ -13,6 +14,9 @@ defmodule Pleroma.Web.OAuth.Token do
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
+ @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
+ @type t :: %__MODULE__{}
+
schema "oauth_tokens" do
field(:token, :string)
field(:refresh_token, :string)
@@ -24,28 +28,67 @@ defmodule Pleroma.Web.OAuth.Token do
timestamps()
end
+ @doc "Gets token for app by access token"
+ @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
+ def get_by_token(%App{id: app_id} = _app, token) do
+ from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
+ |> Repo.find_resource()
+ end
+
+ @doc "Gets token for app by refresh token"
+ @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
+ def get_by_refresh_token(%App{id: app_id} = _app, token) do
+ from(t in __MODULE__,
+ where: t.app_id == ^app_id and t.refresh_token == ^token,
+ preload: [:user]
+ )
+ |> Repo.find_resource()
+ end
+
def exchange_token(app, auth) do
with {:ok, auth} <- Authorization.use_token(auth),
true <- auth.app_id == app.id do
- create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes)
+ create_token(
+ app,
+ User.get_cached_by_id(auth.user_id),
+ %{scopes: auth.scopes}
+ )
end
end
- def create_token(%App{} = app, %User{} = user, scopes \\ nil) do
- scopes = scopes || app.scopes
- token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
- refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
-
- token = %Token{
- token: token,
- refresh_token: refresh_token,
- scopes: scopes,
- user_id: user.id,
- app_id: app.id,
- valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
- }
-
- Repo.insert(token)
+ defp put_token(changeset) do
+ changeset
+ |> change(%{token: Token.Utils.generate_token()})
+ |> validate_required([:token])
+ |> unique_constraint(:token)
+ end
+
+ defp put_refresh_token(changeset, attrs) do
+ refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_token())
+
+ changeset
+ |> change(%{refresh_token: refresh_token})
+ |> validate_required([:refresh_token])
+ |> unique_constraint(:refresh_token)
+ end
+
+ defp put_valid_until(changeset, attrs) do
+ expires_in =
+ Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), @expires_in))
+
+ changeset
+ |> change(%{valid_until: expires_in})
+ |> validate_required([:valid_until])
+ end
+
+ def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
+ %__MODULE__{user_id: user.id, app_id: app.id}
+ |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
+ |> validate_required([:scopes, :user_id, :app_id])
+ |> put_valid_until(attrs)
+ |> put_token
+ |> put_refresh_token(attrs)
+ |> Repo.insert()
end
def delete_user_tokens(%User{id: user_id}) do
@@ -73,4 +116,10 @@ defmodule Pleroma.Web.OAuth.Token do
|> Repo.all()
|> Repo.preload(:app)
end
+
+ def is_expired?(%__MODULE__{valid_until: valid_until}) do
+ NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0
+ end
+
+ def is_expired?(_), do: false
end
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
new file mode 100644
index 000000000..7df0be14e
--- /dev/null
+++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
@@ -0,0 +1,54 @@
+defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
+ @moduledoc """
+ Functions for dealing with refresh token strategy.
+ """
+
+ alias Pleroma.Config
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.OAuth.Token.Strategy.Revoke
+
+ @doc """
+ Will grant access token by refresh token.
+ """
+ @spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()}
+ def grant(token) do
+ access_token = Repo.preload(token, [:user, :app])
+
+ result =
+ Repo.transaction(fn ->
+ token_params = %{
+ app: access_token.app,
+ user: access_token.user,
+ scopes: access_token.scopes
+ }
+
+ access_token
+ |> revoke_access_token()
+ |> create_access_token(token_params)
+ end)
+
+ case result do
+ {:ok, {:error, reason}} -> {:error, reason}
+ {:ok, {:ok, token}} -> {:ok, token}
+ {:error, reason} -> {:error, reason}
+ end
+ end
+
+ defp revoke_access_token(token) do
+ Revoke.revoke(token)
+ end
+
+ defp create_access_token({:error, error}, _), do: {:error, error}
+
+ defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
+ Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
+ end
+
+ defp add_refresh_token(params, token) do
+ case Config.get([:oauth2, :issue_new_refresh_token], false) do
+ true -> Map.put(params, :refresh_token, token)
+ false -> params
+ end
+ end
+end
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex
new file mode 100644
index 000000000..dea63ca54
--- /dev/null
+++ b/lib/pleroma/web/oauth/token/strategy/revoke.ex
@@ -0,0 +1,22 @@
+defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
+ @moduledoc """
+ Functions for dealing with revocation.
+ """
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Token
+
+ @doc "Finds and revokes access token for app and by token"
+ @spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()}
+ def revoke(%App{} = app, %{"token" => token} = _attrs) do
+ with {:ok, token} <- Token.get_by_token(app, token),
+ do: revoke(token)
+ end
+
+ @doc "Revokes access token"
+ @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
+ def revoke(%Token{} = token) do
+ Repo.delete(token)
+ end
+end
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex
new file mode 100644
index 000000000..a81560a1c
--- /dev/null
+++ b/lib/pleroma/web/oauth/token/utils.ex
@@ -0,0 +1,30 @@
+defmodule Pleroma.Web.OAuth.Token.Utils do
+ @moduledoc """
+ Auxiliary functions for dealing with tokens.
+ """
+
+ @doc "convert token inserted_at to unix timestamp"
+ def format_created_at(%{inserted_at: inserted_at} = _token) do
+ inserted_at
+ |> DateTime.from_naive!("Etc/UTC")
+ |> DateTime.to_unix()
+ end
+
+ @doc false
+ @spec generate_token(keyword()) :: binary()
+ def generate_token(opts \\ []) do
+ opts
+ |> Keyword.get(:size, 32)
+ |> :crypto.strong_rand_bytes()
+ |> Base.url_encode64(padding: false)
+ end
+
+ # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
+ # decoding it. Investigate sometime.
+ def fix_padding(token) do
+ token
+ |> URI.decode()
+ |> Base.url_decode64!(padding: false)
+ |> Base.url_encode64(padding: false)
+ end
+end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 1122e6c5d..c03f8ab3a 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -352,7 +352,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
def delete_account(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
{:ok, user} ->
- Task.start(fn -> User.delete(user) end)
+ User.delete(user)
json(conn, %{status: "success"})
{:error, msg} ->
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 79ed9dad2..ef7b6fe65 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
alias Ecto.Changeset
alias Pleroma.Activity
+ alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
@@ -653,7 +654,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
defp parse_profile_bio(user, params) do
if bio = params["description"] do
- Map.put(params, "bio", User.parse_bio(bio, user))
+ emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
+
+ emojis =
+ ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
+ |> Enum.dedup()
+
+ user_info =
+ user.info
+ |> Map.put(
+ "emoji",
+ emojis
+ )
+
+ params
+ |> Map.put("bio", User.parse_bio(bio, user))
+ |> Map.put("info", user_info)
else
params
end
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index ea015b8f0..f0a4ddbd3 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -67,6 +67,13 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
{String.trim(name, ":"), url}
end)
+ emoji = Enum.dedup(emoji ++ user.info.emoji)
+
+ description_html =
+ (user.bio || "")
+ |> HTML.filter_tags(User.html_filter_policy(for_user))
+ |> Formatter.emojify(emoji)
+
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
fields =
@@ -78,7 +85,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
%{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
- "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
+ "description_html" => description_html,
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,