X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;ds=inline;f=lib%2Fpleroma%2Femoji.ex;h=933f4275af1d6cec7625b52ad00c9491b620798e;hb=a079ec3a3cdfd42d2cbd51c7698c2c87828e5778;hp=7d12eff7fe2982901b130ee4a604e71536c5e046;hpb=c2b8bc79e88f558a626514ab0705548315cace61;p=akkoma
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index 7d12eff7f..933f4275a 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -1,31 +1,45 @@
# 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:
-
- * 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
- require Logger
+ alias Pleroma.Emoji.Combinations
+ alias Pleroma.Emoji.Loader
- @type pattern :: Regex.t() | module() | String.t()
- @type patterns :: pattern() | [pattern()]
- @type group_patterns :: keyword(patterns())
+ require Logger
@ets __MODULE__.Ets
- @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
- @groups Pleroma.Config.get([:emoji, :groups])
+ @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
@@ -38,18 +52,33 @@ defmodule Pleroma.Emoji do
@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()}, ...]
+ @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)
@@ -59,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
@@ -76,171 +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
- emoji_dir_path =
- Path.join(
- Pleroma.Config.get!([:instance, :static_dir]),
- "emoji"
- )
-
- 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}")
+ defp update_emojis(emojis) do
+ :ets.insert(@ets, emojis)
+ end
- {:ok, packs} ->
- # Print the packs we've found
- Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
+ @external_resource "lib/pleroma/emoji-test.txt"
- emojis =
- Enum.flat_map(
- packs,
- fn pack -> load_pack(Path.join(emoji_dir_path, pack)) end
- )
+ regional_indicators =
+ Enum.map(127_462..127_487, fn codepoint ->
+ <>
+ end)
- true = :ets.insert(@ets, emojis)
- 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()
- # 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 = emojis ++ regional_indicators
- emojis =
- (load_from_file("config/emoji.txt") ++
- load_from_file("config/custom_emoji.txt") ++
- load_from_globs(shortcode_globs))
- |> Enum.reject(fn value -> value == nil end)
+ for emoji <- emojis do
+ def is_unicode_emoji?(unquote(emoji)), do: true
+ end
- true = :ets.insert(@ets, emojis)
+ def is_unicode_emoji?(_), do: false
- :ok
+ def stripped_name(name) when is_binary(name) do
+ name
+ |> String.replace_leading(":", "")
+ |> String.replace_trailing(":", "")
end
- defp load_pack(pack_dir) do
- pack_name = Path.basename(pack_dir)
+ def stripped_name(name), do: name
- emoji_txt = Path.join(pack_dir, "emoji.txt")
-
- if File.exists?(emoji_txt) do
- load_from_file(emoji_txt)
+ def maybe_quote(name) when is_binary(name) do
+ if is_unicode_emoji?(name) do
+ name
else
- Logger.info(
- "No emoji.txt found for pack \"#{pack_name}\", assuming all .png files are emoji"
- )
-
- make_shortcode_to_file_map(pack_dir, [".png"])
- |> Enum.map(fn {shortcode, rel_file} ->
- filename = Path.join("/emoji/#{pack_name}", rel_file)
-
- {shortcode, filename, [to_string(match_extra(@groups, filename))]}
- end)
+ if String.starts_with?(name, ":") do
+ name
+ else
+ ":#{name}:"
+ 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 maybe_quote(name), do: name
- def find_all_emoji(dir, exts) do
- Enum.reduce(
- File.ls!(dir),
- [],
- fn f, acc ->
- filepath = Path.join(dir, f)
+ def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil
- 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
+ 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)
- defp load_from_file(file) do
- if File.exists?(file) do
- load_from_file_stream(File.stream!(file))
+ if is_nil(tag) do
+ nil
else
- []
+ tag
+ |> Map.get("icon")
+ |> Map.get("url")
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, [to_string(match_extra(@groups, file))]}
-
- [name, file | tags] ->
- {name, file, tags}
+ def emoji_url(_), do: nil
- _ ->
- nil
- end
- end)
- |> Enum.to_list()
+ def emoji_name_with_instance(name, url) do
+ url = url |> URI.parse() |> Map.get(:host)
+ "#{name}@#{url}"
end
- defp load_from_globs(globs) do
- static_path = Path.join(:code.priv_dir(:pleroma), "static")
+ emoji_qualification_map =
+ emojis
+ |> Enum.filter(&String.contains?(&1, "\uFE0F"))
+ |> Combinations.variate_emoji_qualification()
- paths =
- Enum.map(globs, fn glob ->
- Path.join(static_path, glob)
- |> Path.wildcard()
- end)
- |> Enum.concat()
-
- Enum.map(paths, fn path ->
- tag = match_extra(@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)
+ for {qualified, unqualified_list} <- emoji_qualification_map do
+ for unqualified <- unqualified_list do
+ def fully_qualify_emoji(unquote(unqualified)), do: unquote(qualified)
+ 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
+ def fully_qualify_emoji(emoji), do: emoji
- 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
+ def matches_shortcode?(nil), do: false
+ def matches_shortcode?(s), do: Regex.match?(@emoji_regex, s)
end