From 3601f03147bd104f6acff64e7c8d5d4d3e1f53a2 Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Mon, 1 Apr 2019 17:17:57 +0700
Subject: [PATCH 1/9] Adding tag to emoji ets table

changes in apis
---
 config/config.exs                             |  7 ++-
 config/emoji.txt                              |  5 +-
 docs/api/pleroma_api.md                       |  6 +--
 docs/config/custom_emoji.md                   | 24 ++++++++-
 lib/pleroma/emoji.ex                          | 53 ++++++++++++++++---
 lib/pleroma/formatter.ex                      |  8 +--
 lib/pleroma/web/common_api/common_api.ex      |  2 +-
 lib/pleroma/web/common_api/utils.ex           |  2 +-
 .../mastodon_api/mastodon_api_controller.ex   |  5 +-
 .../controllers/util_controller.ex            |  8 ++-
 test/emoji_test.exs                           | 30 +++++++++++
 test/formatter_test.exs                       |  3 +-
 .../mastodon_api_controller_test.exs          | 16 ++++++
 test/web/twitter_api/util_controller_test.exs | 21 ++++++++
 14 files changed, 165 insertions(+), 25 deletions(-)
 create mode 100644 test/emoji_test.exs

diff --git a/config/config.exs b/config/config.exs
index 0df38d75a..245c7d268 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -54,7 +54,12 @@ config :pleroma, Pleroma.Uploaders.MDII,
   cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
   files: "https://mdii.sakura.ne.jp"
 
