98cb255d12
OTP builds to 1.15 Changelog entry Ensure policies are fully loaded Fix :warn use main branch for linkify Fix warn in tests Migrations for phoenix 1.17 Revert "Migrations for phoenix 1.17" This reverts commit 6a3b2f15b74ea5e33150529385215b7a531f3999. Oban upgrade Add default empty whitelist mix format limit test to amd64 OTP 26 tests for 1.15 use OTP_VERSION tag baka just 1.15 Massive deps update Update locale, deps Mix format shell???? multiline??? ? max cases 1 use assert_recieve don't put_env in async tests don't async conn/fs tests mix format FIx some uploader issues Fix tests
233 lines
6.6 KiB
Elixir
233 lines
6.6 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
# 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
|
|
alias Pleroma.Emoji
|
|
|
|
require Logger
|
|
|
|
@mix_env Mix.env()
|
|
|
|
@type pattern :: Regex.t() | module() | String.t()
|
|
@type patterns :: pattern() | [pattern()]
|
|
@type group_patterns :: keyword(patterns())
|
|
@type emoji :: {String.t(), Emoji.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.warning(
|
|
"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)
|
|
|
|
Emoji.clear_all()
|
|
emojis
|
|
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], [])
|
|
|
|
# for testing emoji.txt entries we do not want exposed in normal operation
|
|
test_emoji =
|
|
if @mix_env == :test do
|
|
load_from_file("test/config/emoji.txt", emoji_groups)
|
|
else
|
|
[]
|
|
end
|
|
|
|
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) ++
|
|
test_emoji)
|
|
|> Enum.reject(fn value -> value == nil end)
|
|
|
|
Enum.map(emojis ++ emojis_txt, &prepare_emoji/1)
|
|
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)
|
|
|
|
pack_file = Path.join(pack_dir, "pack.json")
|
|
|
|
if File.exists?(pack_file) do
|
|
Logger.info("Loading emoji pack from JSON: #{pack_file}")
|
|
contents = Jason.decode!(File.read!(pack_file))
|
|
|
|
contents["files"]
|
|
|> Enum.map(fn {name, rel_file} ->
|
|
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
|
{name, filename, ["pack:#{pack_name}"]}
|
|
end)
|
|
else
|
|
# Load from emoji.txt / all files
|
|
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
|
|
|
if File.exists?(emoji_txt) do
|
|
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
|
|
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
|
|
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
|
|
dir
|
|
|> File.ls!()
|
|
|> Enum.flat_map(fn f ->
|
|
filepath = Path.join(dir, f)
|
|
|
|
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
|
|
|
|
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
|