X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Femoji.ex;h=7d12eff7fe2982901b130ee4a604e71536c5e046;hb=e124a109c1897529b4b9eae563f550b9fb5dfb50;hp=bb3190e08af3beffd96aebcb1dbe336a04be7281;hpb=b73a1a33de76dc848037a5d0e951866bd21f92c4;p=akkoma diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index bb3190e08..933f4275a 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -1,47 +1,84 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Emoji do @moduledoc """ - The emojis are loaded from: - - * the built-in Finmojis (if enabled in configuration), - * the files: `config/emoji.txt` and `config/custom_emoji.txt` - * glob paths - - 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.Combinations + alias Pleroma.Emoji.Loader + + require Logger + @ets __MODULE__.Ets - @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}] + @ets_options [ + :ordered_set, + :protected, + :named_table, + {:read_concurrency, true} + ] + @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/ + + 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 + def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) end @doc "Reloads the emojis from disk." @spec reload() :: :ok - def reload() do + def reload do GenServer.call(__MODULE__, :reload) end @doc "Returns the path of the emoji `name`." @spec get(String.t()) :: String.t() | nil def get(name) do + name = + if String.starts_with?(name, ":") do + name + |> String.replace_leading(":", "") + |> String.replace_trailing(":", "") + else + name + end + case :ets.lookup(@ets, name) do [{_, path}] -> path _ -> nil end end + @spec exist?(String.t()) :: boolean() + def exist?(name), do: not is_nil(get(name)) + @doc "Returns all the emojos!!" - @spec get_all() :: [{String.t(), String.t()}, ...] - def get_all() do + @spec get_all() :: list({String.t(), String.t(), String.t()}) + def get_all do :ets.tab2list(@ets) end + @doc "Clear out old emojis" + def clear_all, do: :ets.delete_all_objects(@ets) + @doc false def init(_) do @ets = :ets.new(@ets, @ets_options) @@ -51,13 +88,13 @@ defmodule Pleroma.Emoji 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 @@ -68,131 +105,108 @@ defmodule Pleroma.Emoji do @doc false def code_change(_old_vsn, state, _extra) do - load() + update_emojis(Loader.load()) {:ok, state} end - defp load() do - emojis = - (load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++ - load_from_file("config/emoji.txt") ++ - load_from_file("config/custom_emoji.txt") ++ - load_from_globs( - Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, []) - )) - |> Enum.reject(fn value -> value == nil end) - - true = :ets.insert(@ets, emojis) - :ok + defp update_emojis(emojis) do + :ets.insert(@ets, emojis) end - @finmoji [ - "a_trusted_friend", - "alandislands", - "association", - "auroraborealis", - "baby_in_a_box", - "bear", - "black_gold", - "christmasparty", - "crosscountryskiing", - "cupofcoffee", - "education", - "fashionista_finns", - "finnishlove", - "flag", - "forest", - "four_seasons_of_bbq", - "girlpower", - "handshake", - "happiness", - "headbanger", - "icebreaker", - "iceman", - "joulutorttu", - "kaamos", - "kalsarikannit_f", - "kalsarikannit_m", - "karjalanpiirakka", - "kicksled", - "kokko", - "lavatanssit", - "losthopes_f", - "losthopes_m", - "mattinykanen", - "meanwhileinfinland", - "moominmamma", - "nordicfamily", - "out_of_office", - "peacemaker", - "perkele", - "pesapallo", - "polarbear", - "pusa_hispida_saimensis", - "reindeer", - "sami", - "sauna_f", - "sauna_m", - "sauna_whisk", - "sisu", - "stuck", - "suomimainittu", - "superfood", - "swan", - "the_cap", - "the_conductor", - "the_king", - "the_voice", - "theoriginalsanta", - "tomoffinland", - "torillatavataan", - "unbreakable", - "waiting", - "white_nights", - "woollysocks" - ] - defp load_finmoji(true) do - Enum.map(@finmoji, fn finmoji -> - {finmoji, "/finmoji/128px/#{finmoji}-128.png"} + @external_resource "lib/pleroma/emoji-test.txt" + + regional_indicators = + Enum.map(127_462..127_487, fn codepoint -> + <> + end) + + emojis = + @external_resource + |> File.read!() + |> String.split("\n") + |> Enum.filter(fn line -> + line != "" and not String.starts_with?(line, "#") and + String.contains?(line, "fully-qualified") + end) + |> Enum.map(fn line -> + line + |> String.split(";", parts: 2) + |> hd() + |> String.trim() + |> String.split() + |> Enum.map(fn codepoint -> + <> + end) + |> Enum.join() end) + |> Enum.uniq() + + emojis = emojis ++ regional_indicators + + for emoji <- emojis do + def is_unicode_emoji?(unquote(emoji)), do: true + end + + def is_unicode_emoji?(_), do: false + + def stripped_name(name) when is_binary(name) do + name + |> String.replace_leading(":", "") + |> String.replace_trailing(":", "") end - defp load_finmoji(_), do: [] + def stripped_name(name), do: name - defp load_from_file(file) do - if File.exists?(file) do - load_from_file_stream(File.stream!(file)) + def maybe_quote(name) when is_binary(name) do + if is_unicode_emoji?(name) do + name else - [] + if String.starts_with?(name, ":") do + name + else + ":#{name}:" + end end end - defp load_from_file_stream(stream) do - stream - |> Stream.map(&String.trim/1) - |> Stream.map(fn line -> - case String.split(line, ~r/,\s*/) do - [name, file] -> {name, file} - _ -> nil - end - end) - |> Enum.to_list() + def maybe_quote(name), do: name + + def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil + + def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do + tag = + tags + |> Enum.find(fn tag -> tag["type"] == "Emoji" && tag["name"] == stripped_name(emoji) end) + + if is_nil(tag) do + nil + else + tag + |> Map.get("icon") + |> Map.get("url") + end end - defp load_from_globs(globs) do - static_path = Path.join(:code.priv_dir(:pleroma), "static") + def emoji_url(_), do: nil - paths = - Enum.map(globs, fn glob -> - Path.join(static_path, glob) - |> Path.wildcard() - end) - |> Enum.concat() + def emoji_name_with_instance(name, url) do + url = url |> URI.parse() |> Map.get(:host) + "#{name}@#{url}" + end - Enum.map(paths, fn path -> - shortcode = Path.basename(path, Path.extname(path)) - external_path = Path.join("/", Path.relative_to(path, static_path)) - {shortcode, external_path} - end) + emoji_qualification_map = + emojis + |> Enum.filter(&String.contains?(&1, "\uFE0F")) + |> Combinations.variate_emoji_qualification() + + for {qualified, unqualified_list} <- emoji_qualification_map do + for unqualified <- unqualified_list do + def fully_qualify_emoji(unquote(unqualified)), do: unquote(qualified) + end end + + def fully_qualify_emoji(emoji), do: emoji + + def matches_shortcode?(nil), do: false + def matches_shortcode?(s), do: Regex.match?(@emoji_regex, s) end