-config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
+config :pleroma, :emoji,
+  shortcode_globs: ["/emoji/custom/**/*.png"],
+  custom_tag: "Custom",
+  finmoji_tag: "Finmoji",
+  emoji_tag: "Emoji",
+  custom_emoji_tag: "Custom"
 
 config :pleroma, :uri_schemes,
   valid_schemes: [
diff --git a/config/emoji.txt b/config/emoji.txt
index 7afacb09f..79246f239 100644
--- a/config/emoji.txt
+++ b/config/emoji.txt
@@ -1,5 +1,5 @@
-firefox, /emoji/Firefox.gif
-blank, /emoji/blank.png
+firefox, /emoji/Firefox.gif, Gif,Fun
+blank, /emoji/blank.png, Fun
 f_00b, /emoji/f_00b.png
 f_00b11b, /emoji/f_00b11b.png
 f_00b33b, /emoji/f_00b33b.png
@@ -28,4 +28,3 @@ f_33b00b, /emoji/f_33b00b.png
 f_33b22b, /emoji/f_33b22b.png
 f_33h, /emoji/f_33h.png
 f_33t, /emoji/f_33t.png
-
diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md
index 478c9d874..2e8fb04d2 100644
--- a/docs/api/pleroma_api.md
+++ b/docs/api/pleroma_api.md
@@ -10,7 +10,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
 * Authentication: not required
 * Params: none
 * Response: JSON
-* Example response: `{"kalsarikannit_f":"/finmoji/128px/kalsarikannit_f-128.png","perkele":"/finmoji/128px/perkele-128.png","blobdab":"/emoji/blobdab.png","happiness":"/finmoji/128px/happiness-128.png"}`
+* Example response: `[{"kalsarikannit_f":{"tags":["Finmoji"],"image_url":"/finmoji/128px/kalsarikannit_f-128.png"}},{"perkele":{"tags":["Finmoji"],"image_url":"/finmoji/128px/perkele-128.png"}},{"blobdab":{"tags":["SomeTag"],"image_url":"/emoji/blobdab.png"}},"happiness":{"tags":["Finmoji"],"image_url":"/finmoji/128px/happiness-128.png"}}]`
 * Note: Same data as Mastodon API’s `/api/v1/custom_emojis` but in a different format
 
 ## `/api/pleroma/follow_import`
@@ -27,14 +27,14 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi
 * Method: `GET`
 * Authentication: not required
 * Params: none
-* Response: Provider specific JSON, the only guaranteed parameter is `type` 
+* Response: Provider specific JSON, the only guaranteed parameter is `type`
 * Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint"}`
 
 ## `/api/pleroma/delete_account`
 ### Delete an account
 * Method `POST`
 * Authentication: required
-* Params: 
+* Params:
     * `password`: user's password
 * Response: JSON. Returns `{"status": "success"}` if the deletion was successful, `{"error": "[error message]"}` otherwise
 * Example response: `{"error": "Invalid password."}`
diff --git a/docs/config/custom_emoji.md b/docs/config/custom_emoji.md
index e833d2080..e47a75c8e 100644
--- a/docs/config/custom_emoji.md
+++ b/docs/config/custom_emoji.md
@@ -11,8 +11,28 @@ image files (in `/priv/static/emoji/custom`): `happy.png` and `sad.png`
 
 content of `config/custom_emoji.txt`:
 ```
-happy, /emoji/custom/happy.png
-sad, /emoji/custom/sad.png
+happy, /emoji/custom/happy.png, Tag1,Tag2
+sad, /emoji/custom/sad.png, Tag1
+foo, /emoji/custom/foo.png
 ```
 
 The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
+
+# Emoji tags
+
+Changing default tags:
+
+* For `Finmoji`, `emoji.txt` and `custom_emoji.txt` are added default tags, which can be configured in the `config.exs`:
+* For emoji loaded from globs:
+    - `priv/static/emoji/custom/*.png` - `custom_tag`, can be configured in `config.exs`
+    - `priv/static/emoji/custom/TagName/*.png` - folder (`TagName`) is used as tag
+
+
+```
+config :pleroma, :emoji,
+  shortcode_globs: ["/emoji/custom/**/*.png"],
+  custom_tag: "Custom", # Default tag for emoji in `priv/static/emoji/custom` path
+  finmoji_tag: "Finmoji", # Default tag for Finmoji
+  emoji_tag: "Emoji", # Default tag for emoji.txt
+  custom_emoji_tag: "Custom" # Default tag for custom_emoji.txt
+```
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index f3f08cd9d..c35aed6ee 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Emoji do
 
     * the built-in Finmojis (if enabled in configuration),
     * the files: `config/emoji.txt` and `config/custom_emoji.txt`
-    * glob paths
+    * 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.
   """
@@ -152,8 +152,10 @@ defmodule Pleroma.Emoji do
     "woollysocks"
   ]
   defp load_finmoji(true) do
+    tag = Keyword.get(Application.get_env(:pleroma, :emoji), :finmoji_tag)
+
     Enum.map(@finmoji, fn finmoji ->
-      {finmoji, "/finmoji/128px/#{finmoji}-128.png"}
+      {finmoji, "/finmoji/128px/#{finmoji}-128.png", tag}
     end)
   end
 
@@ -168,31 +170,70 @@ defmodule Pleroma.Emoji do
   end
 
   defp load_from_file_stream(stream) do
+    default_tag =
+      stream.path
+      |> Path.basename(".txt")
+      |> get_default_tag()
+
     stream
     |> Stream.map(&String.trim/1)
     |> Stream.map(fn line ->
       case String.split(line, ~r/,\s*/) do
-        [name, file] -> {name, file}
-        _ -> nil
+        [name, file, tags] ->
+          {name, file, tags}
+
+        [name, file] ->
+          {name, file, default_tag}
+
+        _ ->
+          nil
       end
     end)
     |> Enum.to_list()
   end
 
+  @spec get_default_tag(String.t()) :: String.t()
+  defp get_default_tag(file_name) when file_name in ["emoji", "custom_emojii"] do
+    Keyword.get(
+      Application.get_env(:pleroma, :emoji),
+      String.to_existing_atom(file_name <> "_tag")
+    )
+  end
+
+  defp get_default_tag(_), do: Keyword.get(Application.get_env(:pleroma, :emoji), :custom_tag)
+
   defp load_from_globs(globs) do
     static_path = Path.join(:code.priv_dir(:pleroma), "static")
 
     paths =
       Enum.map(globs, fn glob ->
+        static_part =
+          Path.dirname(glob)
+          |> String.replace_trailing("**", "")
+
         Path.join(static_path, glob)
         |> Path.wildcard()
+        |> Enum.map(fn path ->
+          custom_folder =
+            path
+            |> Path.relative_to(Path.join(static_path, static_part))
+            |> Path.dirname()
+
+          [path, custom_folder]
+        end)
       end)
       |> Enum.concat()
 
-    Enum.map(paths, fn path ->
+    Enum.map(paths, fn [path, custom_folder] ->
+      tag =
+        case custom_folder do
+          "." -> Keyword.get(Application.get_env(:pleroma, :emoji), :custom_tag)
+          tag -> tag
+        end
+
       shortcode = Path.basename(path, Path.extname(path))
       external_path = Path.join("/", Path.relative_to(path, static_path))
-      {shortcode, external_path}
+      {shortcode, external_path, tag}
     end)
   end
 end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index e3625383b..8ea9dbd38 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -77,9 +77,9 @@ defmodule Pleroma.Formatter do
   def emojify(text, nil), do: text
 
   def emojify(text, emoji, strip \\ false) do
-    Enum.reduce(emoji, text, fn {emoji, file}, text ->
-      emoji = HTML.strip_tags(emoji)
-      file = HTML.strip_tags(file)
+    Enum.reduce(emoji, text, fn emoji_data, text ->
+      emoji = HTML.strip_tags(elem(emoji_data, 0))
+      file = HTML.strip_tags(elem(emoji_data, 1))
 
       html =
         if not strip do
@@ -101,7 +101,7 @@ defmodule Pleroma.Formatter do
   def demojify(text, nil), do: text
 
   def get_emoji(text) when is_binary(text) do
-    Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
+    Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
   end
 
   def get_emoji(_), do: []
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 25b990677..f910eb1f9 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -167,7 +167,7 @@ defmodule Pleroma.Web.CommonAPI do
              object,
              "emoji",
              (Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
-             |> Enum.reduce(%{}, fn {name, file}, acc ->
+             |> Enum.reduce(%{}, fn {name, file, _}, acc ->
                Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
              end)
            ) do
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index f596f703b..49f0170cc 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -285,7 +285,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
 
   def emoji_from_profile(%{info: _info} = user) do
     (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
-    |> Enum.map(fn {shortcode, url} ->
+    |> Enum.map(fn {shortcode, url, _} ->
       %{
         "type" => "Emoji",
         "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index eee4e7678..583e4007c 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -178,14 +178,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   defp mastodonized_emoji do
     Pleroma.Emoji.get_all()
-    |> Enum.map(fn {shortcode, relative_url} ->
+    |> Enum.map(fn {shortcode, relative_url, tags} ->
       url = to_string(URI.merge(Web.base_url(), relative_url))
 
       %{
         "shortcode" => shortcode,
         "static_url" => url,
         "visible_in_picker" => true,
-        "url" => url
+        "url" => url,
+        "tags" => String.split(tags, ",")
       }
     end)
   end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index faa733fec..e58d9e4cd 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -266,7 +266,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
   end
 
   def emoji(conn, _params) do
-    json(conn, Enum.into(Emoji.get_all(), %{}))
+    emoji =
+      Emoji.get_all()
+      |> Enum.map(fn {short_code, path, tags} ->
+        %{short_code => %{image_url: path, tags: String.split(tags, ",")}}
+      end)
+
+    json(conn, emoji)
   end
 
   def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
diff --git a/test/emoji_test.exs b/test/emoji_test.exs
new file mode 100644
index 000000000..c9c32e20b
--- /dev/null
+++ b/test/emoji_test.exs
@@ -0,0 +1,30 @@
+defmodule Pleroma.EmojiTest do
+  use ExUnit.Case, async: true
+  alias Pleroma.Emoji
+
+  describe "get_all/0" do
+    setup do
+      emoji_list = Emoji.get_all()
+      {:ok, emoji_list: emoji_list}
+    end
+    test "first emoji", %{emoji_list: emoji_list} do
+      [emoji | _others] = emoji_list
+      {code, path, tags} = emoji
+
+      assert tuple_size(emoji) == 3
+      assert is_binary(code)
+      assert is_binary(path)
+      assert is_binary(tags)
+    end
+
+    test "random emoji", %{emoji_list: emoji_list} do
+      emoji = Enum.random(emoji_list)
+     {code, path, tags} = emoji
+
+      assert tuple_size(emoji) == 3
+      assert is_binary(code)
+      assert is_binary(path)
+      assert is_binary(tags)
+    end
+  end
+end
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index fcdf931b7..e67042a5f 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -271,7 +271,8 @@ defmodule Pleroma.FormatterTest do
   test "it returns the emoji used in the text" do
     text = "I love :moominmamma:"
 
-    assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png"}]
+    tag = Keyword.get(Application.get_env(:pleroma, :emoji), :finmoji_tag)
+    assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png", tag}]
   end
 
   test "it returns a nice empty result when no emojis are present" do
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index d9bcbf5a9..3b10c4a1a 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -2265,4 +2265,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert link_header =~ ~r/max_id=#{notification1.id}/
     end
   end
+
+  describe "custom emoji" do
+    test "with tags", %{conn: conn} do
+      [emoji | _body] =
+        conn
+        |> get("/api/v1/custom_emojis")
+        |> json_response(200)
+
+      assert Map.has_key?(emoji, "shortcode")
+      assert Map.has_key?(emoji, "static_url")
+      assert Map.has_key?(emoji, "tags")
+      assert is_list(emoji["tags"])
+      assert Map.has_key?(emoji, "url")
+      assert Map.has_key?(emoji, "visible_in_picker")
+    end
+  end
 end
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 832fdc096..1063ad28f 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -164,4 +164,25 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       assert response == Jason.encode!(config |> Enum.into(%{})) |> Jason.decode!()
     end
   end
+
+  describe "/api/pleroma/emoji" do
+    test "returns json with custom emoji with tags", %{conn: conn} do
+      [emoji | _body] =
+        conn
+        |> get("/api/pleroma/emoji")
+        |> json_response(200)
+
+      [key] = Map.keys(emoji)
+
+      %{
+        ^key => %{
+          "image_url" => url,
+          "tags" => tags
+        }
+      } = emoji
+
+      assert is_binary(url)
+      assert is_list(tags)
+    end
+  end
 end

From 17d3d05a7196140b62dd791af8d7ced8b0ad9fa1 Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Mon, 1 Apr 2019 17:54:30 +0700
Subject: [PATCH 2/9] code style

little fix
---
 lib/pleroma/emoji.ex    | 6 +++---
 test/emoji_test.exs     | 3 ++-
 test/formatter_test.exs | 5 ++++-
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index c35aed6ee..ad3170f9a 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -152,7 +152,7 @@ defmodule Pleroma.Emoji do
     "woollysocks"
   ]
   defp load_finmoji(true) do
-    tag = Keyword.get(Application.get_env(:pleroma, :emoji), :finmoji_tag)
+    tag = Application.get_env(:pleroma, :emoji)[:finmoji_tag]
 
     Enum.map(@finmoji, fn finmoji ->
       {finmoji, "/finmoji/128px/#{finmoji}-128.png", tag}
@@ -193,14 +193,14 @@ defmodule Pleroma.Emoji do
   end
 
   @spec get_default_tag(String.t()) :: String.t()
-  defp get_default_tag(file_name) when file_name in ["emoji", "custom_emojii"] do
+  defp get_default_tag(file_name) when file_name in ["emoji", "custom_emoji"] do
     Keyword.get(
       Application.get_env(:pleroma, :emoji),
       String.to_existing_atom(file_name <> "_tag")
     )
   end
 
-  defp get_default_tag(_), do: Keyword.get(Application.get_env(:pleroma, :emoji), :custom_tag)
+  defp get_default_tag(_), do: Application.get_env(:pleroma, :emoji)[:custom_tag]
 
   defp load_from_globs(globs) do
     static_path = Path.join(:code.priv_dir(:pleroma), "static")
diff --git a/test/emoji_test.exs b/test/emoji_test.exs
index c9c32e20b..a90213d7d 100644
--- a/test/emoji_test.exs
+++ b/test/emoji_test.exs
@@ -7,6 +7,7 @@ defmodule Pleroma.EmojiTest do
       emoji_list = Emoji.get_all()
       {:ok, emoji_list: emoji_list}
     end
+
     test "first emoji", %{emoji_list: emoji_list} do
       [emoji | _others] = emoji_list
       {code, path, tags} = emoji
@@ -19,7 +20,7 @@ defmodule Pleroma.EmojiTest do
 
     test "random emoji", %{emoji_list: emoji_list} do
       emoji = Enum.random(emoji_list)
-     {code, path, tags} = emoji
+      {code, path, tags} = emoji
 
       assert tuple_size(emoji) == 3
       assert is_binary(code)
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index e67042a5f..38430e170 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -272,7 +272,10 @@ defmodule Pleroma.FormatterTest do
     text = "I love :moominmamma:"
 
     tag = Keyword.get(Application.get_env(:pleroma, :emoji), :finmoji_tag)
-    assert Formatter.get_emoji(text) == [{"moominmamma", "/finmoji/128px/moominmamma-128.png", tag}]
+
+    assert Formatter.get_emoji(text) == [
+             {"moominmamma", "/finmoji/128px/moominmamma-128.png", tag}
+           ]
   end
 
   test "it returns a nice empty result when no emojis are present" do

From 49733f61763091514faa49493fdc20b795c08c1c Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Mon, 1 Apr 2019 18:28:19 +0700
Subject: [PATCH 3/9] add docs folder to gitignore

ref #770
---
 .gitignore | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.gitignore b/.gitignore
index 04c61ede7..774893b35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,6 @@ erl_crash.dump
 
 # Editor config
 /.vscode/
+
+# Prevent committing docs files
+/priv/static/doc/*

From 9b2188da7cab43a162d441294db7d3155e2eeab3 Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Tue, 2 Apr 2019 15:44:56 +0700
Subject: [PATCH 4/9] refactoring of emoji tags config to use groups

---
 config/config.exs    |  9 +++--
 lib/pleroma/emoji.ex | 92 +++++++++++++++++++++++---------------------
 test/emoji_test.exs  | 75 ++++++++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+), 47 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index 245c7d268..4a22167b2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -56,10 +56,11 @@ config :pleroma, Pleroma.Uploaders.MDII,
 
 config :pleroma, :emoji,
   shortcode_globs: ["/emoji/custom/**/*.png"],
-  custom_tag: "Custom",
-  finmoji_tag: "Finmoji",
-  emoji_tag: "Emoji",
-  custom_emoji_tag: "Custom"
+  groups: [
+    # Place here groups, which have more priority on defaults. Example in `docs/config/custom_emoji.md`
+    Finmoji: "/finmoji/128px/*-128.png",
+    Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
+  ]
 
 config :pleroma, :uri_schemes,
   valid_schemes: [
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index ad3170f9a..b60d19e89 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -13,8 +13,14 @@ defmodule Pleroma.Emoji do
   This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
   """
   use GenServer
+
+  @type pattern :: Regex.t() | module() | String.t()
+  @type patterns :: pattern | [pattern]
+  @type group_patterns :: keyword(patterns)
+
   @ets __MODULE__.Ets
   @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
+  @groups Application.get_env(:pleroma, :emoji)[:groups]
 
   @doc false
   def start_link do
@@ -73,13 +79,14 @@ defmodule Pleroma.Emoji do
   end
 
   defp load do
+    finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
+    shortcode_globs = Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
+
     emojis =
-      (load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
+      (load_finmoji(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, [])
-         ))
+         load_from_globs(shortcode_globs))
       |> Enum.reject(fn value -> value == nil end)
 
     true = :ets.insert(@ets, emojis)
