Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

236 linhas
6.6KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Emoji.Loader do
  5. @moduledoc """
  6. The Loader emoji from:
  7. * emoji packs in INSTANCE-DIR/emoji
  8. * the files: `config/emoji.txt` and `config/custom_emoji.txt`
  9. * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
  10. """
  11. alias Pleroma.Config
  12. alias Pleroma.Emoji
  13. require Logger
  14. @mix_env Mix.env()
  15. @type pattern :: Regex.t() | module() | String.t()
  16. @type patterns :: pattern() | [pattern()]
  17. @type group_patterns :: keyword(patterns())
  18. @type emoji :: {String.t(), Emoji.t()}
  19. @doc """
  20. Loads emojis from files/packs.
  21. returns list emojis in format:
  22. `{"000", "/emoji/freespeechextremist.com/000.png", ["Custom"]}`
  23. """
  24. @spec load() :: list(emoji)
  25. def load do
  26. emoji_dir_path = Path.join(Config.get!([:instance, :static_dir]), "emoji")
  27. emoji_groups = Config.get([:emoji, :groups])
  28. emojis =
  29. case File.ls(emoji_dir_path) do
  30. {:error, :enoent} ->
  31. # The custom emoji directory doesn't exist,
  32. # don't do anything
  33. []
  34. {:error, e} ->
  35. # There was some other error
  36. Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
  37. []
  38. {:ok, results} ->
  39. grouped =
  40. Enum.group_by(results, fn file ->
  41. File.dir?(Path.join(emoji_dir_path, file))
  42. end)
  43. packs = grouped[true] || []
  44. files = grouped[false] || []
  45. # Print the packs we've found
  46. Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}")
  47. if not Enum.empty?(files) do
  48. Logger.warn(
  49. "Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
  50. Enum.join(files, ", ")
  51. }"
  52. )
  53. end
  54. emojis =
  55. Enum.flat_map(packs, fn pack ->
  56. load_pack(Path.join(emoji_dir_path, pack), emoji_groups)
  57. end)
  58. Emoji.clear_all()
  59. emojis
  60. end
  61. # Compat thing for old custom emoji handling & default emoji,
  62. # it should run even if there are no emoji packs
  63. shortcode_globs = Config.get([:emoji, :shortcode_globs], [])
  64. # for testing emoji.txt entries we do not want exposed in normal operation
  65. test_emoji =
  66. if @mix_env == :test do
  67. load_from_file("test/config/emoji.txt", emoji_groups)
  68. else
  69. []
  70. end
  71. emojis_txt =
  72. (load_from_file("config/emoji.txt", emoji_groups) ++
  73. load_from_file("config/custom_emoji.txt", emoji_groups) ++
  74. load_from_globs(shortcode_globs, emoji_groups) ++
  75. test_emoji)
  76. |> Enum.reject(fn value -> value == nil end)
  77. Enum.map(emojis ++ emojis_txt, &prepare_emoji/1)
  78. end
  79. defp prepare_emoji({code, _, _} = emoji), do: {code, Emoji.build(emoji)}
  80. defp load_pack(pack_dir, emoji_groups) do
  81. pack_name = Path.basename(pack_dir)
  82. pack_file = Path.join(pack_dir, "pack.json")
  83. if File.exists?(pack_file) do
  84. contents = Jason.decode!(File.read!(pack_file))
  85. contents["files"]
  86. |> Enum.map(fn {name, rel_file} ->
  87. filename = Path.join("/emoji/#{pack_name}", rel_file)
  88. {name, filename, ["pack:#{pack_name}"]}
  89. end)
  90. else
  91. # Load from emoji.txt / all files
  92. emoji_txt = Path.join(pack_dir, "emoji.txt")
  93. if File.exists?(emoji_txt) do
  94. load_from_file(emoji_txt, emoji_groups)
  95. else
  96. extensions = Config.get([:emoji, :pack_extensions])
  97. Logger.info(
  98. "No emoji.txt found for pack \"#{pack_name}\", assuming all #{
  99. Enum.join(extensions, ", ")
  100. } files are emoji"
  101. )
  102. make_shortcode_to_file_map(pack_dir, extensions)
  103. |> Enum.map(fn {shortcode, rel_file} ->
  104. filename = Path.join("/emoji/#{pack_name}", rel_file)
  105. {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]}
  106. end)
  107. end
  108. end
  109. end
  110. def make_shortcode_to_file_map(pack_dir, exts) do
  111. find_all_emoji(pack_dir, exts)
  112. |> Enum.map(&Path.relative_to(&1, pack_dir))
  113. |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end)
  114. |> Enum.into(%{})
  115. end
  116. def find_all_emoji(dir, exts) do
  117. dir
  118. |> File.ls!()
  119. |> Enum.flat_map(fn f ->
  120. filepath = Path.join(dir, f)
  121. if File.dir?(filepath) do
  122. find_all_emoji(filepath, exts)
  123. else
  124. [filepath]
  125. end
  126. end)
  127. |> Enum.filter(fn f -> Path.extname(f) in exts end)
  128. end
  129. defp load_from_file(file, emoji_groups) do
  130. if File.exists?(file) do
  131. load_from_file_stream(File.stream!(file), emoji_groups)
  132. else
  133. []
  134. end
  135. end
  136. defp load_from_file_stream(stream, emoji_groups) do
  137. stream
  138. |> Stream.map(&String.trim/1)
  139. |> Stream.map(fn line ->
  140. case String.split(line, ~r/,\s*/) do
  141. [name, file] ->
  142. {name, file, [to_string(match_extra(emoji_groups, file))]}
  143. [name, file | tags] ->
  144. {name, file, tags}
  145. _ ->
  146. nil
  147. end
  148. end)
  149. |> Enum.to_list()
  150. end
  151. defp load_from_globs(globs, emoji_groups) do
  152. static_path = Path.join(:code.priv_dir(:pleroma), "static")
  153. paths =
  154. Enum.map(globs, fn glob ->
  155. Path.join(static_path, glob)
  156. |> Path.wildcard()
  157. end)
  158. |> Enum.concat()
  159. Enum.map(paths, fn path ->
  160. tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path)))
  161. shortcode = Path.basename(path, Path.extname(path))
  162. external_path = Path.join("/", Path.relative_to(path, static_path))
  163. {shortcode, external_path, [to_string(tag)]}
  164. end)
  165. end
  166. @doc """
  167. Finds a matching group for the given emoji filename
  168. """
  169. @spec match_extra(group_patterns(), String.t()) :: atom() | nil
  170. def match_extra(group_patterns, filename) do
  171. match_group_patterns(group_patterns, fn pattern ->
  172. case pattern do
  173. %Regex{} = regex -> Regex.match?(regex, filename)
  174. string when is_binary(string) -> filename == string
  175. end
  176. end)
  177. end
  178. defp match_group_patterns(group_patterns, matcher) do
  179. Enum.find_value(group_patterns, fn {group, patterns} ->
  180. patterns =
  181. patterns
  182. |> List.wrap()
  183. |> Enum.map(fn pattern ->
  184. if String.contains?(pattern, "*") do
  185. ~r(#{String.replace(pattern, "*", ".*")})
  186. else
  187. pattern
  188. end
  189. end)
  190. Enum.any?(patterns, matcher) && group
  191. end)
  192. end
  193. end