logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

emoji.ex (4642B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Emoji do
  5. @moduledoc """
  6. This GenServer stores in an ETS table the list of the loaded emojis,
  7. and also allows to reload the list at runtime.
  8. """
  9. use GenServer
  10. alias Pleroma.Emoji.Combinations
  11. alias Pleroma.Emoji.Loader
  12. require Logger
  13. @ets __MODULE__.Ets
  14. @ets_options [
  15. :ordered_set,
  16. :protected,
  17. :named_table,
  18. {:read_concurrency, true}
  19. ]
  20. defstruct [:code, :file, :tags, :safe_code, :safe_file]
  21. @type t :: %__MODULE__{}
  22. @doc "Build emoji struct"
  23. def build({code, file, tags}) do
  24. %__MODULE__{
  25. code: code,
  26. file: file,
  27. tags: tags,
  28. safe_code: Pleroma.HTML.strip_tags(code),
  29. safe_file: Pleroma.HTML.strip_tags(file)
  30. }
  31. end
  32. def build({code, file}), do: build({code, file, []})
  33. @doc false
  34. def start_link(_) do
  35. GenServer.start_link(__MODULE__, [], name: __MODULE__)
  36. end
  37. @doc "Reloads the emojis from disk."
  38. @spec reload() :: :ok
  39. def reload do
  40. GenServer.call(__MODULE__, :reload)
  41. end
  42. @doc "Returns the path of the emoji `name`."
  43. @spec get(String.t()) :: Pleroma.Emoji.t() | nil
  44. def get(name) do
  45. name = maybe_strip_name(name)
  46. case :ets.lookup(@ets, name) do
  47. [{_, emoji}] -> emoji
  48. _ -> nil
  49. end
  50. end
  51. @spec exist?(String.t()) :: boolean()
  52. def exist?(name), do: not is_nil(get(name))
  53. @doc "Returns all the emojos!!"
  54. @spec get_all() :: list({String.t(), String.t(), String.t()})
  55. def get_all do
  56. :ets.tab2list(@ets)
  57. end
  58. @doc "Clear out old emojis"
  59. def clear_all, do: :ets.delete_all_objects(@ets)
  60. @doc false
  61. def init(_) do
  62. @ets = :ets.new(@ets, @ets_options)
  63. GenServer.cast(self(), :reload)
  64. {:ok, nil}
  65. end
  66. @doc false
  67. def handle_cast(:reload, state) do
  68. update_emojis(Loader.load())
  69. {:noreply, state}
  70. end
  71. @doc false
  72. def handle_call(:reload, _from, state) do
  73. update_emojis(Loader.load())
  74. {:reply, :ok, state}
  75. end
  76. @doc false
  77. def terminate(_, _) do
  78. :ok
  79. end
  80. @doc false
  81. def code_change(_old_vsn, state, _extra) do
  82. update_emojis(Loader.load())
  83. {:ok, state}
  84. end
  85. defp update_emojis(emojis) do
  86. :ets.insert(@ets, emojis)
  87. end
  88. @external_resource "lib/pleroma/emoji-test.txt"
  89. regional_indicators =
  90. Enum.map(127_462..127_487, fn codepoint ->
  91. <<codepoint::utf8>>
  92. end)
  93. emojis =
  94. @external_resource
  95. |> File.read!()
  96. |> String.split("\n")
  97. |> Enum.filter(fn line ->
  98. line != "" and not String.starts_with?(line, "#") and
  99. String.contains?(line, "fully-qualified")
  100. end)
  101. |> Enum.map(fn line ->
  102. line
  103. |> String.split(";", parts: 2)
  104. |> hd()
  105. |> String.trim()
  106. |> String.split()
  107. |> Enum.map(fn codepoint ->
  108. <<String.to_integer(codepoint, 16)::utf8>>
  109. end)
  110. |> Enum.join()
  111. end)
  112. |> Enum.uniq()
  113. emojis = emojis ++ regional_indicators
  114. for emoji <- emojis do
  115. def unicode?(unquote(emoji)), do: true
  116. end
  117. def unicode?(_), do: false
  118. @emoji_regex ~r/:[A-Za-z0-9_-]+(@.+)?:/
  119. def custom?(s) when is_binary(s), do: Regex.match?(@emoji_regex, s)
  120. def custom?(_), do: false
  121. def maybe_strip_name(name) when is_binary(name), do: String.trim(name, ":")
  122. def maybe_strip_name(name), do: name
  123. def maybe_quote(name) when is_binary(name) do
  124. if unicode?(name) do
  125. name
  126. else
  127. if String.starts_with?(name, ":") do
  128. name
  129. else
  130. ":#{name}:"
  131. end
  132. end
  133. end
  134. def maybe_quote(name), do: name
  135. def emoji_url(%{"type" => "EmojiReact", "content" => _, "tag" => []}), do: nil
  136. def emoji_url(%{"type" => "EmojiReact", "content" => emoji, "tag" => tags}) do
  137. emoji = maybe_strip_name(emoji)
  138. tag =
  139. tags
  140. |> Enum.find(fn tag ->
  141. tag["type"] == "Emoji" && !is_nil(tag["name"]) && tag["name"] == emoji
  142. end)
  143. if is_nil(tag) do
  144. nil
  145. else
  146. tag
  147. |> Map.get("icon")
  148. |> Map.get("url")
  149. end
  150. end
  151. def emoji_url(_), do: nil
  152. def emoji_name_with_instance(name, url) do
  153. url = url |> URI.parse() |> Map.get(:host)
  154. "#{name}@#{url}"
  155. end
  156. emoji_qualification_map =
  157. emojis
  158. |> Enum.filter(&String.contains?(&1, "\uFE0F"))
  159. |> Combinations.variate_emoji_qualification()
  160. for {qualified, unqualified_list} <- emoji_qualification_map do
  161. for unqualified <- unqualified_list do
  162. def fully_qualify_emoji(unquote(unqualified)), do: unquote(qualified)
  163. end
  164. end
  165. def fully_qualify_emoji(emoji), do: emoji
  166. end