@@ -151,11 +158,12 @@ defmodule Pleroma.Emoji do
     "white_nights",
     "woollysocks"
   ]
-  defp load_finmoji(true) do
-    tag = Application.get_env(:pleroma, :emoji)[:finmoji_tag]
 
+  defp load_finmoji(true) do
     Enum.map(@finmoji, fn finmoji ->
-      {finmoji, "/finmoji/128px/#{finmoji}-128.png", tag}
+      file_name = "/finmoji/128px/#{finmoji}-128.png"
+      group = match_extra(@groups, file_name)
+      {finmoji, file_name, to_string(group)}
     end)
   end
 
@@ -170,11 +178,6 @@ defmodule Pleroma.Emoji do
   end
 
   defp load_from_file_stream(stream) do
-    default_tag =
-      stream.path
-      |> Path.basename(".txt")
-      |> get_default_tag()
-
     stream
     |> Stream.map(&String.trim/1)
     |> Stream.map(fn line ->
@@ -183,7 +186,7 @@ defmodule Pleroma.Emoji do
           {name, file, tags}
 
         [name, file] ->
-          {name, file, default_tag}
+          {name, file, to_string(match_extra(@groups, file))}
 
         _ ->
           nil
@@ -192,48 +195,51 @@ defmodule Pleroma.Emoji do
     |> Enum.to_list()
   end
 
-  @spec get_default_tag(String.t()) :: String.t()
-  defp get_default_tag(file_name) when file_name in ["emoji", "custom_emoji"] do
-    Keyword.get(
-      Application.get_env(:pleroma, :emoji),
-      String.to_existing_atom(file_name <> "_tag")
-    )
-  end
-
-  defp get_default_tag(_), do: Application.get_env(:pleroma, :emoji)[:custom_tag]
-
   defp load_from_globs(globs) do
     static_path = Path.join(:code.priv_dir(:pleroma), "static")
 
     paths =
       Enum.map(globs, fn glob ->
-        static_part =
-          Path.dirname(glob)
-          |> String.replace_trailing("**", "")
-
         Path.join(static_path, glob)
         |> Path.wildcard()
-        |> Enum.map(fn path ->
-          custom_folder =
-            path
-            |> Path.relative_to(Path.join(static_path, static_part))
-            |> Path.dirname()
-
-          [path, custom_folder]
-        end)
       end)
       |> Enum.concat()
 
-    Enum.map(paths, fn [path, custom_folder] ->
-      tag =
-        case custom_folder do
-          "." -> Keyword.get(Application.get_env(:pleroma, :emoji), :custom_tag)
-          tag -> tag
-        end
-
+    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, tag}
+      {shortcode, external_path, to_string(tag)}
+    end)
+  end
+
+  @doc """
+  Finds a matching group for the given extra 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
diff --git a/test/emoji_test.exs b/test/emoji_test.exs
index a90213d7d..cb1d62d00 100644
--- a/test/emoji_test.exs
+++ b/test/emoji_test.exs
@@ -28,4 +28,79 @@ defmodule Pleroma.EmojiTest do
       assert is_binary(tags)
     end
   end
+
+  describe "match_extra/2" do
+    setup do
+      groups = [
+        "list of files": ["/emoji/custom/first_file.png", "/emoji/custom/second_file.png"],
+        "wildcard folder": "/emoji/custom/*/file.png",
+        "wildcard files": "/emoji/custom/folder/*.png",
+        "special file": "/emoji/custom/special.png"
+      ]
+
+      {:ok, groups: groups}
+    end
+
+    test "config for list of files", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/custom/first_file.png")
+        |> to_string()
+
+      assert group == "list of files"
+    end
+
+    test "config with wildcard folder", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/custom/some_folder/file.png")
+        |> to_string()
+
+      assert group == "wildcard folder"
+    end
+
+    test "config with wildcard folder and subfolders", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/custom/some_folder/another_folder/file.png")
+        |> to_string()
+
+      assert group == "wildcard folder"
+    end
+
+    test "config with wildcard files", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/custom/folder/some_file.png")
+        |> to_string()
+
+      assert group == "wildcard files"
+    end
+
+    test "config with wildcard files and subfolders", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/custom/folder/another_folder/some_file.png")
+        |> to_string()
+
+      assert group == "wildcard files"
+    end
+
+    test "config for special file", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/custom/special.png")
+        |> to_string()
+
+      assert group == "special file"
+    end
+
+    test "no mathing returns nil", %{groups: groups} do
+      group =
+        groups
+        |> Emoji.match_extra("/emoji/some_undefined.png")
+
+      refute group
+    end
+  end
 end

From 851c5bf0936fbc58bf509f79531e6cdc070efde5 Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Tue, 2 Apr 2019 15:57:57 +0700
Subject: [PATCH 5/9] updating custom_emoji docs

---
 docs/config/custom_emoji.md | 41 +++++++++++++++++++++++++------------
 1 file changed, 28 insertions(+), 13 deletions(-)

diff --git a/docs/config/custom_emoji.md b/docs/config/custom_emoji.md
index e47a75c8e..d37220a72 100644
--- a/docs/config/custom_emoji.md
+++ b/docs/config/custom_emoji.md
@@ -18,21 +18,36 @@ foo, /emoji/custom/foo.png
 
 The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
 
-# Emoji tags
-
-Changing default tags:
-
-* For `Finmoji`, `emoji.txt` and `custom_emoji.txt` are added default tags, which can be configured in the `config.exs`:
-* For emoji loaded from globs:
-    - `priv/static/emoji/custom/*.png` - `custom_tag`, can be configured in `config.exs`
-    - `priv/static/emoji/custom/TagName/*.png` - folder (`TagName`) is used as tag
-
+# Emoji tags (groups)
 
+Default tags are set in `config.exs`.
 ```
 config :pleroma, :emoji,
   shortcode_globs: ["/emoji/custom/**/*.png"],
-  custom_tag: "Custom", # Default tag for emoji in `priv/static/emoji/custom` path
-  finmoji_tag: "Finmoji", # Default tag for Finmoji
-  emoji_tag: "Emoji", # Default tag for emoji.txt
-  custom_emoji_tag: "Custom" # Default tag for custom_emoji.txt
+  groups: [
+    Finmoji: "/finmoji/128px/*-128.png",
+    Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
+  ]
 ```
+
+Order of the `groups` matters, so to override default tags just put your group on the top of the list. E.g:
+```
+config :pleroma, :emoji,
+  shortcode_globs: ["/emoji/custom/**/*.png"],
+  groups: [
+    "Finmoji special": "/finmoji/128px/a_trusted_friend-128.png", # special file
+    "Cirno": "/emoji/custom/cirno*.png", # png files in /emoji/custom/ which start with `cirno`
+    "Special group": "/emoji/custom/special_folder/*.png", # png files in /emoji/custom/special_folder/
+    "Another group": "/emoji/custom/special_folder/*/.png", # png files in /emoji/custom/special_folder/ subfolders
+    Finmoji: "/finmoji/128px/*-128.png",
+    Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
+  ]
+```
+
+Priority of tag assign in emoji.txt and custom.txt:
+
+`tag in file > special group setting in config.exs > default setting in config.exs`
+
+Priority for globs:
+
+`special group setting in config.exs > default setting in config.exs`

From 08d64b977f74abb7cb42bf985116eba91d9a6166 Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Tue, 2 Apr 2019 16:13:34 +0700
Subject: [PATCH 6/9] little changes and typos

---
 config/config.exs           | 2 +-
 docs/config/custom_emoji.md | 4 ++--
 lib/pleroma/emoji.ex        | 6 +++---
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index 4a22167b2..139ec0ace 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -57,7 +57,7 @@ config :pleroma, Pleroma.Uploaders.MDII,
 config :pleroma, :emoji,
   shortcode_globs: ["/emoji/custom/**/*.png"],
   groups: [
-    # Place here groups, which have more priority on defaults. Example in `docs/config/custom_emoji.md`
+    # Put groups that have higher priority than defaults here. Example in `docs/config/custom_emoji.md`
     Finmoji: "/finmoji/128px/*-128.png",
     Custom: ["/emoji/*.png", "/emoji/custom/*.png"]
   ]
diff --git a/docs/config/custom_emoji.md b/docs/config/custom_emoji.md
index d37220a72..49a451fcc 100644
--- a/docs/config/custom_emoji.md
+++ b/docs/config/custom_emoji.md
@@ -30,7 +30,7 @@ config :pleroma, :emoji,
   ]
 ```
 
-Order of the `groups` matters, so to override default tags just put your group on the top of the list. E.g:
+Order of the `groups` matters, so to override default tags just put your group on top of the list. E.g:
 ```
 config :pleroma, :emoji,
   shortcode_globs: ["/emoji/custom/**/*.png"],
@@ -44,7 +44,7 @@ config :pleroma, :emoji,
   ]
 ```
 
-Priority of tag assign in emoji.txt and custom.txt:
+Priority of tags assigns in emoji.txt and custom.txt:
 
 `tag in file > special group setting in config.exs > default setting in config.exs`
 
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index b60d19e89..7a60f3961 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -15,8 +15,8 @@ defmodule Pleroma.Emoji do
   use GenServer
 
   @type pattern :: Regex.t() | module() | String.t()
-  @type patterns :: pattern | [pattern]
-  @type group_patterns :: keyword(patterns)
+  @type patterns :: pattern() | [pattern()]
+  @type group_patterns :: keyword(patterns())
 
   @ets __MODULE__.Ets
   @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
@@ -80,7 +80,7 @@ defmodule Pleroma.Emoji do
 
   defp load do
     finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
-    shortcode_globs = Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
+    shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
 
     emojis =
       (load_finmoji(finmoji_enabled) ++

From 484162c18774ff28842a517ae0afcaaf824e12bf Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Tue, 2 Apr 2019 16:26:40 +0700
Subject: [PATCH 7/9] test fix

---
 test/formatter_test.exs | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index 38430e170..e74985c4e 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -271,10 +271,8 @@ defmodule Pleroma.FormatterTest do
   test "it returns the emoji used in the text" do
     text = "I love :moominmamma:"
 
-    tag = Keyword.get(Application.get_env(:pleroma, :emoji), :finmoji_tag)
-
     assert Formatter.get_emoji(text) == [
-             {"moominmamma", "/finmoji/128px/moominmamma-128.png", tag}
+             {"moominmamma", "/finmoji/128px/moominmamma-128.png", "Finmoji"}
            ]
   end
 

From 3465b7ba9ad0e26128f18fd4e36aece767ba269e Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Tue, 2 Apr 2019 20:32:37 +0700
Subject: [PATCH 8/9] syntax highlighting

---
 docs/config/custom_emoji.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/config/custom_emoji.md b/docs/config/custom_emoji.md
index 49a451fcc..96fcb2fc6 100644
--- a/docs/config/custom_emoji.md
+++ b/docs/config/custom_emoji.md
@@ -21,7 +21,7 @@ The files should be PNG (APNG is okay with `.png` for `image/png` Content-type)
 # Emoji tags (groups)
 
 Default tags are set in `config.exs`.
-```
+```elixir
 config :pleroma, :emoji,
   shortcode_globs: ["/emoji/custom/**/*.png"],
   groups: [
@@ -31,7 +31,7 @@ config :pleroma, :emoji,
 ```
 
 Order of the `groups` matters, so to override default tags just put your group on top of the list. E.g:
-```
+```elixir
 config :pleroma, :emoji,
   shortcode_globs: ["/emoji/custom/**/*.png"],
   groups: [

From d140738edf75467420b35c500716cf89de66548d Mon Sep 17 00:00:00 2001
From: Alex S <alex.strizhakov@gmail.com>
Date: Tue, 2 Apr 2019 20:35:41 +0700
Subject: [PATCH 9/9] second level of headertext change in doc

---
 docs/config/custom_emoji.md | 2 +-
 lib/pleroma/emoji.ex        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/config/custom_emoji.md b/docs/config/custom_emoji.md
index 96fcb2fc6..419a7d0e2 100644
--- a/docs/config/custom_emoji.md
+++ b/docs/config/custom_emoji.md
@@ -18,7 +18,7 @@ foo, /emoji/custom/foo.png
 
 The files should be PNG (APNG is okay with `.png` for `image/png` Content-type) and under 50kb for compatibility with mastodon.
 
-# Emoji tags (groups)
+## Emoji tags (groups)
 
 Default tags are set in `config.exs`.
 ```elixir
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index 7a60f3961..87c7f2cec 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -214,7 +214,7 @@ defmodule Pleroma.Emoji do
   end
 
   @doc """
-  Finds a matching group for the given extra filename
+  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