From 04f6b48ac1a76fe9c6c3fd573427d418bc152adf Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 31 Oct 2020 13:38:35 +0300 Subject: Auth subsystem refactoring and tweaks. Added proper OAuth skipping for SessionAuthenticationPlug. Integrated LegacyAuthenticationPlug into AuthenticationPlug. Adjusted tests & docs. --- lib/pleroma/helpers/auth_helper.ex | 17 ++++++ .../web/plugs/admin_secret_authentication_plug.ex | 4 +- lib/pleroma/web/plugs/authentication_plug.ex | 63 ++++++++++------------ lib/pleroma/web/plugs/basic_auth_decoder_plug.ex | 6 +++ lib/pleroma/web/plugs/ensure_user_key_plug.ex | 5 +- .../web/plugs/legacy_authentication_plug.ex | 41 -------------- .../web/plugs/session_authentication_plug.ex | 10 ++++ lib/pleroma/web/plugs/set_user_session_id_plug.ex | 3 +- lib/pleroma/web/plugs/user_fetcher_plug.ex | 6 +++ lib/pleroma/web/router.ex | 1 - 10 files changed, 73 insertions(+), 83 deletions(-) create mode 100644 lib/pleroma/helpers/auth_helper.ex delete mode 100644 lib/pleroma/web/plugs/legacy_authentication_plug.ex (limited to 'lib') diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex new file mode 100644 index 000000000..6e29c006a --- /dev/null +++ b/lib/pleroma/helpers/auth_helper.ex @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.AuthHelper do + alias Pleroma.Web.Plugs.OAuthScopesPlug + + @doc """ + Skips OAuth permissions (scopes) checks, assigns nil `:token`. + Intended to be used with explicit authentication and only when OAuth token cannot be determined. + """ + def skip_oauth(conn) do + conn + |> Plug.Conn.assign(:token, nil) + |> OAuthScopesPlug.skip_plug() + end +end diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex index d7d4e4092..ff49801f4 100644 --- a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex +++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex @@ -5,8 +5,8 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do import Plug.Conn + alias Pleroma.Helpers.AuthHelper alias Pleroma.User - alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter def init(options) do @@ -51,7 +51,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do defp assign_admin_user(conn) do conn |> assign(:user, %User{is_admin: true}) - |> OAuthScopesPlug.skip_plug() + |> AuthHelper.skip_oauth() end defp handle_bad_token(conn) do diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index e2a8b1b69..a7b8a9bfe 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -3,6 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.AuthenticationPlug do + @moduledoc "Password authentication plug." + + alias Pleroma.Helpers.AuthHelper alias Pleroma.User import Plug.Conn @@ -11,6 +14,30 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def init(options), do: options + def call(%{assigns: %{user: %User{}}} = conn, _), do: conn + + def call( + %{ + assigns: %{ + auth_user: %{password_hash: password_hash} = auth_user, + auth_credentials: %{password: password} + } + } = conn, + _ + ) do + if checkpw(password, password_hash) do + {:ok, auth_user} = maybe_update_password(auth_user, password) + + conn + |> assign(:user, auth_user) + |> AuthHelper.skip_oauth() + else + conn + end + end + + def call(conn, _), do: conn + def checkpw(password, "$6" <> _ = password_hash) do :crypt.crypt(password, password_hash) == password_hash end @@ -40,40 +67,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do - user - |> User.password_update_changeset(%{ - "password" => password, - "password_confirmation" => password - }) - |> Pleroma.Repo.update() + User.reset_password(user, %{password: password, password_confirmation: password}) end - - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - - def call( - %{ - assigns: %{ - auth_user: %{password_hash: password_hash} = auth_user, - auth_credentials: %{password: password} - } - } = conn, - _ - ) do - if checkpw(password, password_hash) do - {:ok, auth_user} = maybe_update_password(auth_user, password) - - conn - |> assign(:user, auth_user) - |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug() - else - conn - end - end - - def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do - Pbkdf2.no_user_verify() - conn - end - - def call(conn, _), do: conn end diff --git a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex index 4dadfb000..97529aedb 100644 --- a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex +++ b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex @@ -3,6 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do + @moduledoc """ + Decodes HTTP Basic Auth information and assigns `:auth_credentials`. + + NOTE: no checks are performed at this step, auth_credentials/username could be easily faked. + """ + import Plug.Conn def init(options) do diff --git a/lib/pleroma/web/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex index 70d3091f0..31608dbbf 100644 --- a/lib/pleroma/web/plugs/ensure_user_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_key_plug.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do import Plug.Conn + @moduledoc "Ensures `conn.assigns.user` is initialized." + def init(opts) do opts end @@ -12,7 +14,6 @@ defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do def call(%{assigns: %{user: _}} = conn, _), do: conn def call(conn, _) do - conn - |> assign(:user, nil) + assign(conn, :user, nil) end end diff --git a/lib/pleroma/web/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex deleted file mode 100644 index 2a54d0b59..000000000 --- a/lib/pleroma/web/plugs/legacy_authentication_plug.ex +++ /dev/null @@ -1,41 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do - import Plug.Conn - - alias Pleroma.User - - def init(options) do - options - end - - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - - def call( - %{ - assigns: %{ - auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user, - auth_credentials: %{password: password} - } - } = conn, - _ - ) do - with ^password_hash <- :crypt.crypt(password, password_hash), - {:ok, user} <- - User.reset_password(auth_user, %{password: password, password_confirmation: password}) do - conn - |> assign(:auth_user, user) - |> assign(:user, user) - |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug() - else - _ -> - conn - end - end - - def call(conn, _) do - conn - end -end diff --git a/lib/pleroma/web/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex index 6e176d553..51704e273 100644 --- a/lib/pleroma/web/plugs/session_authentication_plug.ex +++ b/lib/pleroma/web/plugs/session_authentication_plug.ex @@ -3,17 +3,27 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do + @moduledoc """ + Authenticates user by session-stored `:user_id` and request-contained username. + Username can be provided via HTTP Basic Auth (the password is not checked and can be anything). + """ + import Plug.Conn + alias Pleroma.Helpers.AuthHelper + def init(options) do options end + def call(%{assigns: %{user: %Pleroma.User{}}} = conn, _), do: conn + def call(conn, _) do with saved_user_id <- get_session(conn, :user_id), %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do conn |> assign(:user, conn.assigns.auth_user) + |> AuthHelper.skip_oauth() else _ -> conn end diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex index e520159e4..6ddb6b5e5 100644 --- a/lib/pleroma/web/plugs/set_user_session_id_plug.ex +++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex @@ -11,8 +11,7 @@ defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do end def call(%{assigns: %{user: %User{id: id}}} = conn, _) do - conn - |> put_session(:user_id, id) + put_session(conn, :user_id, id) end def call(conn, _), do: conn diff --git a/lib/pleroma/web/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex index 4039600da..89e16b49f 100644 --- a/lib/pleroma/web/plugs/user_fetcher_plug.ex +++ b/lib/pleroma/web/plugs/user_fetcher_plug.ex @@ -3,6 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.UserFetcherPlug do + @moduledoc """ + Assigns `:auth_user` basing on `:auth_credentials`. + + NOTE: no checks are performed at this step, auth_credentials/username could be easily faked. + """ + alias Pleroma.User import Plug.Conn diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 76ca2c9b5..9da10f1e5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -49,7 +49,6 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug) plug(Pleroma.Web.Plugs.UserFetcherPlug) plug(Pleroma.Web.Plugs.SessionAuthenticationPlug) - plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug) plug(Pleroma.Web.Plugs.AuthenticationPlug) end -- cgit v1.2.3 From 1830b6aae5e3fa0dfebcadd6f4b78871f702dd2d Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 13 Nov 2020 15:13:14 +0300 Subject: added error messages for posix error code --- lib/pleroma/emoji/pack.ex | 55 +++++++++++-------- lib/pleroma/utils.ex | 16 ++++++ .../controllers/emoji_file_controller.ex | 37 +++++++++---- .../controllers/emoji_pack_controller.ex | 63 +++++++++++++++------- 4 files changed, 122 insertions(+), 49 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index ca58e5432..4f4e84bfe 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -22,14 +22,14 @@ defmodule Pleroma.Emoji.Pack do alias Pleroma.Emoji alias Pleroma.Emoji.Pack + alias Pleroma.Utils @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} def create(name) do with :ok <- validate_not_empty([name]), dir <- Path.join(emoji_path(), name), :ok <- File.mkdir(dir) do - %__MODULE__{pack_file: Path.join(dir, "pack.json")} - |> save_pack() + save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")}) end end @@ -94,7 +94,7 @@ defmodule Pleroma.Emoji.Pack do def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do with {:ok, zip_files} <- :zip.table(to_charlist(file.path)), [_ | _] = emojies <- unpack_zip_emojies(zip_files), - {:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do + {:ok, tmp_dir} <- Utils.tmp_dir("emoji") do try do {:ok, _emoji_files} = :zip.unzip( @@ -282,18 +282,21 @@ defmodule Pleroma.Emoji.Pack do end end - @spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found} + @spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()} def load_pack(name) do pack_file = Path.join([emoji_path(), name, "pack.json"]) - if File.exists?(pack_file) do + with {:ok, _} <- File.stat(pack_file), + {:ok, pack_data} <- File.read(pack_file) do pack = - pack_file - |> File.read!() - |> from_json() - |> Map.put(:pack_file, pack_file) - |> Map.put(:path, Path.dirname(pack_file)) - |> Map.put(:name, name) + from_json( + pack_data, + %{ + pack_file: pack_file, + path: Path.dirname(pack_file), + name: name + } + ) files_count = pack.files @@ -301,8 +304,6 @@ defmodule Pleroma.Emoji.Pack do |> length() {:ok, Map.put(pack, :files_count, files_count)} - else - {:error, :not_found} end end @@ -434,10 +435,17 @@ defmodule Pleroma.Emoji.Pack do end end - defp from_json(json) do + defp from_json(json, attrs) do map = Jason.decode!(json) - struct(__MODULE__, %{files: map["files"], pack: map["pack"]}) + pack_attrs = + attrs + |> Map.merge(%{ + files: map["files"], + pack: map["pack"] + }) + + struct(__MODULE__, pack_attrs) end defp validate_shareable_packs_available(uri) do @@ -491,10 +499,10 @@ defmodule Pleroma.Emoji.Pack do end defp create_subdirs(file_path) do - if String.contains?(file_path, "/") do - file_path - |> Path.dirname() - |> File.mkdir_p!() + with true <- String.contains?(file_path, "/"), + path <- Path.dirname(file_path), + false <- File.exists?(path) do + File.mkdir_p!(path) end end @@ -518,10 +526,15 @@ defmodule Pleroma.Emoji.Pack do defp get_filename(pack, shortcode) do with %{^shortcode => filename} when is_binary(filename) <- pack.files, - true <- pack.path |> Path.join(filename) |> File.exists?() do + file_path <- Path.join(pack.path, filename), + {:ok, _} <- File.stat(file_path) do {:ok, filename} else - _ -> {:error, :doesnt_exist} + {:error, _} = error -> + error + + _ -> + {:error, :doesnt_exist} end end diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index e95766223..fa75a8c99 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -3,6 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Utils do + @posix_error_codes ~w( + eacces eagain ebadf ebadmsg ebusy edeadlk edeadlock edquot eexist efault + efbig eftype eintr einval eio eisdir eloop emfile emlink emultihop + enametoolong enfile enobufs enodev enolck enolink enoent enomem enospc + enosr enostr enosys enotblk enotdir enotsup enxio eopnotsupp eoverflow + eperm epipe erange erofs espipe esrch estale etxtbsy exdev + )a + def compile_dir(dir) when is_binary(dir) do dir |> File.ls!() @@ -44,4 +52,12 @@ defmodule Pleroma.Utils do error -> error end end + + @spec posix_error_message(atom()) :: binary() + def posix_error_message(code) when code in @posix_error_codes do + error_message = Gettext.dgettext(Pleroma.Web.Gettext, "posix_errors", "#{code}") + "(POSIX error: #{error_message})" + end + + def posix_error_message(_), do: "" end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex index 428c97de6..c15980ff0 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -42,7 +42,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "pack name, shortcode or filename cannot be empty"}) {:error, _} = error -> - handle_error(conn, error, %{pack_name: pack_name}) + handle_error(conn, error, %{ + pack_name: pack_name, + message: "Unexpected error occurred while adding file to pack." + }) end end @@ -69,7 +72,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "new_shortcode or new_filename cannot be empty"}) {:error, _} = error -> - handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) + handle_error(conn, error, %{ + pack_name: pack_name, + code: shortcode, + message: "Unexpected error occurred while updating." + }) end end @@ -84,7 +91,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "pack name or shortcode cannot be empty"}) {:error, _} = error -> - handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) + handle_error(conn, error, %{ + pack_name: pack_name, + code: shortcode, + message: "Unexpected error occurred while deleting emoji file." + }) end end @@ -94,18 +105,24 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) end - defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do + defp handle_error(conn, {:error, :enoent}, %{pack_name: pack_name}) do conn |> put_status(:not_found) |> json(%{error: "pack \"#{pack_name}\" is not found"}) end - defp handle_error(conn, {:error, _}, _) do - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while adding file to pack." - ) + defp handle_error(conn, {:error, error}, opts) do + message = + [ + Map.get(opts, :message, "Unexpected error occurred."), + Pleroma.Utils.posix_error_message(error) + ] + |> Enum.join(" ") + |> String.trim() + + conn + |> put_status(:internal_server_error) + |> json(%{error: message}) end defp get_filename(%Plug.Upload{filename: filename}), do: filename diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index a9accc5af..2fb29d34e 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do json(conn, pack) else - {:error, :not_found} -> + {:error, :enoent} -> conn |> put_status(:not_found) |> json(%{error: "Pack #{name} does not exist"}) @@ -80,6 +80,17 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do conn |> put_status(:bad_request) |> json(%{error: "pack name cannot be empty"}) + + {:error, error} -> + error_message = + add_posix_error( + "Failed to get the contents of the `#{name}` pack.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) end end @@ -95,7 +106,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" }) - {:error, :not_found} -> + {:error, :enoent} -> conn |> put_status(:not_found) |> json(%{error: "Pack #{name} does not exist"}) @@ -116,10 +127,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:internal_server_error) |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) - {:error, e} -> + {:error, error} -> conn |> put_status(:internal_server_error) - |> json(%{error: e}) + |> json(%{error: error}) end end @@ -139,12 +150,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:bad_request) |> json(%{error: "pack name cannot be empty"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while creating pack." - ) + {:error, error} -> + error_message = + add_posix_error( + "Unexpected error occurred while creating pack.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) end end @@ -164,10 +179,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:bad_request) |> json(%{error: "pack name cannot be empty"}) - {:error, _, _} -> + {:error, error, _} -> + error_message = add_posix_error("Couldn't delete the pack #{name}", error) + conn |> put_status(:internal_server_error) - |> json(%{error: "Couldn't delete the pack #{name}"}) + |> json(%{error: error_message}) end end @@ -180,12 +197,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:bad_request) |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while updating pack metadata." - ) + {:error, error} -> + error_message = + add_posix_error( + "Unexpected error occurred while updating pack metadata.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) end end @@ -204,4 +225,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> json(%{error: "Error accessing emoji pack directory"}) end end + + defp add_posix_error(msg, error) do + [msg, Pleroma.Utils.posix_error_message(error)] + |> Enum.join(" ") + |> String.trim() + end end -- cgit v1.2.3 From 36ec6045214a69cd958c00eb6d37852fff1c7d08 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sat, 14 Nov 2020 08:30:22 +0300 Subject: added test --- lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index 79f52dcb3..e576ccbad 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -169,7 +169,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do responses: %{ 200 => ok_response(), 400 => Operation.response("Bad Request", "application/json", ApiError), - 404 => Operation.response("Not Found", "application/json", ApiError) + 404 => Operation.response("Not Found", "application/json", ApiError), + 500 => Operation.response("Error", "application/json", ApiError) } } end @@ -184,7 +185,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do parameters: [name_param()], responses: %{ 200 => Operation.response("Metadata", "application/json", metadata()), - 400 => Operation.response("Bad Request", "application/json", ApiError) + 400 => Operation.response("Bad Request", "application/json", ApiError), + 500 => Operation.response("Error", "application/json", ApiError) } } end -- cgit v1.2.3 From e1d25bad0c91f903ef6d8c7a2c5d7f2d63213d85 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 16 Nov 2020 21:45:37 +0300 Subject: fix tests --- lib/pleroma/emoji/pack.ex | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 4f4e84bfe..f768af19f 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -62,10 +62,9 @@ defmodule Pleroma.Emoji.Pack do @spec delete(String.t()) :: {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} def delete(name) do - with :ok <- validate_not_empty([name]) do - emoji_path() - |> Path.join(name) - |> File.rm_rf() + with :ok <- validate_not_empty([name]), + pack_path <- Path.join(emoji_path(), name) do + File.rm_rf(pack_path) end end -- cgit v1.2.3 From e4b202d905f4d2ec433862884f34729257990edf Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Mon, 16 Nov 2020 22:23:28 +0300 Subject: added test --- lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index a56641426..747f17e7f 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -27,7 +27,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do 422 => Operation.response("Unprocessable Entity", "application/json", ApiError), 404 => Operation.response("Not Found", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) + 409 => Operation.response("Conflict", "application/json", ApiError), + 500 => Operation.response("Error", "application/json", ApiError) } } end -- cgit v1.2.3 From 25eb222bed8c55a81e1ee4c17e05834d0b894030 Mon Sep 17 00:00:00 2001 From: Maksim Date: Wed, 18 Nov 2020 05:19:01 +0000 Subject: Apply 1 suggestion(s) to 1 file(s) --- lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index 2fb29d34e..d2e869e6e 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -180,7 +180,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> json(%{error: "pack name cannot be empty"}) {:error, error, _} -> - error_message = add_posix_error("Couldn't delete the pack #{name}", error) + error_message = add_posix_error("Couldn't delete the #{name} pack", error) conn |> put_status(:internal_server_error) -- cgit v1.2.3 From 9c5d1cb9ed41dafea5db5637151a4568a9372d03 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 18 Nov 2020 09:58:51 +0300 Subject: fix tests --- lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index d2e869e6e..bc4c8d840 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -180,7 +180,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> json(%{error: "pack name cannot be empty"}) {:error, error, _} -> - error_message = add_posix_error("Couldn't delete the #{name} pack", error) + error_message = add_posix_error("Couldn't delete the `#{name}` pack", error) conn |> put_status(:internal_server_error) -- cgit v1.2.3 From a60242464e6a92bf6de46a1cf7877799de27a3ce Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 19 Nov 2020 16:12:01 +0100 Subject: Search: Add option to search with the websearch function --- lib/pleroma/activity/search.ex | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index ceb365bb3..8449b9b00 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -19,11 +19,13 @@ defmodule Pleroma.Activity.Search do offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) + search_function = Pleroma.Config.get([:instance, :search_function], :plain) + Activity |> Activity.with_preloaded_object() |> Activity.restrict_deactivated_users() |> restrict_public() - |> query_with(index_type, search_query) + |> query_with(index_type, search_query, search_function) |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> maybe_restrict_blocked(user) @@ -50,7 +52,7 @@ defmodule Pleroma.Activity.Search do ) end - defp query_with(q, :gin, search_query) do + defp query_with(q, :gin, search_query, :plain) do from([a, o] in q, where: fragment( @@ -61,7 +63,18 @@ defmodule Pleroma.Activity.Search do ) end - defp query_with(q, :rum, search_query) do + defp query_with(q, :gin, search_query, :websearch) do + from([a, o] in q, + where: + fragment( + "to_tsvector('english', ?->>'content') @@ websearch_to_tsquery('english', ?)", + o.data, + ^search_query + ) + ) + end + + defp query_with(q, :rum, search_query, :plain) do from([a, o] in q, where: fragment( @@ -73,6 +86,18 @@ defmodule Pleroma.Activity.Search do ) end + defp query_with(q, :rum, search_query, :websearch) do + from([a, o] in q, + where: + fragment( + "? @@ websearch_to_tsquery('english', ?)", + o.fts_content, + ^search_query + ), + order_by: [fragment("? <=> now()::date", o.inserted_at)] + ) + end + defp maybe_restrict_local(q, user) do limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) -- cgit v1.2.3 From 66f411fba0ecb350a2cd80293aabdecf402abaf9 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Thu, 19 Nov 2020 22:13:45 +0300 Subject: added subject actor to moderation log --- lib/pleroma/activity.ex | 13 +++ lib/pleroma/moderation_log.ex | 122 +++++++++++++-------- .../web/admin_api/controllers/report_controller.ex | 17 ++- 3 files changed, 102 insertions(+), 50 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 553834da0..d2066f7a0 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -194,6 +194,19 @@ defmodule Pleroma.Activity do end end + def get_by_id_with_user_actor(id) do + case FlakeId.flake_id?(id) do + true -> + Activity + |> where([a], a.id == ^id) + |> with_preloaded_user_actor() + |> Repo.one() + + _ -> + nil + end + end + def get_by_id_with_object(id) do Activity |> where(id: ^id) diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 142dd8e0a..0a701127f 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -112,16 +112,19 @@ defmodule Pleroma.ModerationLog do @spec insert_log(%{actor: User, subject: User, action: String.t()}) :: {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - action: "report_update", - subject: %Activity{data: %{"type" => "Flag"}} = subject - }) do + def insert_log( + %{ + actor: %User{} = actor, + action: "report_update", + subject: %Activity{data: %{"type" => "Flag"}} = subject + } = attrs + ) do %ModerationLog{ data: %{ "actor" => user_to_map(actor), "action" => "report_update", "subject" => report_to_map(subject), + "subject_actor" => user_to_map(attrs[:subject_actor]), "message" => "" } } @@ -130,17 +133,20 @@ defmodule Pleroma.ModerationLog do @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - action: "report_note", - subject: %Activity{} = subject, - text: text - }) do + def insert_log( + %{ + actor: %User{} = actor, + action: "report_note", + subject: %Activity{} = subject, + text: text + } = attrs + ) do %ModerationLog{ data: %{ "actor" => user_to_map(actor), "action" => "report_note", "subject" => report_to_map(subject), + "subject_actor" => user_to_map(attrs[:subject_actor]), "text" => text } } @@ -149,17 +155,20 @@ defmodule Pleroma.ModerationLog do @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - action: "report_note_delete", - subject: %Activity{} = subject, - text: text - }) do + def insert_log( + %{ + actor: %User{} = actor, + action: "report_note_delete", + subject: %Activity{} = subject, + text: text + } = attrs + ) do %ModerationLog{ data: %{ "actor" => user_to_map(actor), "action" => "report_note_delete", "subject" => report_to_map(subject), + "subject_actor" => user_to_map(attrs[:subject_actor]), "text" => text } } @@ -345,17 +354,18 @@ defmodule Pleroma.ModerationLog do end defp user_to_map(users) when is_list(users) do - users |> Enum.map(&user_to_map/1) + Enum.map(users, &user_to_map/1) end defp user_to_map(%User{} = user) do user - |> Map.from_struct() |> Map.take([:id, :nickname]) |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end) |> Map.put("type", "user") end + defp user_to_map(_), do: nil + defp report_to_map(%Activity{} = report) do %{ "type" => "report", @@ -512,38 +522,48 @@ defmodule Pleroma.ModerationLog do end @spec get_log_entry_message(ModerationLog) :: String.t() - def get_log_entry_message(%ModerationLog{ - data: %{ - "actor" => %{"nickname" => actor_nickname}, - "action" => "report_update", - "subject" => %{"id" => subject_id, "state" => state, "type" => "report"} - } - }) do - "@#{actor_nickname} updated report ##{subject_id} with '#{state}' state" + def get_log_entry_message( + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "report_update", + "subject" => %{"id" => subject_id, "state" => state, "type" => "report"} + } + } = log + ) do + "@#{actor_nickname} updated report ##{subject_id}" <> + subject_actor_nickname(log, " (on user ", ")") <> + " with '#{state}' state" end @spec get_log_entry_message(ModerationLog) :: String.t() - def get_log_entry_message(%ModerationLog{ - data: %{ - "actor" => %{"nickname" => actor_nickname}, - "action" => "report_note", - "subject" => %{"id" => subject_id, "type" => "report"}, - "text" => text - } - }) do - "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" + def get_log_entry_message( + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "report_note", + "subject" => %{"id" => subject_id, "type" => "report"}, + "text" => text + } + } = log + ) do + "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" <> + subject_actor_nickname(log, " on user ") end @spec get_log_entry_message(ModerationLog) :: String.t() - def get_log_entry_message(%ModerationLog{ - data: %{ - "actor" => %{"nickname" => actor_nickname}, - "action" => "report_note_delete", - "subject" => %{"id" => subject_id, "type" => "report"}, - "text" => text - } - }) do - "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" + def get_log_entry_message( + %ModerationLog{ + data: %{ + "actor" => %{"nickname" => actor_nickname}, + "action" => "report_note_delete", + "subject" => %{"id" => subject_id, "type" => "report"}, + "text" => text + } + } = log + ) do + "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}" <> + subject_actor_nickname(log, " on user ") end @spec get_log_entry_message(ModerationLog) :: String.t() @@ -676,4 +696,16 @@ defmodule Pleroma.ModerationLog do |> Enum.map(&"@#{&1["nickname"]}") |> Enum.join(", ") end + + defp subject_actor_nickname(%ModerationLog{data: data}, prefix_msg, postfix_msg \\ "") do + case data do + %{"subject_actor" => %{"nickname" => subject_actor}} -> + [prefix_msg, "@#{subject_actor}", postfix_msg] + |> Enum.reject(&(&1 == "")) + |> Enum.join() + + _ -> + "" + end + end end diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex index 6a0e56f5f..cc77cbfdf 100644 --- a/lib/pleroma/web/admin_api/controllers/report_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex @@ -50,10 +50,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do Enum.map(reports, fn report -> case CommonAPI.update_report_state(report.id, report.state) do {:ok, activity} -> + report = Activity.get_by_id_with_user_actor(activity.id) + ModerationLog.insert_log(%{ action: "report_update", actor: admin, - subject: activity + subject: activity, + subject_actor: report.user_actor }) activity @@ -73,11 +76,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do def notes_create(%{assigns: %{user: user}, body_params: %{content: content}} = conn, %{ id: report_id }) do - with {:ok, _} <- ReportNote.create(user.id, report_id, content) do + with {:ok, _} <- ReportNote.create(user.id, report_id, content), + report <- Activity.get_by_id_with_user_actor(report_id) do ModerationLog.insert_log(%{ action: "report_note", actor: user, - subject: Activity.get_by_id(report_id), + subject: report, + subject_actor: report.user_actor, text: content }) @@ -91,11 +96,13 @@ defmodule Pleroma.Web.AdminAPI.ReportController do id: note_id, report_id: report_id }) do - with {:ok, note} <- ReportNote.destroy(note_id) do + with {:ok, note} <- ReportNote.destroy(note_id), + report <- Activity.get_by_id_with_user_actor(report_id) do ModerationLog.insert_log(%{ action: "report_note_delete", actor: user, - subject: Activity.get_by_id(report_id), + subject: report, + subject_actor: report.user_actor, text: note.content }) -- cgit v1.2.3 From a407e33c78121abf880f257d291f45ed28b55eeb Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 20 Nov 2020 16:26:22 +0100 Subject: Application: Save postgres version in the environment --- lib/pleroma/application.ex | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 8f08a6222..f2a8c7825 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -109,7 +109,28 @@ defmodule Pleroma.Application do # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Pleroma.Supervisor] - Supervisor.start_link(children, opts) + result = Supervisor.start_link(children, opts) + + set_postgres_server_version() + + result + end + + defp set_postgres_server_version() do + version = + with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"), + {num, _} <- Float.parse(version) do + num + else + e -> + Logger.warn( + "Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6" + ) + + 9.6 + end + + Application.put_env(:postgres, :version, version) end def load_custom_modules do -- cgit v1.2.3 From 9a1e5f5d48ef9f3b5a817c02dc8820aa99a6f693 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 20 Nov 2020 16:26:43 +0100 Subject: Search: Change search method based on detected pg version --- lib/pleroma/activity/search.ex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index cc98e2d06..ea9783225 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -19,7 +19,12 @@ defmodule Pleroma.Activity.Search do offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) - search_function = Pleroma.Config.get([:instance, :search_function], :plain) + search_function = + if Application.get_env(:postgres, :version) >= 11 do + :websearch + else + :plain + end Activity |> Activity.with_preloaded_object() -- cgit v1.2.3 From e4289792d28cb38c520e03df2ed82f6f30eb4c51 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 20 Nov 2020 16:38:05 +0100 Subject: Linting. --- lib/pleroma/application.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index f2a8c7825..17a241cdf 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -116,7 +116,7 @@ defmodule Pleroma.Application do result end - defp set_postgres_server_version() do + defp set_postgres_server_version do version = with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"), {num, _} <- Float.parse(version) do -- cgit v1.2.3 From ccc2cf0e87f47618163da588ead76846c64cba7a Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 21 Nov 2020 19:47:25 +0300 Subject: Session-based OAuth auth fixes (token expiration check), refactoring, tweaks. --- lib/pleroma/helpers/auth_helper.ex | 10 ++- lib/pleroma/web/o_auth/o_auth_controller.ex | 10 ++- lib/pleroma/web/o_auth/token.ex | 8 ++ lib/pleroma/web/plugs/o_auth_plug.ex | 92 +++++++++------------- .../web/plugs/session_authentication_plug.ex | 31 -------- lib/pleroma/web/plugs/set_user_session_id_plug.ex | 7 +- lib/pleroma/web/plugs/user_enabled_plug.ex | 9 ++- lib/pleroma/web/router.ex | 12 ++- 8 files changed, 84 insertions(+), 95 deletions(-) delete mode 100644 lib/pleroma/web/plugs/session_authentication_plug.ex (limited to 'lib') diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex index 6e29c006a..878fec346 100644 --- a/lib/pleroma/helpers/auth_helper.ex +++ b/lib/pleroma/helpers/auth_helper.ex @@ -5,13 +5,21 @@ defmodule Pleroma.Helpers.AuthHelper do alias Pleroma.Web.Plugs.OAuthScopesPlug + import Plug.Conn + @doc """ Skips OAuth permissions (scopes) checks, assigns nil `:token`. Intended to be used with explicit authentication and only when OAuth token cannot be determined. """ def skip_oauth(conn) do conn - |> Plug.Conn.assign(:token, nil) + |> assign(:token, nil) |> OAuthScopesPlug.skip_plug() end + + def drop_auth_info(conn) do + conn + |> assign(:user, nil) + |> assign(:token, nil) + end end diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index d2f9d1ceb..83a25907d 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -363,7 +363,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, _token} <- RevokeToken.revoke(app, params) do + {:ok, %Token{} = oauth_token} <- RevokeToken.revoke(app, params) do + conn = + with session_token = get_session(conn, :oauth_token), + %Token{token: ^session_token} <- oauth_token do + delete_session(conn, :oauth_token) + else + _ -> conn + end + json(conn, %{}) else _error -> diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex index de37998f2..9170a7ec7 100644 --- a/lib/pleroma/web/o_auth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -27,6 +27,14 @@ defmodule Pleroma.Web.OAuth.Token do timestamps() end + @doc "Gets token by unique access token" + @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found} + def get_by_token(token) do + token + |> Query.get_by_token() + |> Repo.find_resource() + end + @doc "Gets token for app by access token" @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(%App{id: app_id} = _app, token) do diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index c7b58d90f..a3b7d42f7 100644 --- a/lib/pleroma/web/plugs/o_auth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.OAuthPlug do + @moduledoc "Performs OAuth authentication by token from params / headers / cookies." + import Plug.Conn import Ecto.Query @@ -17,45 +19,26 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - def call(%{params: %{"access_token" => access_token}} = conn, _) do - with {:ok, user, token_record} <- fetch_user_and_token(access_token) do - conn - |> assign(:token, token_record) - |> assign(:user, user) - else - _ -> - # token found, but maybe only with app - with {:ok, app, token_record} <- fetch_app_and_token(access_token) do - conn - |> assign(:token, token_record) - |> assign(:app, app) - else - _ -> conn - end - end - end - def call(conn, _) do - case fetch_token_str(conn) do - {:ok, token} -> - with {:ok, user, token_record} <- fetch_user_and_token(token) do - conn - |> assign(:token, token_record) - |> assign(:user, user) - else - _ -> - # token found, but maybe only with app - with {:ok, app, token_record} <- fetch_app_and_token(token) do - conn - |> assign(:token, token_record) - |> assign(:app, app) - else - _ -> conn - end - end - - _ -> + with {:ok, token_str} <- fetch_token_str(conn) do + with {:ok, user, user_token} <- fetch_user_and_token(token_str), + false <- Token.is_expired?(user_token) do conn + |> assign(:token, user_token) + |> assign(:user, user) + else + _ -> + with {:ok, app, app_token} <- fetch_app_and_token(token_str), + false <- Token.is_expired?(app_token) do + conn + |> assign(:token, app_token) + |> assign(:app, app) + else + _ -> conn + end + end + else + _ -> conn end end @@ -70,7 +53,6 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do preload: [user: user] ) - # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength with %Token{user: user} = token_record <- Repo.one(query) do {:ok, user, token_record} end @@ -86,29 +68,23 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do end end - # Gets token from session by :oauth_token key + # Gets token string from conn (in params / headers / session) # - @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_from_session(conn) do - case get_session(conn, :oauth_token) do - nil -> :no_token_found - token -> {:ok, token} - end + @spec fetch_token_str(Plug.Conn.t() | list(String.t())) :: :no_token_found | {:ok, String.t()} + defp fetch_token_str(%Plug.Conn{params: %{"access_token" => access_token}} = _conn) do + {:ok, access_token} end - # Gets token from headers - # - @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} defp fetch_token_str(%Plug.Conn{} = conn) do headers = get_req_header(conn, "authorization") - with :no_token_found <- fetch_token_str(headers), - do: fetch_token_from_session(conn) + with {:ok, token} <- fetch_token_str(headers) do + {:ok, token} + else + _ -> fetch_token_from_session(conn) + end end - @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_str([]), do: :no_token_found - defp fetch_token_str([token | tail]) do trimmed_token = String.trim(token) @@ -117,4 +93,14 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do _ -> fetch_token_str(tail) end end + + defp fetch_token_str([]), do: :no_token_found + + @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} + defp fetch_token_from_session(conn) do + case get_session(conn, :oauth_token) do + nil -> :no_token_found + token -> {:ok, token} + end + end end diff --git a/lib/pleroma/web/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex deleted file mode 100644 index 51704e273..000000000 --- a/lib/pleroma/web/plugs/session_authentication_plug.ex +++ /dev/null @@ -1,31 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do - @moduledoc """ - Authenticates user by session-stored `:user_id` and request-contained username. - Username can be provided via HTTP Basic Auth (the password is not checked and can be anything). - """ - - import Plug.Conn - - alias Pleroma.Helpers.AuthHelper - - def init(options) do - options - end - - def call(%{assigns: %{user: %Pleroma.User{}}} = conn, _), do: conn - - def call(conn, _) do - with saved_user_id <- get_session(conn, :user_id), - %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do - conn - |> assign(:user, conn.assigns.auth_user) - |> AuthHelper.skip_oauth() - else - _ -> conn - end - end -end diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex index 6ddb6b5e5..d2338c03f 100644 --- a/lib/pleroma/web/plugs/set_user_session_id_plug.ex +++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex @@ -4,14 +4,15 @@ defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do import Plug.Conn - alias Pleroma.User + + alias Pleroma.Web.OAuth.Token def init(opts) do opts end - def call(%{assigns: %{user: %User{id: id}}} = conn, _) do - put_session(conn, :user_id, id) + def call(%{assigns: %{token: %Token{} = oauth_token}} = conn, _) do + put_session(conn, :oauth_token, oauth_token.token) end def call(conn, _), do: conn diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex index fa28ee48b..291d1f568 100644 --- a/lib/pleroma/web/plugs/user_enabled_plug.ex +++ b/lib/pleroma/web/plugs/user_enabled_plug.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.UserEnabledPlug do - import Plug.Conn + alias Pleroma.Helpers.AuthHelper alias Pleroma.User def init(options) do @@ -12,8 +12,11 @@ defmodule Pleroma.Web.Plugs.UserEnabledPlug do def call(%{assigns: %{user: %User{} = user}} = conn, _) do case User.account_status(user) do - :active -> conn - _ -> assign(conn, :user, nil) + :active -> + conn + + _ -> + AuthHelper.drop_auth_info(conn) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c075fc7d3..2b8b3e95c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -34,6 +34,7 @@ defmodule Pleroma.Web.Router do plug(:fetch_session) plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.UserEnabledPlug) + plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) end pipeline :expect_authentication do @@ -48,7 +49,6 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug) plug(Pleroma.Web.Plugs.UserFetcherPlug) - plug(Pleroma.Web.Plugs.SessionAuthenticationPlug) plug(Pleroma.Web.Plugs.AuthenticationPlug) end @@ -319,18 +319,24 @@ defmodule Pleroma.Web.Router do scope "/oauth", Pleroma.Web.OAuth do scope [] do pipe_through(:oauth) + get("/authorize", OAuthController, :authorize) + post("/authorize", OAuthController, :create_authorization) end - post("/authorize", OAuthController, :create_authorization) post("/token", OAuthController, :token_exchange) - post("/revoke", OAuthController, :token_revoke) get("/registration_details", OAuthController, :registration_details) post("/mfa/challenge", MFAController, :challenge) post("/mfa/verify", MFAController, :verify, as: :mfa_verify) get("/mfa", MFAController, :show) + scope [] do + pipe_through(:fetch_session) + + post("/revoke", OAuthController, :token_revoke) + end + scope [] do pipe_through(:browser) -- cgit v1.2.3 From 67b15cc033fd1154d1e6a96a5c5f141921c2e688 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 23 Nov 2020 15:29:55 +0100 Subject: Search: Save detected pg version in a persistent term. --- lib/pleroma/activity/search.ex | 2 +- lib/pleroma/application.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index ea9783225..babf9520b 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Activity.Search do author = Keyword.get(options, :author) search_function = - if Application.get_env(:postgres, :version) >= 11 do + if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do :websearch else :plain diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 22936bd7f..bd568d858 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -131,7 +131,7 @@ defmodule Pleroma.Application do 9.6 end - Application.put_env(:postgres, :version, version) + :persistent_term.put({Pleroma.Repo, :postgres_version}, version) end def load_custom_modules do -- cgit v1.2.3 From 5eef4988bf968e12329e6e4ee89beccee19a66ce Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Tue, 24 Nov 2020 18:44:48 +0300 Subject: fix for elixir 1.11 load runtime configs in releases with config provider --- lib/pleroma/config/holder.ex | 19 ++++++---- lib/pleroma/config/release_runtime_provider.ex | 50 ++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 lib/pleroma/config/release_runtime_provider.ex (limited to 'lib') diff --git a/lib/pleroma/config/holder.ex b/lib/pleroma/config/holder.ex index f037d5d48..a99fc0471 100644 --- a/lib/pleroma/config/holder.ex +++ b/lib/pleroma/config/holder.ex @@ -9,12 +9,7 @@ defmodule Pleroma.Config.Holder do def save_default do default_config = if System.get_env("RELEASE_NAME") do - release_config = - [:code.root_dir(), "releases", System.get_env("RELEASE_VSN"), "releases.exs"] - |> Path.join() - |> Pleroma.Config.Loader.read() - - Pleroma.Config.Loader.merge(@config, release_config) + Pleroma.Config.Loader.merge(@config, release_defaults()) else @config end @@ -32,4 +27,16 @@ defmodule Pleroma.Config.Holder do def default_config(group, key), do: get_in(get_default(), [group, key]) defp get_default, do: Pleroma.Config.get(:default_config) + + @spec release_defaults() :: keyword() + def release_defaults do + [ + pleroma: [ + {:instance, [static_dir: "/var/lib/pleroma/static"]}, + {Pleroma.Uploaders.Local, [uploads: "/var/lib/pleroma/uploads"]}, + {:modules, [runtime_dir: "/var/lib/pleroma/modules"]}, + {:release, true} + ] + ] + end end diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex new file mode 100644 index 000000000..8227195dc --- /dev/null +++ b/lib/pleroma/config/release_runtime_provider.ex @@ -0,0 +1,50 @@ +defmodule Pleroma.Config.ReleaseRuntimeProvider do + @moduledoc """ + Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases. + """ + @behaviour Config.Provider + + @impl true + def init(opts), do: opts + + @impl true + def load(config, _opts) do + with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults()) + + config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs" + + with_runtime_config = + if File.exists?(config_path) do + runtime_config = Config.Reader.read!(config_path) + + with_defaults + |> Config.Reader.merge(pleroma: [config_path: config_path]) + |> Config.Reader.merge(runtime_config) + else + warning = [ + IO.ANSI.red(), + IO.ANSI.bright(), + "!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file", + IO.ANSI.reset() + ] + + IO.puts(warning) + with_defaults + end + + exported_config_path = + config_path + |> Path.dirname() + |> Path.join("prod.exported_from_db.secret.exs") + + with_exported = + if File.exists?(exported_config_path) do + exported_config = Config.Reader.read!(with_runtime_config) + Config.Reader.merge(with_runtime_config, exported_config) + else + with_runtime_config + end + + with_exported + end +end -- cgit v1.2.3 From 12a5981cc3da65b7f2763d0ec05871b0986234f5 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 25 Nov 2020 21:47:23 +0300 Subject: Session token setting on token exchange. Auth-related refactoring. --- lib/pleroma/helpers/auth_helper.ex | 15 +++++++++++++++ .../mastodon_api/controllers/account_controller.ex | 3 +-- .../web/mastodon_api/controllers/auth_controller.ex | 5 +++-- lib/pleroma/web/o_auth/mfa_controller.ex | 3 +-- lib/pleroma/web/o_auth/o_auth_controller.ex | 19 +++++++++++++------ lib/pleroma/web/plugs/o_auth_plug.ex | 3 ++- lib/pleroma/web/plugs/set_user_session_id_plug.ex | 5 ++--- lib/pleroma/web/router.ex | 14 +++++++------- 8 files changed, 44 insertions(+), 23 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex index 878fec346..392fa7d5d 100644 --- a/lib/pleroma/helpers/auth_helper.ex +++ b/lib/pleroma/helpers/auth_helper.ex @@ -4,9 +4,12 @@ defmodule Pleroma.Helpers.AuthHelper do alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Plug.Conn import Plug.Conn + @oauth_token_session_key :oauth_token + @doc """ Skips OAuth permissions (scopes) checks, assigns nil `:token`. Intended to be used with explicit authentication and only when OAuth token cannot be determined. @@ -22,4 +25,16 @@ defmodule Pleroma.Helpers.AuthHelper do |> assign(:user, nil) |> assign(:token, nil) end + + def get_session_token(%Conn{} = conn) do + get_session(conn, @oauth_token_session_key) + end + + def put_session_token(%Conn{} = conn, token) when is_binary(token) do + put_session(conn, @oauth_token_session_key, token) + end + + def delete_session_token(%Conn{} = conn) do + delete_session(conn, @oauth_token_session_key) + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 7011b7eb1..b4375872b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -25,7 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.MastodonAPI.MastodonAPIController alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.OAuth.OAuthController - alias Pleroma.Web.OAuth.OAuthView alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter @@ -103,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:ok, user} <- TwitterAPI.register_user(params), {_, {:ok, token}} <- {:login, OAuthController.login(user, app, app.scopes)} do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + OAuthController.after_token_exchange(conn, %{user: user, token: token}) else {:login, {:account_status, :confirmation_pending}} -> json_response(conn, :ok, %{ diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 9cc3984d0..fa582dcfc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.Helpers.AuthHelper alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization @@ -30,7 +31,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do {:ok, auth} <- Authorization.get_by_token(app, auth_token), {:ok, token} <- Token.exchange_token(app, auth) do conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> redirect(to: local_mastodon_root_path(conn)) end end @@ -53,7 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do @doc "DELETE /auth/sign_out" def logout(conn, _) do conn - |> clear_session + |> clear_session() |> redirect(to: "/") end diff --git a/lib/pleroma/web/o_auth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex index f102c93e7..5d5ec286a 100644 --- a/lib/pleroma/web/o_auth/mfa_controller.ex +++ b/lib/pleroma/web/o_auth/mfa_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.OAuth.MFAController do alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.OAuth.MFAView, as: View alias Pleroma.Web.OAuth.OAuthController - alias Pleroma.Web.OAuth.OAuthView alias Pleroma.Web.OAuth.Token plug(:fetch_session when action in [:show, :verify]) @@ -75,7 +74,7 @@ defmodule Pleroma.Web.OAuth.MFAController do {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), {:ok, _} <- validates_challenge(user, params), {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + OAuthController.after_token_exchange(conn, %{user: user, token: token}) else _error -> conn diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 83a25907d..8103395b3 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do use Pleroma.Web, :controller + alias Pleroma.Helpers.AuthHelper alias Pleroma.Helpers.UriHelper alias Pleroma.Maps alias Pleroma.MFA @@ -248,7 +249,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token), {:ok, token} <- RefreshToken.grant(token) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + after_token_exchange(conn, %{user: user, token: token}) else _error -> render_invalid_credentials_error(conn) end @@ -260,7 +261,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:ok, auth} <- Authorization.get_by_token(app, fixed_token), %User{} = user <- User.get_cached_by_id(auth.user_id), {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + after_token_exchange(conn, %{user: user, token: token}) else error -> handle_token_exchange_error(conn, error) @@ -275,7 +276,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:ok, app} <- Token.Utils.fetch_app(conn), requested_scopes <- Scopes.fetch_scopes(params, app.scopes), {:ok, token} <- login(user, app, requested_scopes) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + after_token_exchange(conn, %{user: user, token: token}) else error -> handle_token_exchange_error(conn, error) @@ -298,7 +299,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, auth} <- Authorization.create_authorization(app, %User{}), {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{token: token})) + after_token_exchange(conn, %{token: token}) else _error -> handle_token_exchange_error(conn, :invalid_credentails) @@ -308,6 +309,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Bad request def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do + conn + |> AuthHelper.put_session_token(token.token) + |> json(OAuthView.render("token.json", view_params)) + end + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do conn |> put_status(:forbidden) @@ -365,9 +372,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, %Token{} = oauth_token} <- RevokeToken.revoke(app, params) do conn = - with session_token = get_session(conn, :oauth_token), + with session_token = AuthHelper.get_session_token(conn), %Token{token: ^session_token} <- oauth_token do - delete_session(conn, :oauth_token) + AuthHelper.delete_session_token(conn) else _ -> conn end diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index a3b7d42f7..eb287318b 100644 --- a/lib/pleroma/web/plugs/o_auth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do import Plug.Conn import Ecto.Query + alias Pleroma.Helpers.AuthHelper alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.App @@ -98,7 +99,7 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} defp fetch_token_from_session(conn) do - case get_session(conn, :oauth_token) do + case AuthHelper.get_session_token(conn) do nil -> :no_token_found token -> {:ok, token} end diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex index d2338c03f..9f4a6b6ac 100644 --- a/lib/pleroma/web/plugs/set_user_session_id_plug.ex +++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex @@ -3,8 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do - import Plug.Conn - + alias Pleroma.Helpers.AuthHelper alias Pleroma.Web.OAuth.Token def init(opts) do @@ -12,7 +11,7 @@ defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do end def call(%{assigns: %{token: %Token{} = oauth_token}} = conn, _) do - put_session(conn, :oauth_token, oauth_token.token) + AuthHelper.put_session_token(conn, oauth_token.token) end def call(conn, _), do: conn diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3a3e63db6..b3462ba00 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -320,6 +320,11 @@ defmodule Pleroma.Web.Router do end scope "/oauth", Pleroma.Web.OAuth do + get("/registration_details", OAuthController, :registration_details) + + post("/mfa/verify", MFAController, :verify, as: :mfa_verify) + get("/mfa", MFAController, :show) + scope [] do pipe_through(:oauth) @@ -327,17 +332,12 @@ defmodule Pleroma.Web.Router do post("/authorize", OAuthController, :create_authorization) end - post("/token", OAuthController, :token_exchange) - get("/registration_details", OAuthController, :registration_details) - - post("/mfa/challenge", MFAController, :challenge) - post("/mfa/verify", MFAController, :verify, as: :mfa_verify) - get("/mfa", MFAController, :show) - scope [] do pipe_through(:fetch_session) + post("/token", OAuthController, :token_exchange) post("/revoke", OAuthController, :token_revoke) + post("/mfa/challenge", MFAController, :challenge) end scope [] do -- cgit v1.2.3 From 751712d97022fa99a190cda228a9bcc10b42ede9 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 25 Nov 2020 12:52:39 -0600 Subject: Prevent mix tasks from spewing any internal logging unless DEBUG is in the env e.g., DEBUG=1 mix pleroma.config migrate_from_db --- lib/mix/pleroma.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 6df1cf538..cd3f44074 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -22,8 +22,8 @@ defmodule Mix.Pleroma do Pleroma.Application.limiters_setup() Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) - if Pleroma.Config.get(:env) != :test do - Application.put_env(:logger, :console, level: :debug) + unless System.get_env("DEBUG") do + Logger.remove_backend(:console) end adapter = Application.get_env(:tesla, :adapter) -- cgit v1.2.3 From fb72f2034a5d6d434b7fcdc428d559bf9312b163 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 25 Nov 2020 16:44:11 +0300 Subject: fix spec --- lib/pleroma/moderation_log.ex | 335 +++++++++++++----------------------------- 1 file changed, 104 insertions(+), 231 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 0a701127f..a7f26793d 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -12,6 +12,26 @@ defmodule Pleroma.ModerationLog do import Ecto.Query + @type t :: %__MODULE__{} + @type log_subject :: Activity.t() | User.t() | list(User.t()) + @type log_params :: %{ + required(:actor) => User.t(), + required(:action) => String.t(), + optional(:subject) => log_subject(), + optional(:subject_actor) => User.t(), + optional(:subject_id) => String.t(), + optional(:subjects) => list(User.t()), + optional(:permission) => String.t(), + optional(:text) => String.t(), + optional(:sensitive) => String.t(), + optional(:visibility) => String.t(), + optional(:followed) => User.t(), + optional(:follower) => User.t(), + optional(:nicknames) => list(String.t()), + optional(:tags) => list(String.t()), + optional(:target) => String.t() + } + schema "moderation_log" do field(:data, :map) @@ -90,212 +110,105 @@ defmodule Pleroma.ModerationLog do parsed_datetime end - @spec insert_log(%{actor: User, subject: [User], action: String.t(), permission: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - subject: subjects, - action: action, - permission: permission - }) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "subject" => user_to_map(subjects), - "action" => action, - "permission" => permission, - "message" => "" - } + defp prepare_log_data(%{actor: actor, action: action} = attrs) do + %{ + "actor" => user_to_map(actor), + "action" => action, + "message" => "" } - |> insert_log_entry_with_message() + |> Pleroma.Maps.put_if_present("subject_actor", user_to_map(attrs[:subject_actor])) end - @spec insert_log(%{actor: User, subject: User, action: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log( - %{ - actor: %User{} = actor, - action: "report_update", - subject: %Activity{data: %{"type" => "Flag"}} = subject - } = attrs - ) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "report_update", - "subject" => report_to_map(subject), - "subject_actor" => user_to_map(attrs[:subject_actor]), - "message" => "" - } - } - |> insert_log_entry_with_message() + defp prepare_log_data(attrs), do: attrs + + @spec insert_log(log_params()) :: {:ok, ModerationLog} | {:error, any} + def insert_log(%{actor: %User{}, subject: subjects, permission: permission} = attrs) do + data = + attrs + |> prepare_log_data + |> Map.merge(%{"subject" => user_to_map(subjects), "permission" => permission}) + + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log( - %{ - actor: %User{} = actor, - action: "report_note", - subject: %Activity{} = subject, - text: text - } = attrs - ) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "report_note", - "subject" => report_to_map(subject), - "subject_actor" => user_to_map(attrs[:subject_actor]), - "text" => text - } - } - |> insert_log_entry_with_message() + def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs) + when action in ["report_note_delete", "report_update", "report_note"] do + data = + attrs + |> prepare_log_data + |> Pleroma.Maps.put_if_present("text", attrs[:text]) + |> Map.merge(%{"subject" => report_to_map(subject)}) + + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: - {:ok, ModerationLog} | {:error, any} def insert_log( %{ - actor: %User{} = actor, - action: "report_note_delete", + actor: %User{}, + action: action, subject: %Activity{} = subject, - text: text + sensitive: sensitive, + visibility: visibility } = attrs - ) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "report_note_delete", - "subject" => report_to_map(subject), - "subject_actor" => user_to_map(attrs[:subject_actor]), - "text" => text - } - } - |> insert_log_entry_with_message() - end - - @spec insert_log(%{ - actor: User, - subject: Activity, - action: String.t(), - sensitive: String.t(), - visibility: String.t() - }) :: {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - action: "status_update", - subject: %Activity{} = subject, - sensitive: sensitive, - visibility: visibility - }) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "status_update", + ) + when action == "status_update" do + data = + attrs + |> prepare_log_data + |> Map.merge(%{ "subject" => status_to_map(subject), "sensitive" => sensitive, - "visibility" => visibility, - "message" => "" - } - } - |> insert_log_entry_with_message() - end + "visibility" => visibility + }) - @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - action: "status_delete", - subject_id: subject_id - }) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "status_delete", - "subject_id" => subject_id, - "message" => "" - } - } - |> insert_log_entry_with_message() + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, subject: User, action: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{actor: %User{} = actor, subject: subject, action: action}) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => action, - "subject" => user_to_map(subject), - "message" => "" - } - } - |> insert_log_entry_with_message() + def insert_log(%{actor: %User{}, action: action, subject_id: subject_id} = attrs) + when action == "status_delete" do + data = + attrs + |> prepare_log_data + |> Map.merge(%{"subject_id" => subject_id}) + + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, subjects: [User], action: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{actor: %User{} = actor, subjects: subjects, action: action}) do - subjects = Enum.map(subjects, &user_to_map/1) + def insert_log(%{actor: %User{}, subject: subject, action: _action} = attrs) do + data = + attrs + |> prepare_log_data + |> Map.merge(%{"subject" => user_to_map(subject)}) - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => action, - "subjects" => subjects, - "message" => "" - } - } - |> insert_log_entry_with_message() + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - followed: %User{} = followed, - follower: %User{} = follower, - action: "follow" - }) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "follow", - "followed" => user_to_map(followed), - "follower" => user_to_map(follower), - "message" => "" - } - } - |> insert_log_entry_with_message() + def insert_log(%{actor: %User{}, subjects: subjects, action: _action} = attrs) do + data = + attrs + |> prepare_log_data + |> Map.merge(%{"subjects" => user_to_map(subjects)}) + + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, action: String.t(), followed: User, follower: User}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - followed: %User{} = followed, - follower: %User{} = follower, - action: "unfollow" - }) do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => "unfollow", - "followed" => user_to_map(followed), - "follower" => user_to_map(follower), - "message" => "" - } - } - |> insert_log_entry_with_message() + def insert_log( + %{ + actor: %User{}, + followed: %User{} = followed, + follower: %User{} = follower, + action: action + } = attrs + ) + when action in ["unfollow", "follow"] do + data = + attrs + |> prepare_log_data + |> Map.merge(%{"followed" => user_to_map(followed), "follower" => user_to_map(follower)}) + + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{ - actor: User, - action: String.t(), - nicknames: [String.t()], - tags: [String.t()] - }) :: {:ok, ModerationLog} | {:error, any} def insert_log(%{ actor: %User{} = actor, nicknames: nicknames, @@ -314,27 +227,16 @@ defmodule Pleroma.ModerationLog do |> insert_log_entry_with_message() end - @spec insert_log(%{actor: User, action: String.t(), target: String.t()}) :: - {:ok, ModerationLog} | {:error, any} - def insert_log(%{ - actor: %User{} = actor, - action: action, - target: target - }) + def insert_log(%{actor: %User{}, action: action, target: target} = attrs) when action in ["relay_follow", "relay_unfollow"] do - %ModerationLog{ - data: %{ - "actor" => user_to_map(actor), - "action" => action, - "target" => target, - "message" => "" - } - } - |> insert_log_entry_with_message() + data = + attrs + |> prepare_log_data + |> Map.merge(%{"target" => target}) + + insert_log_entry_with_message(%ModerationLog{data: data}) end - @spec insert_log(%{actor: User, action: String.t(), subject_id: String.t()}) :: - {:ok, ModerationLog} | {:error, any} def insert_log(%{actor: %User{} = actor, action: "chat_message_delete", subject_id: subject_id}) do %ModerationLog{ data: %{ @@ -367,20 +269,14 @@ defmodule Pleroma.ModerationLog do defp user_to_map(_), do: nil defp report_to_map(%Activity{} = report) do - %{ - "type" => "report", - "id" => report.id, - "state" => report.data["state"] - } + %{"type" => "report", "id" => report.id, "state" => report.data["state"]} end defp status_to_map(%Activity{} = status) do - %{ - "type" => "status", - "id" => status.id - } + %{"type" => "status", "id" => status.id} end + @spec get_log_entry_message(ModerationLog.t()) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -392,7 +288,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} made @#{follower_nickname} #{action} @#{followed_nickname}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -403,7 +298,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} deleted users: #{users_to_nicknames_string(subjects)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -414,7 +308,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} created users: #{users_to_nicknames_string(subjects)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -425,7 +318,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} activated users: #{users_to_nicknames_string(users)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -436,7 +328,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} deactivated users: #{users_to_nicknames_string(users)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -447,7 +338,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -461,7 +351,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} added tags: #{tags_string} to users: #{nicknames_to_string(nicknames)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -475,7 +364,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} removed tags: #{tags_string} from users: #{nicknames_to_string(nicknames)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -487,7 +375,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} made #{users_to_nicknames_string(users)} #{permission}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -499,7 +386,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} revoked #{permission} role from #{users_to_nicknames_string(users)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -510,7 +396,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} followed relay: #{target}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -521,7 +406,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} unfollowed relay: #{target}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message( %ModerationLog{ data: %{ @@ -536,7 +420,6 @@ defmodule Pleroma.ModerationLog do " with '#{state}' state" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message( %ModerationLog{ data: %{ @@ -551,7 +434,6 @@ defmodule Pleroma.ModerationLog do subject_actor_nickname(log, " on user ") end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message( %ModerationLog{ data: %{ @@ -566,7 +448,6 @@ defmodule Pleroma.ModerationLog do subject_actor_nickname(log, " on user ") end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -579,7 +460,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} updated status ##{subject_id}, set visibility: '#{visibility}'" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -592,7 +472,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}'" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -607,7 +486,6 @@ defmodule Pleroma.ModerationLog do }'" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -618,7 +496,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} deleted status ##{subject_id}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -629,7 +506,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -640,7 +516,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -653,7 +528,6 @@ defmodule Pleroma.ModerationLog do }" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, @@ -664,7 +538,6 @@ defmodule Pleroma.ModerationLog do "@#{actor_nickname} updated users: #{users_to_nicknames_string(subjects)}" end - @spec get_log_entry_message(ModerationLog) :: String.t() def get_log_entry_message(%ModerationLog{ data: %{ "actor" => %{"nickname" => actor_nickname}, -- cgit v1.2.3 From 6aadb1cb409a80632d17bba487cfabfdb0b13d34 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 26 Nov 2020 11:12:44 +0300 Subject: digest algorithm is taken from header --- lib/pleroma/web/plugs/digest_plug.ex | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/plugs/digest_plug.ex b/lib/pleroma/web/plugs/digest_plug.ex index b521b3073..fb2723b97 100644 --- a/lib/pleroma/web/plugs/digest_plug.ex +++ b/lib/pleroma/web/plugs/digest_plug.ex @@ -7,8 +7,22 @@ defmodule Pleroma.Web.Plugs.DigestPlug do require Logger def read_body(conn, opts) do + digest_algorithm = + with [digest_header] <- Conn.get_req_header(conn, "digest") do + digest_header + |> String.split("=", parts: 2) + |> List.first() + else + _ -> "SHA-256" + end + + unless String.downcase(digest_algorithm) == "sha-256" do + raise ArgumentError, + message: "invalid value for digest algorithm, got: #{digest_algorithm}" + end + {:ok, body, conn} = Conn.read_body(conn, opts) - digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64()) - {:ok, body, Conn.assign(conn, :digest, digest)} + encoded_digest = :crypto.hash(:sha256, body) |> Base.encode64() + {:ok, body, Conn.assign(conn, :digest, "#{digest_algorithm}=#{encoded_digest}")} end end -- cgit v1.2.3 From f1b07a2b2b6439579f0a35694f693712fb5ec5f4 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sat, 28 Nov 2020 21:51:06 +0300 Subject: OAuth form user remembering feature. Local MastoFE login / logout fixes. --- lib/pleroma/user.ex | 4 + lib/pleroma/web/masto_fe_controller.ex | 34 +-- .../mastodon_api/controllers/auth_controller.ex | 64 ++++-- lib/pleroma/web/o_auth/o_auth_controller.ex | 21 +- lib/pleroma/web/templates/layout/app.html.eex | 236 +-------------------- .../web/templates/o_auth/o_auth/show.html.eex | 66 ++++-- 6 files changed, 136 insertions(+), 289 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index bcd5256c8..6a5a43a25 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2406,4 +2406,8 @@ defmodule Pleroma.User do |> Map.put(:bio, HTML.filter_tags(user.bio, filter)) |> Map.put(:fields, fields) end + + def get_host(%User{ap_id: ap_id} = _user) do + URI.parse(ap_id).host + end end diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 08f92d55f..7011ae214 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastoFEController do use Pleroma.Web, :controller alias Pleroma.User + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.MastodonAPI.AuthController alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -26,27 +28,27 @@ defmodule Pleroma.Web.MastoFEController do ) @doc "GET /web/*path" - def index(%{assigns: %{user: user, token: token}} = conn, _params) - when not is_nil(user) and not is_nil(token) do - conn - |> put_layout(false) - |> render("index.html", - token: token.token, - user: user, - custom_emojis: Pleroma.Emoji.get_all() - ) - end - def index(conn, _params) do - conn - |> put_session(:return_to, conn.request_path) - |> redirect(to: "/web/login") + with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn, + {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do + conn + |> put_layout(false) + |> render("index.html", + token: token.token, + user: user, + custom_emojis: Pleroma.Emoji.get_all() + ) + else + _ -> + conn + |> put_session(:return_to, conn.request_path) + |> redirect(to: "/web/login") + end end @doc "GET /web/manifest.json" def manifest(conn, _params) do - conn - |> render("manifest.json") + render(conn, "manifest.json") end @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere" diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index fa582dcfc..93d057a79 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -8,10 +8,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] alias Pleroma.Helpers.AuthHelper + alias Pleroma.Helpers.UriHelper alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken alias Pleroma.Web.TwitterAPI.TwitterAPI action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -21,24 +23,35 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do @local_mastodon_name "Mastodon-Local" @doc "GET /web/login" - def login(%{assigns: %{user: %User{}}} = conn, _params) do - redirect(conn, to: local_mastodon_root_path(conn)) - end - - # Local Mastodon FE login init action - def login(conn, %{"code" => auth_token}) do - with {:ok, app} <- get_or_make_app(), + # Local Mastodon FE login callback action + def login(conn, %{"code" => auth_token} = params) do + with {:ok, app} <- local_mastofe_app(), {:ok, auth} <- Authorization.get_by_token(app, auth_token), - {:ok, token} <- Token.exchange_token(app, auth) do + {:ok, oauth_token} <- Token.exchange_token(app, auth) do + redirect_to = + conn + |> local_mastodon_post_login_path() + |> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token}) + conn - |> AuthHelper.put_session_token(token.token) - |> redirect(to: local_mastodon_root_path(conn)) + |> AuthHelper.put_session_token(oauth_token.token) + |> redirect(to: redirect_to) + else + _ -> redirect_to_oauth_form(conn, params) + end + end + + def login(conn, params) do + with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn, + {:ok, %{id: ^app_id}} <- local_mastofe_app() do + redirect(conn, to: local_mastodon_post_login_path(conn)) + else + _ -> redirect_to_oauth_form(conn, params) end end - # Local Mastodon FE callback action - def login(conn, _) do - with {:ok, app} <- get_or_make_app() do + defp redirect_to_oauth_form(conn, _params) do + with {:ok, app} <- local_mastofe_app() do path = o_auth_path(conn, :authorize, response_type: "code", @@ -53,9 +66,16 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do @doc "DELETE /auth/sign_out" def logout(conn, _) do - conn - |> clear_session() - |> redirect(to: "/") + conn = + with %{assigns: %{token: %Token{} = oauth_token}} <- conn, + session_token = AuthHelper.get_session_token(conn), + {:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do + AuthHelper.delete_session_token(conn) + else + _ -> conn + end + + redirect(conn, to: "/") end @doc "POST /auth/password" @@ -67,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do json_response(conn, :no_content, "") end - defp local_mastodon_root_path(conn) do + defp local_mastodon_post_login_path(conn) do case get_session(conn, :return_to) do nil -> masto_fe_path(conn, :index, ["getting-started"]) @@ -78,9 +98,11 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do end end - @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} - defp get_or_make_app do - %{client_name: @local_mastodon_name, redirect_uris: "."} - |> App.get_or_make(["read", "write", "follow", "push", "admin"]) + @spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + def local_mastofe_app do + App.get_or_make( + %{client_name: @local_mastodon_name, redirect_uris: "."}, + ["read", "write", "follow", "push", "admin"] + ) end end diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 8103395b3..965c0f879 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -80,6 +80,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do available_scopes = (app && app.scopes) || [] scopes = Scopes.fetch_scopes(params, available_scopes) + user = + with %{assigns: %{user: %User{} = user}} <- conn do + user + else + _ -> nil + end + scopes = if scopes == [] do available_scopes @@ -89,6 +96,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template render(conn, Authenticator.auth_template(), %{ + user: user, + app: app && Map.delete(app, :client_secret), response_type: params["response_type"], client_id: params["client_id"], available_scopes: available_scopes, @@ -132,11 +141,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - def create_authorization( - %Plug.Conn{} = conn, - %{"authorization" => _} = params, - opts \\ [] - ) do + def create_authorization(_, _, opts \\ []) + + def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, params, []) do + create_authorization(conn, params, user: user) + end + + def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do after_create_authorization(conn, auth, params) diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 3f28f1920..1ede59fd8 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -1,233 +1,19 @@ - + - - - - <%= Pleroma.Config.get([:instance, :name]) %> - - + + + <%= Pleroma.Config.get([:instance, :name]) %> + +
-

<%= Pleroma.Config.get([:instance, :name]) %>

<%= @inner_content %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index b17142ff8..1a85818ec 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -5,32 +5,55 @@ <% end %> -

OAuth Authorization

<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> -<%= if @params["registration"] in ["true", true] do %> -

This is the first time you visit! Please enter your Pleroma handle.

-

Choose carefully! You won't be able to change this later. You will be able to change your display name, though.

-
- <%= label f, :nickname, "Pleroma Handle" %> - <%= text_input f, :nickname, placeholder: "lain" %> +<%= if @user do %> + - <%= hidden_input f, :name, value: @params["name"] %> - <%= hidden_input f, :password, value: @params["password"] %> -
-<% else %> -
- <%= label f, :name, "Username" %> - <%= text_input f, :name %> -
-
- <%= label f, :password, "Password" %> - <%= password_input f, :password %> -
- <%= submit "Log In" %> - <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> <% end %> +
+ <%= if @app do %> +

Application <%= @app.client_name %> is requesting access to your account.

+ <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> + <% end %> + + <%= if @user do %> +
+ Cancel + <%= submit "Approve", class: "button--approve" %> +
+ <% else %> + <%= if @params["registration"] in ["true", true] do %> +

This is the first time you visit! Please enter your Pleroma handle.

+

Choose carefully! You won't be able to change this later. You will be able to change your display name, though.

+
+ <%= label f, :nickname, "Pleroma Handle" %> + <%= text_input f, :nickname, placeholder: "lain" %> +
+ <%= hidden_input f, :name, value: @params["name"] %> + <%= hidden_input f, :password, value: @params["password"] %> +
+ <% else %> +
+ <%= label f, :name, "Username" %> + <%= text_input f, :name %> +
+
+ <%= label f, :password, "Password" %> + <%= password_input f, :password %> +
+ <%= submit "Log In" %> + <% end %> + <% end %> +
+ <%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :response_type, value: @response_type %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> @@ -40,4 +63,3 @@ <%= if Pleroma.Config.oauth_consumer_enabled?() do %> <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %> <% end %> - -- cgit v1.2.3 From d50a3345ae7873f8a8744eba8a3eb755e2b8dfdc Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Mon, 30 Nov 2020 21:55:48 +0300 Subject: [#3112] Allowed revoking same-user token from any apps. Added tests. --- lib/pleroma/web/masto_fe_controller.ex | 2 +- lib/pleroma/web/o_auth/o_auth_controller.ex | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 7011ae214..20279ff45 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -6,8 +6,8 @@ defmodule Pleroma.Web.MastoFEController do use Pleroma.Web, :controller alias Pleroma.User - alias Pleroma.Web.OAuth.Token alias Pleroma.Web.MastodonAPI.AuthController + alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.OAuthScopesPlug diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 965c0f879..6e3c7e1a1 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -379,9 +379,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do render_invalid_credentials_error(conn) end - def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, %Token{} = oauth_token} <- RevokeToken.revoke(app, params) do + def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do + with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token), + {:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do conn = with session_token = AuthHelper.get_session_token(conn), %Token{token: ^session_token} <- oauth_token do -- cgit v1.2.3 From 35ba48494f5129d3a0010b045ff36d98e7e7984f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 2 Dec 2020 00:17:52 +0400 Subject: Stream follow updates --- lib/pleroma/following_relationship.ex | 42 ++++++++++++++++++++------ lib/pleroma/user.ex | 12 +------- lib/pleroma/user/import.ex | 2 +- lib/pleroma/web/activity_pub/side_effects.ex | 7 ++--- lib/pleroma/web/streamer.ex | 45 ++++++++++++++++------------ lib/pleroma/web/views/streamer_view.ex | 22 ++++++++++++++ 6 files changed, 86 insertions(+), 44 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 2039a259d..bc6a7eaf9 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -62,23 +62,47 @@ defmodule Pleroma.FollowingRelationship do follow(follower, following, state) following_relationship -> - following_relationship - |> cast(%{state: state}, [:state]) - |> validate_required([:state]) - |> Repo.update() + with {:ok, _following_relationship} <- + following_relationship + |> cast(%{state: state}, [:state]) + |> validate_required([:state]) + |> Repo.update() do + after_update(state, follower, following) + end end end def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do - %__MODULE__{} - |> changeset(%{follower: follower, following: following, state: state}) - |> Repo.insert(on_conflict: :nothing) + with {:ok, _following_relationship} <- + %__MODULE__{} + |> changeset(%{follower: follower, following: following, state: state}) + |> Repo.insert(on_conflict: :nothing) do + after_update(state, follower, following) + end end def unfollow(%User{} = follower, %User{} = following) do case get(follower, following) do - %__MODULE__{} = following_relationship -> Repo.delete(following_relationship) - _ -> {:ok, nil} + %__MODULE__{} = following_relationship -> + with {:ok, _following_relationship} <- Repo.delete(following_relationship) do + after_update(:unfollow, follower, following) + end + + _ -> + {:ok, nil} + end + end + + defp after_update(state, %User{} = follower, %User{} = following) do + with {:ok, following} <- User.update_follower_count(following), + {:ok, follower} <- User.update_following_count(follower) do + Pleroma.Web.Streamer.stream("relationships:update", %{ + state: state, + following: following, + follower: follower + }) + + {:ok, follower, following} end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index bcd5256c8..676483540 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -882,7 +882,7 @@ defmodule Pleroma.User do if not ap_enabled?(followed) do follow(follower, followed) else - {:ok, follower} + {:ok, follower, followed} end end @@ -908,11 +908,6 @@ defmodule Pleroma.User do true -> FollowingRelationship.follow(follower, followed, state) - - {:ok, _} = update_follower_count(followed) - - follower - |> update_following_count() end end @@ -936,11 +931,6 @@ defmodule Pleroma.User do case get_follow_state(follower, followed) do state when state in [:follow_pending, :follow_accept] -> FollowingRelationship.unfollow(follower, followed) - {:ok, followed} = update_follower_count(followed) - - {:ok, follower} = update_following_count(follower) - - {:ok, follower, followed} nil -> {:error, "Not subscribed!"} diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index e458021c8..86b49d8ae 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -45,7 +45,7 @@ defmodule Pleroma.User.Import do identifiers, fn identifier -> with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), - {:ok, follower} <- User.maybe_direct_follow(follower, followed), + {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed), {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do followed else diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 4d8fb721e..8556fca1d 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -47,10 +47,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do %User{} = followed <- User.get_cached_by_ap_id(actor), %User{} = follower <- User.get_cached_by_ap_id(follower_id), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do + {:ok, _follower, followed} <- + FollowingRelationship.update(follower, followed, :follow_accept) do Notification.update_notification_type(followed, follow_activity) - User.update_follower_count(followed) - User.update_following_count(follower) end {:ok, object, meta} @@ -99,7 +98,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do ) do with %User{} = follower <- User.get_cached_by_ap_id(following_user), %User{} = followed <- User.get_cached_by_ap_id(followed_user), - {_, {:ok, _}, _, _} <- + {_, {:ok, _, _}, _, _} <- {:following, User.follow(follower, followed, :follow_pending), follower, followed} do if followed.local && !followed.is_locked do {:ok, accept_data, _} = Builder.accept(followed, object) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 71fe27c89..0b6cc89e9 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -36,9 +36,8 @@ defmodule Pleroma.Web.Streamer do ) :: {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do - case get_topic(stream, user, oauth_token, params) do - {:ok, topic} -> add_socket(topic, user) - error -> error + with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do + add_socket(topic, user) end end @@ -70,10 +69,10 @@ defmodule Pleroma.Web.Streamer do def get_topic( stream, %User{id: user_id} = user, - %Token{user_id: token_user_id} = oauth_token, + %Token{user_id: user_id} = oauth_token, _params ) - when stream in @user_streams and user_id == token_user_id do + when stream in @user_streams do # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) required_scopes = if stream == "user:notification" do @@ -97,10 +96,9 @@ defmodule Pleroma.Web.Streamer do def get_topic( "list", %User{id: user_id} = user, - %Token{user_id: token_user_id} = oauth_token, + %Token{user_id: user_id} = oauth_token, %{"list" => id} - ) - when user_id == token_user_id do + ) do cond do OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> {:error, :unauthorized} @@ -137,16 +135,10 @@ defmodule Pleroma.Web.Streamer do def stream(topics, items) do if should_env_send?() do - List.wrap(topics) - |> Enum.each(fn topic -> - List.wrap(items) - |> Enum.each(fn item -> - spawn(fn -> do_stream(topic, item) end) - end) - end) + for topic <- List.wrap(topics), item <- List.wrap(items) do + spawn(fn -> do_stream(topic, item) end) + end end - - :ok end def filtered_by_user?(user, item, streamed_type \\ :activity) @@ -160,8 +152,7 @@ defmodule Pleroma.Web.Streamer do domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) with parent <- Object.normalize(item) || item, - true <- - Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), + true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, true <- !(streamed_type == :activity && item.data["type"] == "Announce" && @@ -195,6 +186,22 @@ defmodule Pleroma.Web.Streamer do end) end + defp do_stream("relationships:update", item) do + text = StreamerView.render("relationships_update.json", item) + + [item.follower, item.following] + |> Enum.map(fn %{id: id} -> "user:#{id}" end) + |> Enum.each(fn user_topic -> + Logger.debug("Trying to push relationships:update to #{user_topic}\n\n") + + Registry.dispatch(@registry, user_topic, fn list -> + Enum.each(list, fn {pid, _auth} -> + send(pid, {:text, text}) + end) + end) + end) + end + defp do_stream("participation", participation) do user_topic = "direct:#{participation.user_id}" Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 476a33245..92239a411 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -74,6 +74,28 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end + def render("relationships_update.json", item) do + %{ + event: "pleroma:relationships_update", + payload: + %{ + state: item.state, + follower: %{ + id: item.follower.id, + follower_count: item.follower.follower_count, + following_count: item.follower.following_count + }, + following: %{ + id: item.following.id, + follower_count: item.following.follower_count, + following_count: item.following.following_count + } + } + |> Jason.encode!() + } + |> Jason.encode!() + end + def render("conversation.json", %Participation{} = participation) do %{ event: "conversation", -- cgit v1.2.3 From 222312900e6d847e0d4823fb62b6eb3675a0180f Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Dec 2020 12:18:43 +0100 Subject: User: Don't allow local users in remote changesets --- lib/pleroma/user.ex | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index bcd5256c8..9222b5b2a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -472,7 +472,20 @@ defmodule Pleroma.User do |> validate_format(:nickname, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) + |> validate_inclusion(:local, [true]) |> validate_fields(true) + |> validate_non_local() + end + + defp validate_non_local(cng) do + local? = get_field(cng, :local) + + if local? do + cng + |> add_error(:local, "User is local, can't update with this changeset.") + else + cng + end end def update_changeset(struct, params \\ %{}) do -- cgit v1.2.3 From 04af0bbe44ab4ebd83ee2f3b797768d6e255e365 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Dec 2020 13:39:29 +0100 Subject: User: Remove left-over (wrong) fix. --- lib/pleroma/user.ex | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9222b5b2a..4b3a9d690 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -472,7 +472,6 @@ defmodule Pleroma.User do |> validate_format(:nickname, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) - |> validate_inclusion(:local, [true]) |> validate_fields(true) |> validate_non_local() end -- cgit v1.2.3 From 1adee0832148265828b38d9b68a72dec1098bcaf Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Dec 2020 16:15:03 +0100 Subject: Emoji: Update to Unicode 13.1, switch base file, allow multichar. --- lib/pleroma/emoji-test.txt | 4879 ++++++++++++++++++++++++++++++++++++++++++++ lib/pleroma/emoji.ex | 17 +- 2 files changed, 4885 insertions(+), 11 deletions(-) create mode 100644 lib/pleroma/emoji-test.txt (limited to 'lib') diff --git a/lib/pleroma/emoji-test.txt b/lib/pleroma/emoji-test.txt new file mode 100644 index 000000000..d3c6d12bd --- /dev/null +++ b/lib/pleroma/emoji-test.txt @@ -0,0 +1,4879 @@ +# emoji-test.txt +# Date: 2020-09-12, 22:19:50 GMT +# © 2020 Unicode®, Inc. +# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. +# For terms of use, see http://www.unicode.org/terms_of_use.html +# +# Emoji Keyboard/Display Test Data for UTS #51 +# Version: 13.1 +# +# For documentation and usage, see http://www.unicode.org/reports/tr51 +# +# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. +# Format: code points; status # emoji name +# Code points — list of one or more hex code points, separated by spaces +# Status +# component — an Emoji_Component, +# excluding Regional_Indicators, ASCII, and non-Emoji. +# fully-qualified — a fully-qualified emoji (see ED-18 in UTS #51), +# excluding Emoji_Component +# minimally-qualified — a minimally-qualified emoji (see ED-18a in UTS #51) +# unqualified — a unqualified emoji (See ED-19 in UTS #51) +# Notes: +# • This includes the emoji components that need emoji presentation (skin tone and hair) +# when isolated, but omits the components that need not have an emoji +# presentation when isolated. +# • The RGI set is covered by the listed fully-qualified emoji. +# • The listed minimally-qualified and unqualified cover all cases where an +# element of the RGI set is missing one or more emoji presentation selectors. +# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. +# • The groups and subgroups are illustrative. See the Emoji Order chart for more information. + + +# group: Smileys & Emotion + +# subgroup: face-smiling +1F600 ; fully-qualified # 😀 E1.0 grinning face +1F603 ; fully-qualified # 😃 E0.6 grinning face with big eyes +1F604 ; fully-qualified # 😄 E0.6 grinning face with smiling eyes +1F601 ; fully-qualified # 😁 E0.6 beaming face with smiling eyes +1F606 ; fully-qualified # 😆 E0.6 grinning squinting face +1F605 ; fully-qualified # 😅 E0.6 grinning face with sweat +1F923 ; fully-qualified # 🤣 E3.0 rolling on the floor laughing +1F602 ; fully-qualified # 😂 E0.6 face with tears of joy +1F642 ; fully-qualified # 🙂 E1.0 slightly smiling face +1F643 ; fully-qualified # 🙃 E1.0 upside-down face +1F609 ; fully-qualified # 😉 E0.6 winking face +1F60A ; fully-qualified # 😊 E0.6 smiling face with smiling eyes +1F607 ; fully-qualified # 😇 E1.0 smiling face with halo + +# subgroup: face-affection +1F970 ; fully-qualified # 🥰 E11.0 smiling face with hearts +1F60D ; fully-qualified # 😍 E0.6 smiling face with heart-eyes +1F929 ; fully-qualified # 🤩 E5.0 star-struck +1F618 ; fully-qualified # 😘 E0.6 face blowing a kiss +1F617 ; fully-qualified # 😗 E1.0 kissing face +263A FE0F ; fully-qualified # ☺️ E0.6 smiling face +263A ; unqualified # ☺ E0.6 smiling face +1F61A ; fully-qualified # 😚 E0.6 kissing face with closed eyes +1F619 ; fully-qualified # 😙 E1.0 kissing face with smiling eyes +1F972 ; fully-qualified # 🥲 E13.0 smiling face with tear + +# subgroup: face-tongue +1F60B ; fully-qualified # 😋 E0.6 face savoring food +1F61B ; fully-qualified # 😛 E1.0 face with tongue +1F61C ; fully-qualified # 😜 E0.6 winking face with tongue +1F92A ; fully-qualified # 🤪 E5.0 zany face +1F61D ; fully-qualified # 😝 E0.6 squinting face with tongue +1F911 ; fully-qualified # 🤑 E1.0 money-mouth face + +# subgroup: face-hand +1F917 ; fully-qualified # 🤗 E1.0 hugging face +1F92D ; fully-qualified # 🤭 E5.0 face with hand over mouth +1F92B ; fully-qualified # 🤫 E5.0 shushing face +1F914 ; fully-qualified # 🤔 E1.0 thinking face + +# subgroup: face-neutral-skeptical +1F910 ; fully-qualified # 🤐 E1.0 zipper-mouth face +1F928 ; fully-qualified # 🤨 E5.0 face with raised eyebrow +1F610 ; fully-qualified # 😐 E0.7 neutral face +1F611 ; fully-qualified # 😑 E1.0 expressionless face +1F636 ; fully-qualified # 😶 E1.0 face without mouth +1F636 200D 1F32B FE0F ; fully-qualified # 😶‍🌫️ E13.1 face in clouds +1F636 200D 1F32B ; minimally-qualified # 😶‍🌫 E13.1 face in clouds +1F60F ; fully-qualified # 😏 E0.6 smirking face +1F612 ; fully-qualified # 😒 E0.6 unamused face +1F644 ; fully-qualified # 🙄 E1.0 face with rolling eyes +1F62C ; fully-qualified # 😬 E1.0 grimacing face +1F62E 200D 1F4A8 ; fully-qualified # 😮‍💨 E13.1 face exhaling +1F925 ; fully-qualified # 🤥 E3.0 lying face + +# subgroup: face-sleepy +1F60C ; fully-qualified # 😌 E0.6 relieved face +1F614 ; fully-qualified # 😔 E0.6 pensive face +1F62A ; fully-qualified # 😪 E0.6 sleepy face +1F924 ; fully-qualified # 🤤 E3.0 drooling face +1F634 ; fully-qualified # 😴 E1.0 sleeping face + +# subgroup: face-unwell +1F637 ; fully-qualified # 😷 E0.6 face with medical mask +1F912 ; fully-qualified # 🤒 E1.0 face with thermometer +1F915 ; fully-qualified # 🤕 E1.0 face with head-bandage +1F922 ; fully-qualified # 🤢 E3.0 nauseated face +1F92E ; fully-qualified # 🤮 E5.0 face vomiting +1F927 ; fully-qualified # 🤧 E3.0 sneezing face +1F975 ; fully-qualified # 🥵 E11.0 hot face +1F976 ; fully-qualified # 🥶 E11.0 cold face +1F974 ; fully-qualified # 🥴 E11.0 woozy face +1F635 ; fully-qualified # 😵 E0.6 knocked-out face +1F635 200D 1F4AB ; fully-qualified # 😵‍💫 E13.1 face with spiral eyes +1F92F ; fully-qualified # 🤯 E5.0 exploding head + +# subgroup: face-hat +1F920 ; fully-qualified # 🤠 E3.0 cowboy hat face +1F973 ; fully-qualified # 🥳 E11.0 partying face +1F978 ; fully-qualified # 🥸 E13.0 disguised face + +# subgroup: face-glasses +1F60E ; fully-qualified # 😎 E1.0 smiling face with sunglasses +1F913 ; fully-qualified # 🤓 E1.0 nerd face +1F9D0 ; fully-qualified # 🧐 E5.0 face with monocle + +# subgroup: face-concerned +1F615 ; fully-qualified # 😕 E1.0 confused face +1F61F ; fully-qualified # 😟 E1.0 worried face +1F641 ; fully-qualified # 🙁 E1.0 slightly frowning face +2639 FE0F ; fully-qualified # ☹️ E0.7 frowning face +2639 ; unqualified # ☹ E0.7 frowning face +1F62E ; fully-qualified # 😮 E1.0 face with open mouth +1F62F ; fully-qualified # 😯 E1.0 hushed face +1F632 ; fully-qualified # 😲 E0.6 astonished face +1F633 ; fully-qualified # 😳 E0.6 flushed face +1F97A ; fully-qualified # 🥺 E11.0 pleading face +1F626 ; fully-qualified # 😦 E1.0 frowning face with open mouth +1F627 ; fully-qualified # 😧 E1.0 anguished face +1F628 ; fully-qualified # 😨 E0.6 fearful face +1F630 ; fully-qualified # 😰 E0.6 anxious face with sweat +1F625 ; fully-qualified # 😥 E0.6 sad but relieved face +1F622 ; fully-qualified # 😢 E0.6 crying face +1F62D ; fully-qualified # 😭 E0.6 loudly crying face +1F631 ; fully-qualified # 😱 E0.6 face screaming in fear +1F616 ; fully-qualified # 😖 E0.6 confounded face +1F623 ; fully-qualified # 😣 E0.6 persevering face +1F61E ; fully-qualified # 😞 E0.6 disappointed face +1F613 ; fully-qualified # 😓 E0.6 downcast face with sweat +1F629 ; fully-qualified # 😩 E0.6 weary face +1F62B ; fully-qualified # 😫 E0.6 tired face +1F971 ; fully-qualified # 🥱 E12.0 yawning face + +# subgroup: face-negative +1F624 ; fully-qualified # 😤 E0.6 face with steam from nose +1F621 ; fully-qualified # 😡 E0.6 pouting face +1F620 ; fully-qualified # 😠 E0.6 angry face +1F92C ; fully-qualified # 🤬 E5.0 face with symbols on mouth +1F608 ; fully-qualified # 😈 E1.0 smiling face with horns +1F47F ; fully-qualified # 👿 E0.6 angry face with horns +1F480 ; fully-qualified # 💀 E0.6 skull +2620 FE0F ; fully-qualified # ☠️ E1.0 skull and crossbones +2620 ; unqualified # ☠ E1.0 skull and crossbones + +# subgroup: face-costume +1F4A9 ; fully-qualified # 💩 E0.6 pile of poo +1F921 ; fully-qualified # 🤡 E3.0 clown face +1F479 ; fully-qualified # 👹 E0.6 ogre +1F47A ; fully-qualified # 👺 E0.6 goblin +1F47B ; fully-qualified # 👻 E0.6 ghost +1F47D ; fully-qualified # 👽 E0.6 alien +1F47E ; fully-qualified # 👾 E0.6 alien monster +1F916 ; fully-qualified # 🤖 E1.0 robot + +# subgroup: cat-face +1F63A ; fully-qualified # 😺 E0.6 grinning cat +1F638 ; fully-qualified # 😸 E0.6 grinning cat with smiling eyes +1F639 ; fully-qualified # 😹 E0.6 cat with tears of joy +1F63B ; fully-qualified # 😻 E0.6 smiling cat with heart-eyes +1F63C ; fully-qualified # 😼 E0.6 cat with wry smile +1F63D ; fully-qualified # 😽 E0.6 kissing cat +1F640 ; fully-qualified # 🙀 E0.6 weary cat +1F63F ; fully-qualified # 😿 E0.6 crying cat +1F63E ; fully-qualified # 😾 E0.6 pouting cat + +# subgroup: monkey-face +1F648 ; fully-qualified # 🙈 E0.6 see-no-evil monkey +1F649 ; fully-qualified # 🙉 E0.6 hear-no-evil monkey +1F64A ; fully-qualified # 🙊 E0.6 speak-no-evil monkey + +# subgroup: emotion +1F48B ; fully-qualified # 💋 E0.6 kiss mark +1F48C ; fully-qualified # 💌 E0.6 love letter +1F498 ; fully-qualified # 💘 E0.6 heart with arrow +1F49D ; fully-qualified # 💝 E0.6 heart with ribbon +1F496 ; fully-qualified # 💖 E0.6 sparkling heart +1F497 ; fully-qualified # 💗 E0.6 growing heart +1F493 ; fully-qualified # 💓 E0.6 beating heart +1F49E ; fully-qualified # 💞 E0.6 revolving hearts +1F495 ; fully-qualified # 💕 E0.6 two hearts +1F49F ; fully-qualified # 💟 E0.6 heart decoration +2763 FE0F ; fully-qualified # ❣️ E1.0 heart exclamation +2763 ; unqualified # ❣ E1.0 heart exclamation +1F494 ; fully-qualified # 💔 E0.6 broken heart +2764 FE0F 200D 1F525 ; fully-qualified # ❤️‍🔥 E13.1 heart on fire +2764 200D 1F525 ; unqualified # ❤‍🔥 E13.1 heart on fire +2764 FE0F 200D 1FA79 ; fully-qualified # ❤️‍🩹 E13.1 mending heart +2764 200D 1FA79 ; unqualified # ❤‍🩹 E13.1 mending heart +2764 FE0F ; fully-qualified # ❤️ E0.6 red heart +2764 ; unqualified # ❤ E0.6 red heart +1F9E1 ; fully-qualified # 🧡 E5.0 orange heart +1F49B ; fully-qualified # 💛 E0.6 yellow heart +1F49A ; fully-qualified # 💚 E0.6 green heart +1F499 ; fully-qualified # 💙 E0.6 blue heart +1F49C ; fully-qualified # 💜 E0.6 purple heart +1F90E ; fully-qualified # 🤎 E12.0 brown heart +1F5A4 ; fully-qualified # 🖤 E3.0 black heart +1F90D ; fully-qualified # 🤍 E12.0 white heart +1F4AF ; fully-qualified # 💯 E0.6 hundred points +1F4A2 ; fully-qualified # 💢 E0.6 anger symbol +1F4A5 ; fully-qualified # 💥 E0.6 collision +1F4AB ; fully-qualified # 💫 E0.6 dizzy +1F4A6 ; fully-qualified # 💦 E0.6 sweat droplets +1F4A8 ; fully-qualified # 💨 E0.6 dashing away +1F573 FE0F ; fully-qualified # 🕳️ E0.7 hole +1F573 ; unqualified # 🕳 E0.7 hole +1F4A3 ; fully-qualified # 💣 E0.6 bomb +1F4AC ; fully-qualified # 💬 E0.6 speech balloon +1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ E2.0 eye in speech bubble +1F441 200D 1F5E8 FE0F ; unqualified # 👁‍🗨️ E2.0 eye in speech bubble +1F441 FE0F 200D 1F5E8 ; unqualified # 👁️‍🗨 E2.0 eye in speech bubble +1F441 200D 1F5E8 ; unqualified # 👁‍🗨 E2.0 eye in speech bubble +1F5E8 FE0F ; fully-qualified # 🗨️ E2.0 left speech bubble +1F5E8 ; unqualified # 🗨 E2.0 left speech bubble +1F5EF FE0F ; fully-qualified # 🗯️ E0.7 right anger bubble +1F5EF ; unqualified # 🗯 E0.7 right anger bubble +1F4AD ; fully-qualified # 💭 E1.0 thought balloon +1F4A4 ; fully-qualified # 💤 E0.6 zzz + +# Smileys & Emotion subtotal: 170 +# Smileys & Emotion subtotal: 170 w/o modifiers + +# group: People & Body + +# subgroup: hand-fingers-open +1F44B ; fully-qualified # 👋 E0.6 waving hand +1F44B 1F3FB ; fully-qualified # 👋🏻 E1.0 waving hand: light skin tone +1F44B 1F3FC ; fully-qualified # 👋🏼 E1.0 waving hand: medium-light skin tone +1F44B 1F3FD ; fully-qualified # 👋🏽 E1.0 waving hand: medium skin tone +1F44B 1F3FE ; fully-qualified # 👋🏾 E1.0 waving hand: medium-dark skin tone +1F44B 1F3FF ; fully-qualified # 👋🏿 E1.0 waving hand: dark skin tone +1F91A ; fully-qualified # 🤚 E3.0 raised back of hand +1F91A 1F3FB ; fully-qualified # 🤚🏻 E3.0 raised back of hand: light skin tone +1F91A 1F3FC ; fully-qualified # 🤚🏼 E3.0 raised back of hand: medium-light skin tone +1F91A 1F3FD ; fully-qualified # 🤚🏽 E3.0 raised back of hand: medium skin tone +1F91A 1F3FE ; fully-qualified # 🤚🏾 E3.0 raised back of hand: medium-dark skin tone +1F91A 1F3FF ; fully-qualified # 🤚🏿 E3.0 raised back of hand: dark skin tone +1F590 FE0F ; fully-qualified # 🖐️ E0.7 hand with fingers splayed +1F590 ; unqualified # 🖐 E0.7 hand with fingers splayed +1F590 1F3FB ; fully-qualified # 🖐🏻 E1.0 hand with fingers splayed: light skin tone +1F590 1F3FC ; fully-qualified # 🖐🏼 E1.0 hand with fingers splayed: medium-light skin tone +1F590 1F3FD ; fully-qualified # 🖐🏽 E1.0 hand with fingers splayed: medium skin tone +1F590 1F3FE ; fully-qualified # 🖐🏾 E1.0 hand with fingers splayed: medium-dark skin tone +1F590 1F3FF ; fully-qualified # 🖐🏿 E1.0 hand with fingers splayed: dark skin tone +270B ; fully-qualified # ✋ E0.6 raised hand +270B 1F3FB ; fully-qualified # ✋🏻 E1.0 raised hand: light skin tone +270B 1F3FC ; fully-qualified # ✋🏼 E1.0 raised hand: medium-light skin tone +270B 1F3FD ; fully-qualified # ✋🏽 E1.0 raised hand: medium skin tone +270B 1F3FE ; fully-qualified # ✋🏾 E1.0 raised hand: medium-dark skin tone +270B 1F3FF ; fully-qualified # ✋🏿 E1.0 raised hand: dark skin tone +1F596 ; fully-qualified # 🖖 E1.0 vulcan salute +1F596 1F3FB ; fully-qualified # 🖖🏻 E1.0 vulcan salute: light skin tone +1F596 1F3FC ; fully-qualified # 🖖🏼 E1.0 vulcan salute: medium-light skin tone +1F596 1F3FD ; fully-qualified # 🖖🏽 E1.0 vulcan salute: medium skin tone +1F596 1F3FE ; fully-qualified # 🖖🏾 E1.0 vulcan salute: medium-dark skin tone +1F596 1F3FF ; fully-qualified # 🖖🏿 E1.0 vulcan salute: dark skin tone + +# subgroup: hand-fingers-partial +1F44C ; fully-qualified # 👌 E0.6 OK hand +1F44C 1F3FB ; fully-qualified # 👌🏻 E1.0 OK hand: light skin tone +1F44C 1F3FC ; fully-qualified # 👌🏼 E1.0 OK hand: medium-light skin tone +1F44C 1F3FD ; fully-qualified # 👌🏽 E1.0 OK hand: medium skin tone +1F44C 1F3FE ; fully-qualified # 👌🏾 E1.0 OK hand: medium-dark skin tone +1F44C 1F3FF ; fully-qualified # 👌🏿 E1.0 OK hand: dark skin tone +1F90C ; fully-qualified # 🤌 E13.0 pinched fingers +1F90C 1F3FB ; fully-qualified # 🤌🏻 E13.0 pinched fingers: light skin tone +1F90C 1F3FC ; fully-qualified # 🤌🏼 E13.0 pinched fingers: medium-light skin tone +1F90C 1F3FD ; fully-qualified # 🤌🏽 E13.0 pinched fingers: medium skin tone +1F90C 1F3FE ; fully-qualified # 🤌🏾 E13.0 pinched fingers: medium-dark skin tone +1F90C 1F3FF ; fully-qualified # 🤌🏿 E13.0 pinched fingers: dark skin tone +1F90F ; fully-qualified # 🤏 E12.0 pinching hand +1F90F 1F3FB ; fully-qualified # 🤏🏻 E12.0 pinching hand: light skin tone +1F90F 1F3FC ; fully-qualified # 🤏🏼 E12.0 pinching hand: medium-light skin tone +1F90F 1F3FD ; fully-qualified # 🤏🏽 E12.0 pinching hand: medium skin tone +1F90F 1F3FE ; fully-qualified # 🤏🏾 E12.0 pinching hand: medium-dark skin tone +1F90F 1F3FF ; fully-qualified # 🤏🏿 E12.0 pinching hand: dark skin tone +270C FE0F ; fully-qualified # ✌️ E0.6 victory hand +270C ; unqualified # ✌ E0.6 victory hand +270C 1F3FB ; fully-qualified # ✌🏻 E1.0 victory hand: light skin tone +270C 1F3FC ; fully-qualified # ✌🏼 E1.0 victory hand: medium-light skin tone +270C 1F3FD ; fully-qualified # ✌🏽 E1.0 victory hand: medium skin tone +270C 1F3FE ; fully-qualified # ✌🏾 E1.0 victory hand: medium-dark skin tone +270C 1F3FF ; fully-qualified # ✌🏿 E1.0 victory hand: dark skin tone +1F91E ; fully-qualified # 🤞 E3.0 crossed fingers +1F91E 1F3FB ; fully-qualified # 🤞🏻 E3.0 crossed fingers: light skin tone +1F91E 1F3FC ; fully-qualified # 🤞🏼 E3.0 crossed fingers: medium-light skin tone +1F91E 1F3FD ; fully-qualified # 🤞🏽 E3.0 crossed fingers: medium skin tone +1F91E 1F3FE ; fully-qualified # 🤞🏾 E3.0 crossed fingers: medium-dark skin tone +1F91E 1F3FF ; fully-qualified # 🤞🏿 E3.0 crossed fingers: dark skin tone +1F91F ; fully-qualified # 🤟 E5.0 love-you gesture +1F91F 1F3FB ; fully-qualified # 🤟🏻 E5.0 love-you gesture: light skin tone +1F91F 1F3FC ; fully-qualified # 🤟🏼 E5.0 love-you gesture: medium-light skin tone +1F91F 1F3FD ; fully-qualified # 🤟🏽 E5.0 love-you gesture: medium skin tone +1F91F 1F3FE ; fully-qualified # 🤟🏾 E5.0 love-you gesture: medium-dark skin tone +1F91F 1F3FF ; fully-qualified # 🤟🏿 E5.0 love-you gesture: dark skin tone +1F918 ; fully-qualified # 🤘 E1.0 sign of the horns +1F918 1F3FB ; fully-qualified # 🤘🏻 E1.0 sign of the horns: light skin tone +1F918 1F3FC ; fully-qualified # 🤘🏼 E1.0 sign of the horns: medium-light skin tone +1F918 1F3FD ; fully-qualified # 🤘🏽 E1.0 sign of the horns: medium skin tone +1F918 1F3FE ; fully-qualified # 🤘🏾 E1.0 sign of the horns: medium-dark skin tone +1F918 1F3FF ; fully-qualified # 🤘🏿 E1.0 sign of the horns: dark skin tone +1F919 ; fully-qualified # 🤙 E3.0 call me hand +1F919 1F3FB ; fully-qualified # 🤙🏻 E3.0 call me hand: light skin tone +1F919 1F3FC ; fully-qualified # 🤙🏼 E3.0 call me hand: medium-light skin tone +1F919 1F3FD ; fully-qualified # 🤙🏽 E3.0 call me hand: medium skin tone +1F919 1F3FE ; fully-qualified # 🤙🏾 E3.0 call me hand: medium-dark skin tone +1F919 1F3FF ; fully-qualified # 🤙🏿 E3.0 call me hand: dark skin tone + +# subgroup: hand-single-finger +1F448 ; fully-qualified # 👈 E0.6 backhand index pointing left +1F448 1F3FB ; fully-qualified # 👈🏻 E1.0 backhand index pointing left: light skin tone +1F448 1F3FC ; fully-qualified # 👈🏼 E1.0 backhand index pointing left: medium-light skin tone +1F448 1F3FD ; fully-qualified # 👈🏽 E1.0 backhand index pointing left: medium skin tone +1F448 1F3FE ; fully-qualified # 👈🏾 E1.0 backhand index pointing left: medium-dark skin tone +1F448 1F3FF ; fully-qualified # 👈🏿 E1.0 backhand index pointing left: dark skin tone +1F449 ; fully-qualified # 👉 E0.6 backhand index pointing right +1F449 1F3FB ; fully-qualified # 👉🏻 E1.0 backhand index pointing right: light skin tone +1F449 1F3FC ; fully-qualified # 👉🏼 E1.0 backhand index pointing right: medium-light skin tone +1F449 1F3FD ; fully-qualified # 👉🏽 E1.0 backhand index pointing right: medium skin tone +1F449 1F3FE ; fully-qualified # 👉🏾 E1.0 backhand index pointing right: medium-dark skin tone +1F449 1F3FF ; fully-qualified # 👉🏿 E1.0 backhand index pointing right: dark skin tone +1F446 ; fully-qualified # 👆 E0.6 backhand index pointing up +1F446 1F3FB ; fully-qualified # 👆🏻 E1.0 backhand index pointing up: light skin tone +1F446 1F3FC ; fully-qualified # 👆🏼 E1.0 backhand index pointing up: medium-light skin tone +1F446 1F3FD ; fully-qualified # 👆🏽 E1.0 backhand index pointing up: medium skin tone +1F446 1F3FE ; fully-qualified # 👆🏾 E1.0 backhand index pointing up: medium-dark skin tone +1F446 1F3FF ; fully-qualified # 👆🏿 E1.0 backhand index pointing up: dark skin tone +1F595 ; fully-qualified # 🖕 E1.0 middle finger +1F595 1F3FB ; fully-qualified # 🖕🏻 E1.0 middle finger: light skin tone +1F595 1F3FC ; fully-qualified # 🖕🏼 E1.0 middle finger: medium-light skin tone +1F595 1F3FD ; fully-qualified # 🖕🏽 E1.0 middle finger: medium skin tone +1F595 1F3FE ; fully-qualified # 🖕🏾 E1.0 middle finger: medium-dark skin tone +1F595 1F3FF ; fully-qualified # 🖕🏿 E1.0 middle finger: dark skin tone +1F447 ; fully-qualified # 👇 E0.6 backhand index pointing down +1F447 1F3FB ; fully-qualified # 👇🏻 E1.0 backhand index pointing down: light skin tone +1F447 1F3FC ; fully-qualified # 👇🏼 E1.0 backhand index pointing down: medium-light skin tone +1F447 1F3FD ; fully-qualified # 👇🏽 E1.0 backhand index pointing down: medium skin tone +1F447 1F3FE ; fully-qualified # 👇🏾 E1.0 backhand index pointing down: medium-dark skin tone +1F447 1F3FF ; fully-qualified # 👇🏿 E1.0 backhand index pointing down: dark skin tone +261D FE0F ; fully-qualified # ☝️ E0.6 index pointing up +261D ; unqualified # ☝ E0.6 index pointing up +261D 1F3FB ; fully-qualified # ☝🏻 E1.0 index pointing up: light skin tone +261D 1F3FC ; fully-qualified # ☝🏼 E1.0 index pointing up: medium-light skin tone +261D 1F3FD ; fully-qualified # ☝🏽 E1.0 index pointing up: medium skin tone +261D 1F3FE ; fully-qualified # ☝🏾 E1.0 index pointing up: medium-dark skin tone +261D 1F3FF ; fully-qualified # ☝🏿 E1.0 index pointing up: dark skin tone + +# subgroup: hand-fingers-closed +1F44D ; fully-qualified # 👍 E0.6 thumbs up +1F44D 1F3FB ; fully-qualified # 👍🏻 E1.0 thumbs up: light skin tone +1F44D 1F3FC ; fully-qualified # 👍🏼 E1.0 thumbs up: medium-light skin tone +1F44D 1F3FD ; fully-qualified # 👍🏽 E1.0 thumbs up: medium skin tone +1F44D 1F3FE ; fully-qualified # 👍🏾 E1.0 thumbs up: medium-dark skin tone +1F44D 1F3FF ; fully-qualified # 👍🏿 E1.0 thumbs up: dark skin tone +1F44E ; fully-qualified # 👎 E0.6 thumbs down +1F44E 1F3FB ; fully-qualified # 👎🏻 E1.0 thumbs down: light skin tone +1F44E 1F3FC ; fully-qualified # 👎🏼 E1.0 thumbs down: medium-light skin tone +1F44E 1F3FD ; fully-qualified # 👎🏽 E1.0 thumbs down: medium skin tone +1F44E 1F3FE ; fully-qualified # 👎🏾 E1.0 thumbs down: medium-dark skin tone +1F44E 1F3FF ; fully-qualified # 👎🏿 E1.0 thumbs down: dark skin tone +270A ; fully-qualified # ✊ E0.6 raised fist +270A 1F3FB ; fully-qualified # ✊🏻 E1.0 raised fist: light skin tone +270A 1F3FC ; fully-qualified # ✊🏼 E1.0 raised fist: medium-light skin tone +270A 1F3FD ; fully-qualified # ✊🏽 E1.0 raised fist: medium skin tone +270A 1F3FE ; fully-qualified # ✊🏾 E1.0 raised fist: medium-dark skin tone +270A 1F3FF ; fully-qualified # ✊🏿 E1.0 raised fist: dark skin tone +1F44A ; fully-qualified # 👊 E0.6 oncoming fist +1F44A 1F3FB ; fully-qualified # 👊🏻 E1.0 oncoming fist: light skin tone +1F44A 1F3FC ; fully-qualified # 👊🏼 E1.0 oncoming fist: medium-light skin tone +1F44A 1F3FD ; fully-qualified # 👊🏽 E1.0 oncoming fist: medium skin tone +1F44A 1F3FE ; fully-qualified # 👊🏾 E1.0 oncoming fist: medium-dark skin tone +1F44A 1F3FF ; fully-qualified # 👊🏿 E1.0 oncoming fist: dark skin tone +1F91B ; fully-qualified # 🤛 E3.0 left-facing fist +1F91B 1F3FB ; fully-qualified # 🤛🏻 E3.0 left-facing fist: light skin tone +1F91B 1F3FC ; fully-qualified # 🤛🏼 E3.0 left-facing fist: medium-light skin tone +1F91B 1F3FD ; fully-qualified # 🤛🏽 E3.0 left-facing fist: medium skin tone +1F91B 1F3FE ; fully-qualified # 🤛🏾 E3.0 left-facing fist: medium-dark skin tone +1F91B 1F3FF ; fully-qualified # 🤛🏿 E3.0 left-facing fist: dark skin tone +1F91C ; fully-qualified # 🤜 E3.0 right-facing fist +1F91C 1F3FB ; fully-qualified # 🤜🏻 E3.0 right-facing fist: light skin tone +1F91C 1F3FC ; fully-qualified # 🤜🏼 E3.0 right-facing fist: medium-light skin tone +1F91C 1F3FD ; fully-qualified # 🤜🏽 E3.0 right-facing fist: medium skin tone +1F91C 1F3FE ; fully-qualified # 🤜🏾 E3.0 right-facing fist: medium-dark skin tone +1F91C 1F3FF ; fully-qualified # 🤜🏿 E3.0 right-facing fist: dark skin tone + +# subgroup: hands +1F44F ; fully-qualified # 👏 E0.6 clapping hands +1F44F 1F3FB ; fully-qualified # 👏🏻 E1.0 clapping hands: light skin tone +1F44F 1F3FC ; fully-qualified # 👏🏼 E1.0 clapping hands: medium-light skin tone +1F44F 1F3FD ; fully-qualified # 👏🏽 E1.0 clapping hands: medium skin tone +1F44F 1F3FE ; fully-qualified # 👏🏾 E1.0 clapping hands: medium-dark skin tone +1F44F 1F3FF ; fully-qualified # 👏🏿 E1.0 clapping hands: dark skin tone +1F64C ; fully-qualified # 🙌 E0.6 raising hands +1F64C 1F3FB ; fully-qualified # 🙌🏻 E1.0 raising hands: light skin tone +1F64C 1F3FC ; fully-qualified # 🙌🏼 E1.0 raising hands: medium-light skin tone +1F64C 1F3FD ; fully-qualified # 🙌🏽 E1.0 raising hands: medium skin tone +1F64C 1F3FE ; fully-qualified # 🙌🏾 E1.0 raising hands: medium-dark skin tone +1F64C 1F3FF ; fully-qualified # 🙌🏿 E1.0 raising hands: dark skin tone +1F450 ; fully-qualified # 👐 E0.6 open hands +1F450 1F3FB ; fully-qualified # 👐🏻 E1.0 open hands: light skin tone +1F450 1F3FC ; fully-qualified # 👐🏼 E1.0 open hands: medium-light skin tone +1F450 1F3FD ; fully-qualified # 👐🏽 E1.0 open hands: medium skin tone +1F450 1F3FE ; fully-qualified # 👐🏾 E1.0 open hands: medium-dark skin tone +1F450 1F3FF ; fully-qualified # 👐🏿 E1.0 open hands: dark skin tone +1F932 ; fully-qualified # 🤲 E5.0 palms up together +1F932 1F3FB ; fully-qualified # 🤲🏻 E5.0 palms up together: light skin tone +1F932 1F3FC ; fully-qualified # 🤲🏼 E5.0 palms up together: medium-light skin tone +1F932 1F3FD ; fully-qualified # 🤲🏽 E5.0 palms up together: medium skin tone +1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone +1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone +1F91D ; fully-qualified # 🤝 E3.0 handshake +1F64F ; fully-qualified # 🙏 E0.6 folded hands +1F64F 1F3FB ; fully-qualified # 🙏🏻 E1.0 folded hands: light skin tone +1F64F 1F3FC ; fully-qualified # 🙏🏼 E1.0 folded hands: medium-light skin tone +1F64F 1F3FD ; fully-qualified # 🙏🏽 E1.0 folded hands: medium skin tone +1F64F 1F3FE ; fully-qualified # 🙏🏾 E1.0 folded hands: medium-dark skin tone +1F64F 1F3FF ; fully-qualified # 🙏🏿 E1.0 folded hands: dark skin tone + +# subgroup: hand-prop +270D FE0F ; fully-qualified # ✍️ E0.7 writing hand +270D ; unqualified # ✍ E0.7 writing hand +270D 1F3FB ; fully-qualified # ✍🏻 E1.0 writing hand: light skin tone +270D 1F3FC ; fully-qualified # ✍🏼 E1.0 writing hand: medium-light skin tone +270D 1F3FD ; fully-qualified # ✍🏽 E1.0 writing hand: medium skin tone +270D 1F3FE ; fully-qualified # ✍🏾 E1.0 writing hand: medium-dark skin tone +270D 1F3FF ; fully-qualified # ✍🏿 E1.0 writing hand: dark skin tone +1F485 ; fully-qualified # 💅 E0.6 nail polish +1F485 1F3FB ; fully-qualified # 💅🏻 E1.0 nail polish: light skin tone +1F485 1F3FC ; fully-qualified # 💅🏼 E1.0 nail polish: medium-light skin tone +1F485 1F3FD ; fully-qualified # 💅🏽 E1.0 nail polish: medium skin tone +1F485 1F3FE ; fully-qualified # 💅🏾 E1.0 nail polish: medium-dark skin tone +1F485 1F3FF ; fully-qualified # 💅🏿 E1.0 nail polish: dark skin tone +1F933 ; fully-qualified # 🤳 E3.0 selfie +1F933 1F3FB ; fully-qualified # 🤳🏻 E3.0 selfie: light skin tone +1F933 1F3FC ; fully-qualified # 🤳🏼 E3.0 selfie: medium-light skin tone +1F933 1F3FD ; fully-qualified # 🤳🏽 E3.0 selfie: medium skin tone +1F933 1F3FE ; fully-qualified # 🤳🏾 E3.0 selfie: medium-dark skin tone +1F933 1F3FF ; fully-qualified # 🤳🏿 E3.0 selfie: dark skin tone + +# subgroup: body-parts +1F4AA ; fully-qualified # 💪 E0.6 flexed biceps +1F4AA 1F3FB ; fully-qualified # 💪🏻 E1.0 flexed biceps: light skin tone +1F4AA 1F3FC ; fully-qualified # 💪🏼 E1.0 flexed biceps: medium-light skin tone +1F4AA 1F3FD ; fully-qualified # 💪🏽 E1.0 flexed biceps: medium skin tone +1F4AA 1F3FE ; fully-qualified # 💪🏾 E1.0 flexed biceps: medium-dark skin tone +1F4AA 1F3FF ; fully-qualified # 💪🏿 E1.0 flexed biceps: dark skin tone +1F9BE ; fully-qualified # 🦾 E12.0 mechanical arm +1F9BF ; fully-qualified # 🦿 E12.0 mechanical leg +1F9B5 ; fully-qualified # 🦵 E11.0 leg +1F9B5 1F3FB ; fully-qualified # 🦵🏻 E11.0 leg: light skin tone +1F9B5 1F3FC ; fully-qualified # 🦵🏼 E11.0 leg: medium-light skin tone +1F9B5 1F3FD ; fully-qualified # 🦵🏽 E11.0 leg: medium skin tone +1F9B5 1F3FE ; fully-qualified # 🦵🏾 E11.0 leg: medium-dark skin tone +1F9B5 1F3FF ; fully-qualified # 🦵🏿 E11.0 leg: dark skin tone +1F9B6 ; fully-qualified # 🦶 E11.0 foot +1F9B6 1F3FB ; fully-qualified # 🦶🏻 E11.0 foot: light skin tone +1F9B6 1F3FC ; fully-qualified # 🦶🏼 E11.0 foot: medium-light skin tone +1F9B6 1F3FD ; fully-qualified # 🦶🏽 E11.0 foot: medium skin tone +1F9B6 1F3FE ; fully-qualified # 🦶🏾 E11.0 foot: medium-dark skin tone +1F9B6 1F3FF ; fully-qualified # 🦶🏿 E11.0 foot: dark skin tone +1F442 ; fully-qualified # 👂 E0.6 ear +1F442 1F3FB ; fully-qualified # 👂🏻 E1.0 ear: light skin tone +1F442 1F3FC ; fully-qualified # 👂🏼 E1.0 ear: medium-light skin tone +1F442 1F3FD ; fully-qualified # 👂🏽 E1.0 ear: medium skin tone +1F442 1F3FE ; fully-qualified # 👂🏾 E1.0 ear: medium-dark skin tone +1F442 1F3FF ; fully-qualified # 👂🏿 E1.0 ear: dark skin tone +1F9BB ; fully-qualified # 🦻 E12.0 ear with hearing aid +1F9BB 1F3FB ; fully-qualified # 🦻🏻 E12.0 ear with hearing aid: light skin tone +1F9BB 1F3FC ; fully-qualified # 🦻🏼 E12.0 ear with hearing aid: medium-light skin tone +1F9BB 1F3FD ; fully-qualified # 🦻🏽 E12.0 ear with hearing aid: medium skin tone +1F9BB 1F3FE ; fully-qualified # 🦻🏾 E12.0 ear with hearing aid: medium-dark skin tone +1F9BB 1F3FF ; fully-qualified # 🦻🏿 E12.0 ear with hearing aid: dark skin tone +1F443 ; fully-qualified # 👃 E0.6 nose +1F443 1F3FB ; fully-qualified # 👃🏻 E1.0 nose: light skin tone +1F443 1F3FC ; fully-qualified # 👃🏼 E1.0 nose: medium-light skin tone +1F443 1F3FD ; fully-qualified # 👃🏽 E1.0 nose: medium skin tone +1F443 1F3FE ; fully-qualified # 👃🏾 E1.0 nose: medium-dark skin tone +1F443 1F3FF ; fully-qualified # 👃🏿 E1.0 nose: dark skin tone +1F9E0 ; fully-qualified # 🧠 E5.0 brain +1FAC0 ; fully-qualified # 🫀 E13.0 anatomical heart +1FAC1 ; fully-qualified # 🫁 E13.0 lungs +1F9B7 ; fully-qualified # 🦷 E11.0 tooth +1F9B4 ; fully-qualified # 🦴 E11.0 bone +1F440 ; fully-qualified # 👀 E0.6 eyes +1F441 FE0F ; fully-qualified # 👁️ E0.7 eye +1F441 ; unqualified # 👁 E0.7 eye +1F445 ; fully-qualified # 👅 E0.6 tongue +1F444 ; fully-qualified # 👄 E0.6 mouth + +# subgroup: person +1F476 ; fully-qualified # 👶 E0.6 baby +1F476 1F3FB ; fully-qualified # 👶🏻 E1.0 baby: light skin tone +1F476 1F3FC ; fully-qualified # 👶🏼 E1.0 baby: medium-light skin tone +1F476 1F3FD ; fully-qualified # 👶🏽 E1.0 baby: medium skin tone +1F476 1F3FE ; fully-qualified # 👶🏾 E1.0 baby: medium-dark skin tone +1F476 1F3FF ; fully-qualified # 👶🏿 E1.0 baby: dark skin tone +1F9D2 ; fully-qualified # 🧒 E5.0 child +1F9D2 1F3FB ; fully-qualified # 🧒🏻 E5.0 child: light skin tone +1F9D2 1F3FC ; fully-qualified # 🧒🏼 E5.0 child: medium-light skin tone +1F9D2 1F3FD ; fully-qualified # 🧒🏽 E5.0 child: medium skin tone +1F9D2 1F3FE ; fully-qualified # 🧒🏾 E5.0 child: medium-dark skin tone +1F9D2 1F3FF ; fully-qualified # 🧒🏿 E5.0 child: dark skin tone +1F466 ; fully-qualified # 👦 E0.6 boy +1F466 1F3FB ; fully-qualified # 👦🏻 E1.0 boy: light skin tone +1F466 1F3FC ; fully-qualified # 👦🏼 E1.0 boy: medium-light skin tone +1F466 1F3FD ; fully-qualified # 👦🏽 E1.0 boy: medium skin tone +1F466 1F3FE ; fully-qualified # 👦🏾 E1.0 boy: medium-dark skin tone +1F466 1F3FF ; fully-qualified # 👦🏿 E1.0 boy: dark skin tone +1F467 ; fully-qualified # 👧 E0.6 girl +1F467 1F3FB ; fully-qualified # 👧🏻 E1.0 girl: light skin tone +1F467 1F3FC ; fully-qualified # 👧🏼 E1.0 girl: medium-light skin tone +1F467 1F3FD ; fully-qualified # 👧🏽 E1.0 girl: medium skin tone +1F467 1F3FE ; fully-qualified # 👧🏾 E1.0 girl: medium-dark skin tone +1F467 1F3FF ; fully-qualified # 👧🏿 E1.0 girl: dark skin tone +1F9D1 ; fully-qualified # 🧑 E5.0 person +1F9D1 1F3FB ; fully-qualified # 🧑🏻 E5.0 person: light skin tone +1F9D1 1F3FC ; fully-qualified # 🧑🏼 E5.0 person: medium-light skin tone +1F9D1 1F3FD ; fully-qualified # 🧑🏽 E5.0 person: medium skin tone +1F9D1 1F3FE ; fully-qualified # 🧑🏾 E5.0 person: medium-dark skin tone +1F9D1 1F3FF ; fully-qualified # 🧑🏿 E5.0 person: dark skin tone +1F471 ; fully-qualified # 👱 E0.6 person: blond hair +1F471 1F3FB ; fully-qualified # 👱🏻 E1.0 person: light skin tone, blond hair +1F471 1F3FC ; fully-qualified # 👱🏼 E1.0 person: medium-light skin tone, blond hair +1F471 1F3FD ; fully-qualified # 👱🏽 E1.0 person: medium skin tone, blond hair +1F471 1F3FE ; fully-qualified # 👱🏾 E1.0 person: medium-dark skin tone, blond hair +1F471 1F3FF ; fully-qualified # 👱🏿 E1.0 person: dark skin tone, blond hair +1F468 ; fully-qualified # 👨 E0.6 man +1F468 1F3FB ; fully-qualified # 👨🏻 E1.0 man: light skin tone +1F468 1F3FC ; fully-qualified # 👨🏼 E1.0 man: medium-light skin tone +1F468 1F3FD ; fully-qualified # 👨🏽 E1.0 man: medium skin tone +1F468 1F3FE ; fully-qualified # 👨🏾 E1.0 man: medium-dark skin tone +1F468 1F3FF ; fully-qualified # 👨🏿 E1.0 man: dark skin tone +1F9D4 ; fully-qualified # 🧔 E5.0 person: beard +1F9D4 1F3FB ; fully-qualified # 🧔🏻 E5.0 person: light skin tone, beard +1F9D4 1F3FC ; fully-qualified # 🧔🏼 E5.0 person: medium-light skin tone, beard +1F9D4 1F3FD ; fully-qualified # 🧔🏽 E5.0 person: medium skin tone, beard +1F9D4 1F3FE ; fully-qualified # 🧔🏾 E5.0 person: medium-dark skin tone, beard +1F9D4 1F3FF ; fully-qualified # 🧔🏿 E5.0 person: dark skin tone, beard +1F9D4 200D 2642 FE0F ; fully-qualified # 🧔‍♂️ E13.1 man: beard +1F9D4 200D 2642 ; minimally-qualified # 🧔‍♂ E13.1 man: beard +1F9D4 1F3FB 200D 2642 FE0F ; fully-qualified # 🧔🏻‍♂️ E13.1 man: light skin tone, beard +1F9D4 1F3FB 200D 2642 ; minimally-qualified # 🧔🏻‍♂ E13.1 man: light skin tone, beard +1F9D4 1F3FC 200D 2642 FE0F ; fully-qualified # 🧔🏼‍♂️ E13.1 man: medium-light skin tone, beard +1F9D4 1F3FC 200D 2642 ; minimally-qualified # 🧔🏼‍♂ E13.1 man: medium-light skin tone, beard +1F9D4 1F3FD 200D 2642 FE0F ; fully-qualified # 🧔🏽‍♂️ E13.1 man: medium skin tone, beard +1F9D4 1F3FD 200D 2642 ; minimally-qualified # 🧔🏽‍♂ E13.1 man: medium skin tone, beard +1F9D4 1F3FE 200D 2642 FE0F ; fully-qualified # 🧔🏾‍♂️ E13.1 man: medium-dark skin tone, beard +1F9D4 1F3FE 200D 2642 ; minimally-qualified # 🧔🏾‍♂ E13.1 man: medium-dark skin tone, beard +1F9D4 1F3FF 200D 2642 FE0F ; fully-qualified # 🧔🏿‍♂️ E13.1 man: dark skin tone, beard +1F9D4 1F3FF 200D 2642 ; minimally-qualified # 🧔🏿‍♂ E13.1 man: dark skin tone, beard +1F9D4 200D 2640 FE0F ; fully-qualified # 🧔‍♀️ E13.1 woman: beard +1F9D4 200D 2640 ; minimally-qualified # 🧔‍♀ E13.1 woman: beard +1F9D4 1F3FB 200D 2640 FE0F ; fully-qualified # 🧔🏻‍♀️ E13.1 woman: light skin tone, beard +1F9D4 1F3FB 200D 2640 ; minimally-qualified # 🧔🏻‍♀ E13.1 woman: light skin tone, beard +1F9D4 1F3FC 200D 2640 FE0F ; fully-qualified # 🧔🏼‍♀️ E13.1 woman: medium-light skin tone, beard +1F9D4 1F3FC 200D 2640 ; minimally-qualified # 🧔🏼‍♀ E13.1 woman: medium-light skin tone, beard +1F9D4 1F3FD 200D 2640 FE0F ; fully-qualified # 🧔🏽‍♀️ E13.1 woman: medium skin tone, beard +1F9D4 1F3FD 200D 2640 ; minimally-qualified # 🧔🏽‍♀ E13.1 woman: medium skin tone, beard +1F9D4 1F3FE 200D 2640 FE0F ; fully-qualified # 🧔🏾‍♀️ E13.1 woman: medium-dark skin tone, beard +1F9D4 1F3FE 200D 2640 ; minimally-qualified # 🧔🏾‍♀ E13.1 woman: medium-dark skin tone, beard +1F9D4 1F3FF 200D 2640 FE0F ; fully-qualified # 🧔🏿‍♀️ E13.1 woman: dark skin tone, beard +1F9D4 1F3FF 200D 2640 ; minimally-qualified # 🧔🏿‍♀ E13.1 woman: dark skin tone, beard +1F468 200D 1F9B0 ; fully-qualified # 👨‍🦰 E11.0 man: red hair +1F468 1F3FB 200D 1F9B0 ; fully-qualified # 👨🏻‍🦰 E11.0 man: light skin tone, red hair +1F468 1F3FC 200D 1F9B0 ; fully-qualified # 👨🏼‍🦰 E11.0 man: medium-light skin tone, red hair +1F468 1F3FD 200D 1F9B0 ; fully-qualified # 👨🏽‍🦰 E11.0 man: medium skin tone, red hair +1F468 1F3FE 200D 1F9B0 ; fully-qualified # 👨🏾‍🦰 E11.0 man: medium-dark skin tone, red hair +1F468 1F3FF 200D 1F9B0 ; fully-qualified # 👨🏿‍🦰 E11.0 man: dark skin tone, red hair +1F468 200D 1F9B1 ; fully-qualified # 👨‍🦱 E11.0 man: curly hair +1F468 1F3FB 200D 1F9B1 ; fully-qualified # 👨🏻‍🦱 E11.0 man: light skin tone, curly hair +1F468 1F3FC 200D 1F9B1 ; fully-qualified # 👨🏼‍🦱 E11.0 man: medium-light skin tone, curly hair +1F468 1F3FD 200D 1F9B1 ; fully-qualified # 👨🏽‍🦱 E11.0 man: medium skin tone, curly hair +1F468 1F3FE 200D 1F9B1 ; fully-qualified # 👨🏾‍🦱 E11.0 man: medium-dark skin tone, curly hair +1F468 1F3FF 200D 1F9B1 ; fully-qualified # 👨🏿‍🦱 E11.0 man: dark skin tone, curly hair +1F468 200D 1F9B3 ; fully-qualified # 👨‍🦳 E11.0 man: white hair +1F468 1F3FB 200D 1F9B3 ; fully-qualified # 👨🏻‍🦳 E11.0 man: light skin tone, white hair +1F468 1F3FC 200D 1F9B3 ; fully-qualified # 👨🏼‍🦳 E11.0 man: medium-light skin tone, white hair +1F468 1F3FD 200D 1F9B3 ; fully-qualified # 👨🏽‍🦳 E11.0 man: medium skin tone, white hair +1F468 1F3FE 200D 1F9B3 ; fully-qualified # 👨🏾‍🦳 E11.0 man: medium-dark skin tone, white hair +1F468 1F3FF 200D 1F9B3 ; fully-qualified # 👨🏿‍🦳 E11.0 man: dark skin tone, white hair +1F468 200D 1F9B2 ; fully-qualified # 👨‍🦲 E11.0 man: bald +1F468 1F3FB 200D 1F9B2 ; fully-qualified # 👨🏻‍🦲 E11.0 man: light skin tone, bald +1F468 1F3FC 200D 1F9B2 ; fully-qualified # 👨🏼‍🦲 E11.0 man: medium-light skin tone, bald +1F468 1F3FD 200D 1F9B2 ; fully-qualified # 👨🏽‍🦲 E11.0 man: medium skin tone, bald +1F468 1F3FE 200D 1F9B2 ; fully-qualified # 👨🏾‍🦲 E11.0 man: medium-dark skin tone, bald +1F468 1F3FF 200D 1F9B2 ; fully-qualified # 👨🏿‍🦲 E11.0 man: dark skin tone, bald +1F469 ; fully-qualified # 👩 E0.6 woman +1F469 1F3FB ; fully-qualified # 👩🏻 E1.0 woman: light skin tone +1F469 1F3FC ; fully-qualified # 👩🏼 E1.0 woman: medium-light skin tone +1F469 1F3FD ; fully-qualified # 👩🏽 E1.0 woman: medium skin tone +1F469 1F3FE ; fully-qualified # 👩🏾 E1.0 woman: medium-dark skin tone +1F469 1F3FF ; fully-qualified # 👩🏿 E1.0 woman: dark skin tone +1F469 200D 1F9B0 ; fully-qualified # 👩‍🦰 E11.0 woman: red hair +1F469 1F3FB 200D 1F9B0 ; fully-qualified # 👩🏻‍🦰 E11.0 woman: light skin tone, red hair +1F469 1F3FC 200D 1F9B0 ; fully-qualified # 👩🏼‍🦰 E11.0 woman: medium-light skin tone, red hair +1F469 1F3FD 200D 1F9B0 ; fully-qualified # 👩🏽‍🦰 E11.0 woman: medium skin tone, red hair +1F469 1F3FE 200D 1F9B0 ; fully-qualified # 👩🏾‍🦰 E11.0 woman: medium-dark skin tone, red hair +1F469 1F3FF 200D 1F9B0 ; fully-qualified # 👩🏿‍🦰 E11.0 woman: dark skin tone, red hair +1F9D1 200D 1F9B0 ; fully-qualified # 🧑‍🦰 E12.1 person: red hair +1F9D1 1F3FB 200D 1F9B0 ; fully-qualified # 🧑🏻‍🦰 E12.1 person: light skin tone, red hair +1F9D1 1F3FC 200D 1F9B0 ; fully-qualified # 🧑🏼‍🦰 E12.1 person: medium-light skin tone, red hair +1F9D1 1F3FD 200D 1F9B0 ; fully-qualified # 🧑🏽‍🦰 E12.1 person: medium skin tone, red hair +1F9D1 1F3FE 200D 1F9B0 ; fully-qualified # 🧑🏾‍🦰 E12.1 person: medium-dark skin tone, red hair +1F9D1 1F3FF 200D 1F9B0 ; fully-qualified # 🧑🏿‍🦰 E12.1 person: dark skin tone, red hair +1F469 200D 1F9B1 ; fully-qualified # 👩‍🦱 E11.0 woman: curly hair +1F469 1F3FB 200D 1F9B1 ; fully-qualified # 👩🏻‍🦱 E11.0 woman: light skin tone, curly hair +1F469 1F3FC 200D 1F9B1 ; fully-qualified # 👩🏼‍🦱 E11.0 woman: medium-light skin tone, curly hair +1F469 1F3FD 200D 1F9B1 ; fully-qualified # 👩🏽‍🦱 E11.0 woman: medium skin tone, curly hair +1F469 1F3FE 200D 1F9B1 ; fully-qualified # 👩🏾‍🦱 E11.0 woman: medium-dark skin tone, curly hair +1F469 1F3FF 200D 1F9B1 ; fully-qualified # 👩🏿‍🦱 E11.0 woman: dark skin tone, curly hair +1F9D1 200D 1F9B1 ; fully-qualified # 🧑‍🦱 E12.1 person: curly hair +1F9D1 1F3FB 200D 1F9B1 ; fully-qualified # 🧑🏻‍🦱 E12.1 person: light skin tone, curly hair +1F9D1 1F3FC 200D 1F9B1 ; fully-qualified # 🧑🏼‍🦱 E12.1 person: medium-light skin tone, curly hair +1F9D1 1F3FD 200D 1F9B1 ; fully-qualified # 🧑🏽‍🦱 E12.1 person: medium skin tone, curly hair +1F9D1 1F3FE 200D 1F9B1 ; fully-qualified # 🧑🏾‍🦱 E12.1 person: medium-dark skin tone, curly hair +1F9D1 1F3FF 200D 1F9B1 ; fully-qualified # 🧑🏿‍🦱 E12.1 person: dark skin tone, curly hair +1F469 200D 1F9B3 ; fully-qualified # 👩‍🦳 E11.0 woman: white hair +1F469 1F3FB 200D 1F9B3 ; fully-qualified # 👩🏻‍🦳 E11.0 woman: light skin tone, white hair +1F469 1F3FC 200D 1F9B3 ; fully-qualified # 👩🏼‍🦳 E11.0 woman: medium-light skin tone, white hair +1F469 1F3FD 200D 1F9B3 ; fully-qualified # 👩🏽‍🦳 E11.0 woman: medium skin tone, white hair +1F469 1F3FE 200D 1F9B3 ; fully-qualified # 👩🏾‍🦳 E11.0 woman: medium-dark skin tone, white hair +1F469 1F3FF 200D 1F9B3 ; fully-qualified # 👩🏿‍🦳 E11.0 woman: dark skin tone, white hair +1F9D1 200D 1F9B3 ; fully-qualified # 🧑‍🦳 E12.1 person: white hair +1F9D1 1F3FB 200D 1F9B3 ; fully-qualified # 🧑🏻‍🦳 E12.1 person: light skin tone, white hair +1F9D1 1F3FC 200D 1F9B3 ; fully-qualified # 🧑🏼‍🦳 E12.1 person: medium-light skin tone, white hair +1F9D1 1F3FD 200D 1F9B3 ; fully-qualified # 🧑🏽‍🦳 E12.1 person: medium skin tone, white hair +1F9D1 1F3FE 200D 1F9B3 ; fully-qualified # 🧑🏾‍🦳 E12.1 person: medium-dark skin tone, white hair +1F9D1 1F3FF 200D 1F9B3 ; fully-qualified # 🧑🏿‍🦳 E12.1 person: dark skin tone, white hair +1F469 200D 1F9B2 ; fully-qualified # 👩‍🦲 E11.0 woman: bald +1F469 1F3FB 200D 1F9B2 ; fully-qualified # 👩🏻‍🦲 E11.0 woman: light skin tone, bald +1F469 1F3FC 200D 1F9B2 ; fully-qualified # 👩🏼‍🦲 E11.0 woman: medium-light skin tone, bald +1F469 1F3FD 200D 1F9B2 ; fully-qualified # 👩🏽‍🦲 E11.0 woman: medium skin tone, bald +1F469 1F3FE 200D 1F9B2 ; fully-qualified # 👩🏾‍🦲 E11.0 woman: medium-dark skin tone, bald +1F469 1F3FF 200D 1F9B2 ; fully-qualified # 👩🏿‍🦲 E11.0 woman: dark skin tone, bald +1F9D1 200D 1F9B2 ; fully-qualified # 🧑‍🦲 E12.1 person: bald +1F9D1 1F3FB 200D 1F9B2 ; fully-qualified # 🧑🏻‍🦲 E12.1 person: light skin tone, bald +1F9D1 1F3FC 200D 1F9B2 ; fully-qualified # 🧑🏼‍🦲 E12.1 person: medium-light skin tone, bald +1F9D1 1F3FD 200D 1F9B2 ; fully-qualified # 🧑🏽‍🦲 E12.1 person: medium skin tone, bald +1F9D1 1F3FE 200D 1F9B2 ; fully-qualified # 🧑🏾‍🦲 E12.1 person: medium-dark skin tone, bald +1F9D1 1F3FF 200D 1F9B2 ; fully-qualified # 🧑🏿‍🦲 E12.1 person: dark skin tone, bald +1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ E4.0 woman: blond hair +1F471 200D 2640 ; minimally-qualified # 👱‍♀ E4.0 woman: blond hair +1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ E4.0 woman: light skin tone, blond hair +1F471 1F3FB 200D 2640 ; minimally-qualified # 👱🏻‍♀ E4.0 woman: light skin tone, blond hair +1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ E4.0 woman: medium-light skin tone, blond hair +1F471 1F3FC 200D 2640 ; minimally-qualified # 👱🏼‍♀ E4.0 woman: medium-light skin tone, blond hair +1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ E4.0 woman: medium skin tone, blond hair +1F471 1F3FD 200D 2640 ; minimally-qualified # 👱🏽‍♀ E4.0 woman: medium skin tone, blond hair +1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ E4.0 woman: medium-dark skin tone, blond hair +1F471 1F3FE 200D 2640 ; minimally-qualified # 👱🏾‍♀ E4.0 woman: medium-dark skin tone, blond hair +1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ E4.0 woman: dark skin tone, blond hair +1F471 1F3FF 200D 2640 ; minimally-qualified # 👱🏿‍♀ E4.0 woman: dark skin tone, blond hair +1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ E4.0 man: blond hair +1F471 200D 2642 ; minimally-qualified # 👱‍♂ E4.0 man: blond hair +1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ E4.0 man: light skin tone, blond hair +1F471 1F3FB 200D 2642 ; minimally-qualified # 👱🏻‍♂ E4.0 man: light skin tone, blond hair +1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ E4.0 man: medium-light skin tone, blond hair +1F471 1F3FC 200D 2642 ; minimally-qualified # 👱🏼‍♂ E4.0 man: medium-light skin tone, blond hair +1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ E4.0 man: medium skin tone, blond hair +1F471 1F3FD 200D 2642 ; minimally-qualified # 👱🏽‍♂ E4.0 man: medium skin tone, blond hair +1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ E4.0 man: medium-dark skin tone, blond hair +1F471 1F3FE 200D 2642 ; minimally-qualified # 👱🏾‍♂ E4.0 man: medium-dark skin tone, blond hair +1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ E4.0 man: dark skin tone, blond hair +1F471 1F3FF 200D 2642 ; minimally-qualified # 👱🏿‍♂ E4.0 man: dark skin tone, blond hair +1F9D3 ; fully-qualified # 🧓 E5.0 older person +1F9D3 1F3FB ; fully-qualified # 🧓🏻 E5.0 older person: light skin tone +1F9D3 1F3FC ; fully-qualified # 🧓🏼 E5.0 older person: medium-light skin tone +1F9D3 1F3FD ; fully-qualified # 🧓🏽 E5.0 older person: medium skin tone +1F9D3 1F3FE ; fully-qualified # 🧓🏾 E5.0 older person: medium-dark skin tone +1F9D3 1F3FF ; fully-qualified # 🧓🏿 E5.0 older person: dark skin tone +1F474 ; fully-qualified # 👴 E0.6 old man +1F474 1F3FB ; fully-qualified # 👴🏻 E1.0 old man: light skin tone +1F474 1F3FC ; fully-qualified # 👴🏼 E1.0 old man: medium-light skin tone +1F474 1F3FD ; fully-qualified # 👴🏽 E1.0 old man: medium skin tone +1F474 1F3FE ; fully-qualified # 👴🏾 E1.0 old man: medium-dark skin tone +1F474 1F3FF ; fully-qualified # 👴🏿 E1.0 old man: dark skin tone +1F475 ; fully-qualified # 👵 E0.6 old woman +1F475 1F3FB ; fully-qualified # 👵🏻 E1.0 old woman: light skin tone +1F475 1F3FC ; fully-qualified # 👵🏼 E1.0 old woman: medium-light skin tone +1F475 1F3FD ; fully-qualified # 👵🏽 E1.0 old woman: medium skin tone +1F475 1F3FE ; fully-qualified # 👵🏾 E1.0 old woman: medium-dark skin tone +1F475 1F3FF ; fully-qualified # 👵🏿 E1.0 old woman: dark skin tone + +# subgroup: person-gesture +1F64D ; fully-qualified # 🙍 E0.6 person frowning +1F64D 1F3FB ; fully-qualified # 🙍🏻 E1.0 person frowning: light skin tone +1F64D 1F3FC ; fully-qualified # 🙍🏼 E1.0 person frowning: medium-light skin tone +1F64D 1F3FD ; fully-qualified # 🙍🏽 E1.0 person frowning: medium skin tone +1F64D 1F3FE ; fully-qualified # 🙍🏾 E1.0 person frowning: medium-dark skin tone +1F64D 1F3FF ; fully-qualified # 🙍🏿 E1.0 person frowning: dark skin tone +1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ E4.0 man frowning +1F64D 200D 2642 ; minimally-qualified # 🙍‍♂ E4.0 man frowning +1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ E4.0 man frowning: light skin tone +1F64D 1F3FB 200D 2642 ; minimally-qualified # 🙍🏻‍♂ E4.0 man frowning: light skin tone +1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ E4.0 man frowning: medium-light skin tone +1F64D 1F3FC 200D 2642 ; minimally-qualified # 🙍🏼‍♂ E4.0 man frowning: medium-light skin tone +1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ E4.0 man frowning: medium skin tone +1F64D 1F3FD 200D 2642 ; minimally-qualified # 🙍🏽‍♂ E4.0 man frowning: medium skin tone +1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ E4.0 man frowning: medium-dark skin tone +1F64D 1F3FE 200D 2642 ; minimally-qualified # 🙍🏾‍♂ E4.0 man frowning: medium-dark skin tone +1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ E4.0 man frowning: dark skin tone +1F64D 1F3FF 200D 2642 ; minimally-qualified # 🙍🏿‍♂ E4.0 man frowning: dark skin tone +1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ E4.0 woman frowning +1F64D 200D 2640 ; minimally-qualified # 🙍‍♀ E4.0 woman frowning +1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ E4.0 woman frowning: light skin tone +1F64D 1F3FB 200D 2640 ; minimally-qualified # 🙍🏻‍♀ E4.0 woman frowning: light skin tone +1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ E4.0 woman frowning: medium-light skin tone +1F64D 1F3FC 200D 2640 ; minimally-qualified # 🙍🏼‍♀ E4.0 woman frowning: medium-light skin tone +1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ E4.0 woman frowning: medium skin tone +1F64D 1F3FD 200D 2640 ; minimally-qualified # 🙍🏽‍♀ E4.0 woman frowning: medium skin tone +1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ E4.0 woman frowning: medium-dark skin tone +1F64D 1F3FE 200D 2640 ; minimally-qualified # 🙍🏾‍♀ E4.0 woman frowning: medium-dark skin tone +1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ E4.0 woman frowning: dark skin tone +1F64D 1F3FF 200D 2640 ; minimally-qualified # 🙍🏿‍♀ E4.0 woman frowning: dark skin tone +1F64E ; fully-qualified # 🙎 E0.6 person pouting +1F64E 1F3FB ; fully-qualified # 🙎🏻 E1.0 person pouting: light skin tone +1F64E 1F3FC ; fully-qualified # 🙎🏼 E1.0 person pouting: medium-light skin tone +1F64E 1F3FD ; fully-qualified # 🙎🏽 E1.0 person pouting: medium skin tone +1F64E 1F3FE ; fully-qualified # 🙎🏾 E1.0 person pouting: medium-dark skin tone +1F64E 1F3FF ; fully-qualified # 🙎🏿 E1.0 person pouting: dark skin tone +1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ E4.0 man pouting +1F64E 200D 2642 ; minimally-qualified # 🙎‍♂ E4.0 man pouting +1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ E4.0 man pouting: light skin tone +1F64E 1F3FB 200D 2642 ; minimally-qualified # 🙎🏻‍♂ E4.0 man pouting: light skin tone +1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ E4.0 man pouting: medium-light skin tone +1F64E 1F3FC 200D 2642 ; minimally-qualified # 🙎🏼‍♂ E4.0 man pouting: medium-light skin tone +1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ E4.0 man pouting: medium skin tone +1F64E 1F3FD 200D 2642 ; minimally-qualified # 🙎🏽‍♂ E4.0 man pouting: medium skin tone +1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ E4.0 man pouting: medium-dark skin tone +1F64E 1F3FE 200D 2642 ; minimally-qualified # 🙎🏾‍♂ E4.0 man pouting: medium-dark skin tone +1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ E4.0 man pouting: dark skin tone +1F64E 1F3FF 200D 2642 ; minimally-qualified # 🙎🏿‍♂ E4.0 man pouting: dark skin tone +1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ E4.0 woman pouting +1F64E 200D 2640 ; minimally-qualified # 🙎‍♀ E4.0 woman pouting +1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ E4.0 woman pouting: light skin tone +1F64E 1F3FB 200D 2640 ; minimally-qualified # 🙎🏻‍♀ E4.0 woman pouting: light skin tone +1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ E4.0 woman pouting: medium-light skin tone +1F64E 1F3FC 200D 2640 ; minimally-qualified # 🙎🏼‍♀ E4.0 woman pouting: medium-light skin tone +1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ E4.0 woman pouting: medium skin tone +1F64E 1F3FD 200D 2640 ; minimally-qualified # 🙎🏽‍♀ E4.0 woman pouting: medium skin tone +1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ E4.0 woman pouting: medium-dark skin tone +1F64E 1F3FE 200D 2640 ; minimally-qualified # 🙎🏾‍♀ E4.0 woman pouting: medium-dark skin tone +1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ E4.0 woman pouting: dark skin tone +1F64E 1F3FF 200D 2640 ; minimally-qualified # 🙎🏿‍♀ E4.0 woman pouting: dark skin tone +1F645 ; fully-qualified # 🙅 E0.6 person gesturing NO +1F645 1F3FB ; fully-qualified # 🙅🏻 E1.0 person gesturing NO: light skin tone +1F645 1F3FC ; fully-qualified # 🙅🏼 E1.0 person gesturing NO: medium-light skin tone +1F645 1F3FD ; fully-qualified # 🙅🏽 E1.0 person gesturing NO: medium skin tone +1F645 1F3FE ; fully-qualified # 🙅🏾 E1.0 person gesturing NO: medium-dark skin tone +1F645 1F3FF ; fully-qualified # 🙅🏿 E1.0 person gesturing NO: dark skin tone +1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ E4.0 man gesturing NO +1F645 200D 2642 ; minimally-qualified # 🙅‍♂ E4.0 man gesturing NO +1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ E4.0 man gesturing NO: light skin tone +1F645 1F3FB 200D 2642 ; minimally-qualified # 🙅🏻‍♂ E4.0 man gesturing NO: light skin tone +1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ E4.0 man gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2642 ; minimally-qualified # 🙅🏼‍♂ E4.0 man gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ E4.0 man gesturing NO: medium skin tone +1F645 1F3FD 200D 2642 ; minimally-qualified # 🙅🏽‍♂ E4.0 man gesturing NO: medium skin tone +1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ E4.0 man gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2642 ; minimally-qualified # 🙅🏾‍♂ E4.0 man gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ E4.0 man gesturing NO: dark skin tone +1F645 1F3FF 200D 2642 ; minimally-qualified # 🙅🏿‍♂ E4.0 man gesturing NO: dark skin tone +1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ E4.0 woman gesturing NO +1F645 200D 2640 ; minimally-qualified # 🙅‍♀ E4.0 woman gesturing NO +1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ E4.0 woman gesturing NO: light skin tone +1F645 1F3FB 200D 2640 ; minimally-qualified # 🙅🏻‍♀ E4.0 woman gesturing NO: light skin tone +1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ E4.0 woman gesturing NO: medium-light skin tone +1F645 1F3FC 200D 2640 ; minimally-qualified # 🙅🏼‍♀ E4.0 woman gesturing NO: medium-light skin tone +1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ E4.0 woman gesturing NO: medium skin tone +1F645 1F3FD 200D 2640 ; minimally-qualified # 🙅🏽‍♀ E4.0 woman gesturing NO: medium skin tone +1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ E4.0 woman gesturing NO: medium-dark skin tone +1F645 1F3FE 200D 2640 ; minimally-qualified # 🙅🏾‍♀ E4.0 woman gesturing NO: medium-dark skin tone +1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ E4.0 woman gesturing NO: dark skin tone +1F645 1F3FF 200D 2640 ; minimally-qualified # 🙅🏿‍♀ E4.0 woman gesturing NO: dark skin tone +1F646 ; fully-qualified # 🙆 E0.6 person gesturing OK +1F646 1F3FB ; fully-qualified # 🙆🏻 E1.0 person gesturing OK: light skin tone +1F646 1F3FC ; fully-qualified # 🙆🏼 E1.0 person gesturing OK: medium-light skin tone +1F646 1F3FD ; fully-qualified # 🙆🏽 E1.0 person gesturing OK: medium skin tone +1F646 1F3FE ; fully-qualified # 🙆🏾 E1.0 person gesturing OK: medium-dark skin tone +1F646 1F3FF ; fully-qualified # 🙆🏿 E1.0 person gesturing OK: dark skin tone +1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ E4.0 man gesturing OK +1F646 200D 2642 ; minimally-qualified # 🙆‍♂ E4.0 man gesturing OK +1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ E4.0 man gesturing OK: light skin tone +1F646 1F3FB 200D 2642 ; minimally-qualified # 🙆🏻‍♂ E4.0 man gesturing OK: light skin tone +1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ E4.0 man gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2642 ; minimally-qualified # 🙆🏼‍♂ E4.0 man gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ E4.0 man gesturing OK: medium skin tone +1F646 1F3FD 200D 2642 ; minimally-qualified # 🙆🏽‍♂ E4.0 man gesturing OK: medium skin tone +1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ E4.0 man gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2642 ; minimally-qualified # 🙆🏾‍♂ E4.0 man gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ E4.0 man gesturing OK: dark skin tone +1F646 1F3FF 200D 2642 ; minimally-qualified # 🙆🏿‍♂ E4.0 man gesturing OK: dark skin tone +1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ E4.0 woman gesturing OK +1F646 200D 2640 ; minimally-qualified # 🙆‍♀ E4.0 woman gesturing OK +1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ E4.0 woman gesturing OK: light skin tone +1F646 1F3FB 200D 2640 ; minimally-qualified # 🙆🏻‍♀ E4.0 woman gesturing OK: light skin tone +1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ E4.0 woman gesturing OK: medium-light skin tone +1F646 1F3FC 200D 2640 ; minimally-qualified # 🙆🏼‍♀ E4.0 woman gesturing OK: medium-light skin tone +1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ E4.0 woman gesturing OK: medium skin tone +1F646 1F3FD 200D 2640 ; minimally-qualified # 🙆🏽‍♀ E4.0 woman gesturing OK: medium skin tone +1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ E4.0 woman gesturing OK: medium-dark skin tone +1F646 1F3FE 200D 2640 ; minimally-qualified # 🙆🏾‍♀ E4.0 woman gesturing OK: medium-dark skin tone +1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ E4.0 woman gesturing OK: dark skin tone +1F646 1F3FF 200D 2640 ; minimally-qualified # 🙆🏿‍♀ E4.0 woman gesturing OK: dark skin tone +1F481 ; fully-qualified # 💁 E0.6 person tipping hand +1F481 1F3FB ; fully-qualified # 💁🏻 E1.0 person tipping hand: light skin tone +1F481 1F3FC ; fully-qualified # 💁🏼 E1.0 person tipping hand: medium-light skin tone +1F481 1F3FD ; fully-qualified # 💁🏽 E1.0 person tipping hand: medium skin tone +1F481 1F3FE ; fully-qualified # 💁🏾 E1.0 person tipping hand: medium-dark skin tone +1F481 1F3FF ; fully-qualified # 💁🏿 E1.0 person tipping hand: dark skin tone +1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ E4.0 man tipping hand +1F481 200D 2642 ; minimally-qualified # 💁‍♂ E4.0 man tipping hand +1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ E4.0 man tipping hand: light skin tone +1F481 1F3FB 200D 2642 ; minimally-qualified # 💁🏻‍♂ E4.0 man tipping hand: light skin tone +1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ E4.0 man tipping hand: medium-light skin tone +1F481 1F3FC 200D 2642 ; minimally-qualified # 💁🏼‍♂ E4.0 man tipping hand: medium-light skin tone +1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ E4.0 man tipping hand: medium skin tone +1F481 1F3FD 200D 2642 ; minimally-qualified # 💁🏽‍♂ E4.0 man tipping hand: medium skin tone +1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ E4.0 man tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2642 ; minimally-qualified # 💁🏾‍♂ E4.0 man tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ E4.0 man tipping hand: dark skin tone +1F481 1F3FF 200D 2642 ; minimally-qualified # 💁🏿‍♂ E4.0 man tipping hand: dark skin tone +1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ E4.0 woman tipping hand +1F481 200D 2640 ; minimally-qualified # 💁‍♀ E4.0 woman tipping hand +1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ E4.0 woman tipping hand: light skin tone +1F481 1F3FB 200D 2640 ; minimally-qualified # 💁🏻‍♀ E4.0 woman tipping hand: light skin tone +1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ E4.0 woman tipping hand: medium-light skin tone +1F481 1F3FC 200D 2640 ; minimally-qualified # 💁🏼‍♀ E4.0 woman tipping hand: medium-light skin tone +1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ E4.0 woman tipping hand: medium skin tone +1F481 1F3FD 200D 2640 ; minimally-qualified # 💁🏽‍♀ E4.0 woman tipping hand: medium skin tone +1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ E4.0 woman tipping hand: medium-dark skin tone +1F481 1F3FE 200D 2640 ; minimally-qualified # 💁🏾‍♀ E4.0 woman tipping hand: medium-dark skin tone +1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ E4.0 woman tipping hand: dark skin tone +1F481 1F3FF 200D 2640 ; minimally-qualified # 💁🏿‍♀ E4.0 woman tipping hand: dark skin tone +1F64B ; fully-qualified # 🙋 E0.6 person raising hand +1F64B 1F3FB ; fully-qualified # 🙋🏻 E1.0 person raising hand: light skin tone +1F64B 1F3FC ; fully-qualified # 🙋🏼 E1.0 person raising hand: medium-light skin tone +1F64B 1F3FD ; fully-qualified # 🙋🏽 E1.0 person raising hand: medium skin tone +1F64B 1F3FE ; fully-qualified # 🙋🏾 E1.0 person raising hand: medium-dark skin tone +1F64B 1F3FF ; fully-qualified # 🙋🏿 E1.0 person raising hand: dark skin tone +1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ E4.0 man raising hand +1F64B 200D 2642 ; minimally-qualified # 🙋‍♂ E4.0 man raising hand +1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ E4.0 man raising hand: light skin tone +1F64B 1F3FB 200D 2642 ; minimally-qualified # 🙋🏻‍♂ E4.0 man raising hand: light skin tone +1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ E4.0 man raising hand: medium-light skin tone +1F64B 1F3FC 200D 2642 ; minimally-qualified # 🙋🏼‍♂ E4.0 man raising hand: medium-light skin tone +1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ E4.0 man raising hand: medium skin tone +1F64B 1F3FD 200D 2642 ; minimally-qualified # 🙋🏽‍♂ E4.0 man raising hand: medium skin tone +1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ E4.0 man raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2642 ; minimally-qualified # 🙋🏾‍♂ E4.0 man raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ E4.0 man raising hand: dark skin tone +1F64B 1F3FF 200D 2642 ; minimally-qualified # 🙋🏿‍♂ E4.0 man raising hand: dark skin tone +1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ E4.0 woman raising hand +1F64B 200D 2640 ; minimally-qualified # 🙋‍♀ E4.0 woman raising hand +1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ E4.0 woman raising hand: light skin tone +1F64B 1F3FB 200D 2640 ; minimally-qualified # 🙋🏻‍♀ E4.0 woman raising hand: light skin tone +1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ E4.0 woman raising hand: medium-light skin tone +1F64B 1F3FC 200D 2640 ; minimally-qualified # 🙋🏼‍♀ E4.0 woman raising hand: medium-light skin tone +1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ E4.0 woman raising hand: medium skin tone +1F64B 1F3FD 200D 2640 ; minimally-qualified # 🙋🏽‍♀ E4.0 woman raising hand: medium skin tone +1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ E4.0 woman raising hand: medium-dark skin tone +1F64B 1F3FE 200D 2640 ; minimally-qualified # 🙋🏾‍♀ E4.0 woman raising hand: medium-dark skin tone +1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ E4.0 woman raising hand: dark skin tone +1F64B 1F3FF 200D 2640 ; minimally-qualified # 🙋🏿‍♀ E4.0 woman raising hand: dark skin tone +1F9CF ; fully-qualified # 🧏 E12.0 deaf person +1F9CF 1F3FB ; fully-qualified # 🧏🏻 E12.0 deaf person: light skin tone +1F9CF 1F3FC ; fully-qualified # 🧏🏼 E12.0 deaf person: medium-light skin tone +1F9CF 1F3FD ; fully-qualified # 🧏🏽 E12.0 deaf person: medium skin tone +1F9CF 1F3FE ; fully-qualified # 🧏🏾 E12.0 deaf person: medium-dark skin tone +1F9CF 1F3FF ; fully-qualified # 🧏🏿 E12.0 deaf person: dark skin tone +1F9CF 200D 2642 FE0F ; fully-qualified # 🧏‍♂️ E12.0 deaf man +1F9CF 200D 2642 ; minimally-qualified # 🧏‍♂ E12.0 deaf man +1F9CF 1F3FB 200D 2642 FE0F ; fully-qualified # 🧏🏻‍♂️ E12.0 deaf man: light skin tone +1F9CF 1F3FB 200D 2642 ; minimally-qualified # 🧏🏻‍♂ E12.0 deaf man: light skin tone +1F9CF 1F3FC 200D 2642 FE0F ; fully-qualified # 🧏🏼‍♂️ E12.0 deaf man: medium-light skin tone +1F9CF 1F3FC 200D 2642 ; minimally-qualified # 🧏🏼‍♂ E12.0 deaf man: medium-light skin tone +1F9CF 1F3FD 200D 2642 FE0F ; fully-qualified # 🧏🏽‍♂️ E12.0 deaf man: medium skin tone +1F9CF 1F3FD 200D 2642 ; minimally-qualified # 🧏🏽‍♂ E12.0 deaf man: medium skin tone +1F9CF 1F3FE 200D 2642 FE0F ; fully-qualified # 🧏🏾‍♂️ E12.0 deaf man: medium-dark skin tone +1F9CF 1F3FE 200D 2642 ; minimally-qualified # 🧏🏾‍♂ E12.0 deaf man: medium-dark skin tone +1F9CF 1F3FF 200D 2642 FE0F ; fully-qualified # 🧏🏿‍♂️ E12.0 deaf man: dark skin tone +1F9CF 1F3FF 200D 2642 ; minimally-qualified # 🧏🏿‍♂ E12.0 deaf man: dark skin tone +1F9CF 200D 2640 FE0F ; fully-qualified # 🧏‍♀️ E12.0 deaf woman +1F9CF 200D 2640 ; minimally-qualified # 🧏‍♀ E12.0 deaf woman +1F9CF 1F3FB 200D 2640 FE0F ; fully-qualified # 🧏🏻‍♀️ E12.0 deaf woman: light skin tone +1F9CF 1F3FB 200D 2640 ; minimally-qualified # 🧏🏻‍♀ E12.0 deaf woman: light skin tone +1F9CF 1F3FC 200D 2640 FE0F ; fully-qualified # 🧏🏼‍♀️ E12.0 deaf woman: medium-light skin tone +1F9CF 1F3FC 200D 2640 ; minimally-qualified # 🧏🏼‍♀ E12.0 deaf woman: medium-light skin tone +1F9CF 1F3FD 200D 2640 FE0F ; fully-qualified # 🧏🏽‍♀️ E12.0 deaf woman: medium skin tone +1F9CF 1F3FD 200D 2640 ; minimally-qualified # 🧏🏽‍♀ E12.0 deaf woman: medium skin tone +1F9CF 1F3FE 200D 2640 FE0F ; fully-qualified # 🧏🏾‍♀️ E12.0 deaf woman: medium-dark skin tone +1F9CF 1F3FE 200D 2640 ; minimally-qualified # 🧏🏾‍♀ E12.0 deaf woman: medium-dark skin tone +1F9CF 1F3FF 200D 2640 FE0F ; fully-qualified # 🧏🏿‍♀️ E12.0 deaf woman: dark skin tone +1F9CF 1F3FF 200D 2640 ; minimally-qualified # 🧏🏿‍♀ E12.0 deaf woman: dark skin tone +1F647 ; fully-qualified # 🙇 E0.6 person bowing +1F647 1F3FB ; fully-qualified # 🙇🏻 E1.0 person bowing: light skin tone +1F647 1F3FC ; fully-qualified # 🙇🏼 E1.0 person bowing: medium-light skin tone +1F647 1F3FD ; fully-qualified # 🙇🏽 E1.0 person bowing: medium skin tone +1F647 1F3FE ; fully-qualified # 🙇🏾 E1.0 person bowing: medium-dark skin tone +1F647 1F3FF ; fully-qualified # 🙇🏿 E1.0 person bowing: dark skin tone +1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ E4.0 man bowing +1F647 200D 2642 ; minimally-qualified # 🙇‍♂ E4.0 man bowing +1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ E4.0 man bowing: light skin tone +1F647 1F3FB 200D 2642 ; minimally-qualified # 🙇🏻‍♂ E4.0 man bowing: light skin tone +1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ E4.0 man bowing: medium-light skin tone +1F647 1F3FC 200D 2642 ; minimally-qualified # 🙇🏼‍♂ E4.0 man bowing: medium-light skin tone +1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ E4.0 man bowing: medium skin tone +1F647 1F3FD 200D 2642 ; minimally-qualified # 🙇🏽‍♂ E4.0 man bowing: medium skin tone +1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ E4.0 man bowing: medium-dark skin tone +1F647 1F3FE 200D 2642 ; minimally-qualified # 🙇🏾‍♂ E4.0 man bowing: medium-dark skin tone +1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ E4.0 man bowing: dark skin tone +1F647 1F3FF 200D 2642 ; minimally-qualified # 🙇🏿‍♂ E4.0 man bowing: dark skin tone +1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ E4.0 woman bowing +1F647 200D 2640 ; minimally-qualified # 🙇‍♀ E4.0 woman bowing +1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ E4.0 woman bowing: light skin tone +1F647 1F3FB 200D 2640 ; minimally-qualified # 🙇🏻‍♀ E4.0 woman bowing: light skin tone +1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ E4.0 woman bowing: medium-light skin tone +1F647 1F3FC 200D 2640 ; minimally-qualified # 🙇🏼‍♀ E4.0 woman bowing: medium-light skin tone +1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ E4.0 woman bowing: medium skin tone +1F647 1F3FD 200D 2640 ; minimally-qualified # 🙇🏽‍♀ E4.0 woman bowing: medium skin tone +1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ E4.0 woman bowing: medium-dark skin tone +1F647 1F3FE 200D 2640 ; minimally-qualified # 🙇🏾‍♀ E4.0 woman bowing: medium-dark skin tone +1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ E4.0 woman bowing: dark skin tone +1F647 1F3FF 200D 2640 ; minimally-qualified # 🙇🏿‍♀ E4.0 woman bowing: dark skin tone +1F926 ; fully-qualified # 🤦 E3.0 person facepalming +1F926 1F3FB ; fully-qualified # 🤦🏻 E3.0 person facepalming: light skin tone +1F926 1F3FC ; fully-qualified # 🤦🏼 E3.0 person facepalming: medium-light skin tone +1F926 1F3FD ; fully-qualified # 🤦🏽 E3.0 person facepalming: medium skin tone +1F926 1F3FE ; fully-qualified # 🤦🏾 E3.0 person facepalming: medium-dark skin tone +1F926 1F3FF ; fully-qualified # 🤦🏿 E3.0 person facepalming: dark skin tone +1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ E4.0 man facepalming +1F926 200D 2642 ; minimally-qualified # 🤦‍♂ E4.0 man facepalming +1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ E4.0 man facepalming: light skin tone +1F926 1F3FB 200D 2642 ; minimally-qualified # 🤦🏻‍♂ E4.0 man facepalming: light skin tone +1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ E4.0 man facepalming: medium-light skin tone +1F926 1F3FC 200D 2642 ; minimally-qualified # 🤦🏼‍♂ E4.0 man facepalming: medium-light skin tone +1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ E4.0 man facepalming: medium skin tone +1F926 1F3FD 200D 2642 ; minimally-qualified # 🤦🏽‍♂ E4.0 man facepalming: medium skin tone +1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ E4.0 man facepalming: medium-dark skin tone +1F926 1F3FE 200D 2642 ; minimally-qualified # 🤦🏾‍♂ E4.0 man facepalming: medium-dark skin tone +1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ E4.0 man facepalming: dark skin tone +1F926 1F3FF 200D 2642 ; minimally-qualified # 🤦🏿‍♂ E4.0 man facepalming: dark skin tone +1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ E4.0 woman facepalming +1F926 200D 2640 ; minimally-qualified # 🤦‍♀ E4.0 woman facepalming +1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ E4.0 woman facepalming: light skin tone +1F926 1F3FB 200D 2640 ; minimally-qualified # 🤦🏻‍♀ E4.0 woman facepalming: light skin tone +1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ E4.0 woman facepalming: medium-light skin tone +1F926 1F3FC 200D 2640 ; minimally-qualified # 🤦🏼‍♀ E4.0 woman facepalming: medium-light skin tone +1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ E4.0 woman facepalming: medium skin tone +1F926 1F3FD 200D 2640 ; minimally-qualified # 🤦🏽‍♀ E4.0 woman facepalming: medium skin tone +1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ E4.0 woman facepalming: medium-dark skin tone +1F926 1F3FE 200D 2640 ; minimally-qualified # 🤦🏾‍♀ E4.0 woman facepalming: medium-dark skin tone +1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ E4.0 woman facepalming: dark skin tone +1F926 1F3FF 200D 2640 ; minimally-qualified # 🤦🏿‍♀ E4.0 woman facepalming: dark skin tone +1F937 ; fully-qualified # 🤷 E3.0 person shrugging +1F937 1F3FB ; fully-qualified # 🤷🏻 E3.0 person shrugging: light skin tone +1F937 1F3FC ; fully-qualified # 🤷🏼 E3.0 person shrugging: medium-light skin tone +1F937 1F3FD ; fully-qualified # 🤷🏽 E3.0 person shrugging: medium skin tone +1F937 1F3FE ; fully-qualified # 🤷🏾 E3.0 person shrugging: medium-dark skin tone +1F937 1F3FF ; fully-qualified # 🤷🏿 E3.0 person shrugging: dark skin tone +1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ E4.0 man shrugging +1F937 200D 2642 ; minimally-qualified # 🤷‍♂ E4.0 man shrugging +1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ E4.0 man shrugging: light skin tone +1F937 1F3FB 200D 2642 ; minimally-qualified # 🤷🏻‍♂ E4.0 man shrugging: light skin tone +1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ E4.0 man shrugging: medium-light skin tone +1F937 1F3FC 200D 2642 ; minimally-qualified # 🤷🏼‍♂ E4.0 man shrugging: medium-light skin tone +1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ E4.0 man shrugging: medium skin tone +1F937 1F3FD 200D 2642 ; minimally-qualified # 🤷🏽‍♂ E4.0 man shrugging: medium skin tone +1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ E4.0 man shrugging: medium-dark skin tone +1F937 1F3FE 200D 2642 ; minimally-qualified # 🤷🏾‍♂ E4.0 man shrugging: medium-dark skin tone +1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ E4.0 man shrugging: dark skin tone +1F937 1F3FF 200D 2642 ; minimally-qualified # 🤷🏿‍♂ E4.0 man shrugging: dark skin tone +1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ E4.0 woman shrugging +1F937 200D 2640 ; minimally-qualified # 🤷‍♀ E4.0 woman shrugging +1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ E4.0 woman shrugging: light skin tone +1F937 1F3FB 200D 2640 ; minimally-qualified # 🤷🏻‍♀ E4.0 woman shrugging: light skin tone +1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ E4.0 woman shrugging: medium-light skin tone +1F937 1F3FC 200D 2640 ; minimally-qualified # 🤷🏼‍♀ E4.0 woman shrugging: medium-light skin tone +1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ E4.0 woman shrugging: medium skin tone +1F937 1F3FD 200D 2640 ; minimally-qualified # 🤷🏽‍♀ E4.0 woman shrugging: medium skin tone +1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ E4.0 woman shrugging: medium-dark skin tone +1F937 1F3FE 200D 2640 ; minimally-qualified # 🤷🏾‍♀ E4.0 woman shrugging: medium-dark skin tone +1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ E4.0 woman shrugging: dark skin tone +1F937 1F3FF 200D 2640 ; minimally-qualified # 🤷🏿‍♀ E4.0 woman shrugging: dark skin tone + +# subgroup: person-role +1F9D1 200D 2695 FE0F ; fully-qualified # 🧑‍⚕️ E12.1 health worker +1F9D1 200D 2695 ; minimally-qualified # 🧑‍⚕ E12.1 health worker +1F9D1 1F3FB 200D 2695 FE0F ; fully-qualified # 🧑🏻‍⚕️ E12.1 health worker: light skin tone +1F9D1 1F3FB 200D 2695 ; minimally-qualified # 🧑🏻‍⚕ E12.1 health worker: light skin tone +1F9D1 1F3FC 200D 2695 FE0F ; fully-qualified # 🧑🏼‍⚕️ E12.1 health worker: medium-light skin tone +1F9D1 1F3FC 200D 2695 ; minimally-qualified # 🧑🏼‍⚕ E12.1 health worker: medium-light skin tone +1F9D1 1F3FD 200D 2695 FE0F ; fully-qualified # 🧑🏽‍⚕️ E12.1 health worker: medium skin tone +1F9D1 1F3FD 200D 2695 ; minimally-qualified # 🧑🏽‍⚕ E12.1 health worker: medium skin tone +1F9D1 1F3FE 200D 2695 FE0F ; fully-qualified # 🧑🏾‍⚕️ E12.1 health worker: medium-dark skin tone +1F9D1 1F3FE 200D 2695 ; minimally-qualified # 🧑🏾‍⚕ E12.1 health worker: medium-dark skin tone +1F9D1 1F3FF 200D 2695 FE0F ; fully-qualified # 🧑🏿‍⚕️ E12.1 health worker: dark skin tone +1F9D1 1F3FF 200D 2695 ; minimally-qualified # 🧑🏿‍⚕ E12.1 health worker: dark skin tone +1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ E4.0 man health worker +1F468 200D 2695 ; minimally-qualified # 👨‍⚕ E4.0 man health worker +1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ E4.0 man health worker: light skin tone +1F468 1F3FB 200D 2695 ; minimally-qualified # 👨🏻‍⚕ E4.0 man health worker: light skin tone +1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ E4.0 man health worker: medium-light skin tone +1F468 1F3FC 200D 2695 ; minimally-qualified # 👨🏼‍⚕ E4.0 man health worker: medium-light skin tone +1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ E4.0 man health worker: medium skin tone +1F468 1F3FD 200D 2695 ; minimally-qualified # 👨🏽‍⚕ E4.0 man health worker: medium skin tone +1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ E4.0 man health worker: medium-dark skin tone +1F468 1F3FE 200D 2695 ; minimally-qualified # 👨🏾‍⚕ E4.0 man health worker: medium-dark skin tone +1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ E4.0 man health worker: dark skin tone +1F468 1F3FF 200D 2695 ; minimally-qualified # 👨🏿‍⚕ E4.0 man health worker: dark skin tone +1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ E4.0 woman health worker +1F469 200D 2695 ; minimally-qualified # 👩‍⚕ E4.0 woman health worker +1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ E4.0 woman health worker: light skin tone +1F469 1F3FB 200D 2695 ; minimally-qualified # 👩🏻‍⚕ E4.0 woman health worker: light skin tone +1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ E4.0 woman health worker: medium-light skin tone +1F469 1F3FC 200D 2695 ; minimally-qualified # 👩🏼‍⚕ E4.0 woman health worker: medium-light skin tone +1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ E4.0 woman health worker: medium skin tone +1F469 1F3FD 200D 2695 ; minimally-qualified # 👩🏽‍⚕ E4.0 woman health worker: medium skin tone +1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ E4.0 woman health worker: medium-dark skin tone +1F469 1F3FE 200D 2695 ; minimally-qualified # 👩🏾‍⚕ E4.0 woman health worker: medium-dark skin tone +1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ E4.0 woman health worker: dark skin tone +1F469 1F3FF 200D 2695 ; minimally-qualified # 👩🏿‍⚕ E4.0 woman health worker: dark skin tone +1F9D1 200D 1F393 ; fully-qualified # 🧑‍🎓 E12.1 student +1F9D1 1F3FB 200D 1F393 ; fully-qualified # 🧑🏻‍🎓 E12.1 student: light skin tone +1F9D1 1F3FC 200D 1F393 ; fully-qualified # 🧑🏼‍🎓 E12.1 student: medium-light skin tone +1F9D1 1F3FD 200D 1F393 ; fully-qualified # 🧑🏽‍🎓 E12.1 student: medium skin tone +1F9D1 1F3FE 200D 1F393 ; fully-qualified # 🧑🏾‍🎓 E12.1 student: medium-dark skin tone +1F9D1 1F3FF 200D 1F393 ; fully-qualified # 🧑🏿‍🎓 E12.1 student: dark skin tone +1F468 200D 1F393 ; fully-qualified # 👨‍🎓 E4.0 man student +1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 E4.0 man student: light skin tone +1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 E4.0 man student: medium-light skin tone +1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 E4.0 man student: medium skin tone +1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 E4.0 man student: medium-dark skin tone +1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 E4.0 man student: dark skin tone +1F469 200D 1F393 ; fully-qualified # 👩‍🎓 E4.0 woman student +1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 E4.0 woman student: light skin tone +1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 E4.0 woman student: medium-light skin tone +1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 E4.0 woman student: medium skin tone +1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 E4.0 woman student: medium-dark skin tone +1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 E4.0 woman student: dark skin tone +1F9D1 200D 1F3EB ; fully-qualified # 🧑‍🏫 E12.1 teacher +1F9D1 1F3FB 200D 1F3EB ; fully-qualified # 🧑🏻‍🏫 E12.1 teacher: light skin tone +1F9D1 1F3FC 200D 1F3EB ; fully-qualified # 🧑🏼‍🏫 E12.1 teacher: medium-light skin tone +1F9D1 1F3FD 200D 1F3EB ; fully-qualified # 🧑🏽‍🏫 E12.1 teacher: medium skin tone +1F9D1 1F3FE 200D 1F3EB ; fully-qualified # 🧑🏾‍🏫 E12.1 teacher: medium-dark skin tone +1F9D1 1F3FF 200D 1F3EB ; fully-qualified # 🧑🏿‍🏫 E12.1 teacher: dark skin tone +1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 E4.0 man teacher +1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 E4.0 man teacher: light skin tone +1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 E4.0 man teacher: medium-light skin tone +1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 E4.0 man teacher: medium skin tone +1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 E4.0 man teacher: medium-dark skin tone +1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 E4.0 man teacher: dark skin tone +1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 E4.0 woman teacher +1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 E4.0 woman teacher: light skin tone +1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 E4.0 woman teacher: medium-light skin tone +1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 E4.0 woman teacher: medium skin tone +1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 E4.0 woman teacher: medium-dark skin tone +1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 E4.0 woman teacher: dark skin tone +1F9D1 200D 2696 FE0F ; fully-qualified # 🧑‍⚖️ E12.1 judge +1F9D1 200D 2696 ; minimally-qualified # 🧑‍⚖ E12.1 judge +1F9D1 1F3FB 200D 2696 FE0F ; fully-qualified # 🧑🏻‍⚖️ E12.1 judge: light skin tone +1F9D1 1F3FB 200D 2696 ; minimally-qualified # 🧑🏻‍⚖ E12.1 judge: light skin tone +1F9D1 1F3FC 200D 2696 FE0F ; fully-qualified # 🧑🏼‍⚖️ E12.1 judge: medium-light skin tone +1F9D1 1F3FC 200D 2696 ; minimally-qualified # 🧑🏼‍⚖ E12.1 judge: medium-light skin tone +1F9D1 1F3FD 200D 2696 FE0F ; fully-qualified # 🧑🏽‍⚖️ E12.1 judge: medium skin tone +1F9D1 1F3FD 200D 2696 ; minimally-qualified # 🧑🏽‍⚖ E12.1 judge: medium skin tone +1F9D1 1F3FE 200D 2696 FE0F ; fully-qualified # 🧑🏾‍⚖️ E12.1 judge: medium-dark skin tone +1F9D1 1F3FE 200D 2696 ; minimally-qualified # 🧑🏾‍⚖ E12.1 judge: medium-dark skin tone +1F9D1 1F3FF 200D 2696 FE0F ; fully-qualified # 🧑🏿‍⚖️ E12.1 judge: dark skin tone +1F9D1 1F3FF 200D 2696 ; minimally-qualified # 🧑🏿‍⚖ E12.1 judge: dark skin tone +1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ E4.0 man judge +1F468 200D 2696 ; minimally-qualified # 👨‍⚖ E4.0 man judge +1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ E4.0 man judge: light skin tone +1F468 1F3FB 200D 2696 ; minimally-qualified # 👨🏻‍⚖ E4.0 man judge: light skin tone +1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ E4.0 man judge: medium-light skin tone +1F468 1F3FC 200D 2696 ; minimally-qualified # 👨🏼‍⚖ E4.0 man judge: medium-light skin tone +1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ E4.0 man judge: medium skin tone +1F468 1F3FD 200D 2696 ; minimally-qualified # 👨🏽‍⚖ E4.0 man judge: medium skin tone +1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ E4.0 man judge: medium-dark skin tone +1F468 1F3FE 200D 2696 ; minimally-qualified # 👨🏾‍⚖ E4.0 man judge: medium-dark skin tone +1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ E4.0 man judge: dark skin tone +1F468 1F3FF 200D 2696 ; minimally-qualified # 👨🏿‍⚖ E4.0 man judge: dark skin tone +1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ E4.0 woman judge +1F469 200D 2696 ; minimally-qualified # 👩‍⚖ E4.0 woman judge +1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ E4.0 woman judge: light skin tone +1F469 1F3FB 200D 2696 ; minimally-qualified # 👩🏻‍⚖ E4.0 woman judge: light skin tone +1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ E4.0 woman judge: medium-light skin tone +1F469 1F3FC 200D 2696 ; minimally-qualified # 👩🏼‍⚖ E4.0 woman judge: medium-light skin tone +1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ E4.0 woman judge: medium skin tone +1F469 1F3FD 200D 2696 ; minimally-qualified # 👩🏽‍⚖ E4.0 woman judge: medium skin tone +1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ E4.0 woman judge: medium-dark skin tone +1F469 1F3FE 200D 2696 ; minimally-qualified # 👩🏾‍⚖ E4.0 woman judge: medium-dark skin tone +1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ E4.0 woman judge: dark skin tone +1F469 1F3FF 200D 2696 ; minimally-qualified # 👩🏿‍⚖ E4.0 woman judge: dark skin tone +1F9D1 200D 1F33E ; fully-qualified # 🧑‍🌾 E12.1 farmer +1F9D1 1F3FB 200D 1F33E ; fully-qualified # 🧑🏻‍🌾 E12.1 farmer: light skin tone +1F9D1 1F3FC 200D 1F33E ; fully-qualified # 🧑🏼‍🌾 E12.1 farmer: medium-light skin tone +1F9D1 1F3FD 200D 1F33E ; fully-qualified # 🧑🏽‍🌾 E12.1 farmer: medium skin tone +1F9D1 1F3FE 200D 1F33E ; fully-qualified # 🧑🏾‍🌾 E12.1 farmer: medium-dark skin tone +1F9D1 1F3FF 200D 1F33E ; fully-qualified # 🧑🏿‍🌾 E12.1 farmer: dark skin tone +1F468 200D 1F33E ; fully-qualified # 👨‍🌾 E4.0 man farmer +1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 E4.0 man farmer: light skin tone +1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 E4.0 man farmer: medium-light skin tone +1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 E4.0 man farmer: medium skin tone +1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 E4.0 man farmer: medium-dark skin tone +1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 E4.0 man farmer: dark skin tone +1F469 200D 1F33E ; fully-qualified # 👩‍🌾 E4.0 woman farmer +1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 E4.0 woman farmer: light skin tone +1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 E4.0 woman farmer: medium-light skin tone +1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 E4.0 woman farmer: medium skin tone +1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 E4.0 woman farmer: medium-dark skin tone +1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 E4.0 woman farmer: dark skin tone +1F9D1 200D 1F373 ; fully-qualified # 🧑‍🍳 E12.1 cook +1F9D1 1F3FB 200D 1F373 ; fully-qualified # 🧑🏻‍🍳 E12.1 cook: light skin tone +1F9D1 1F3FC 200D 1F373 ; fully-qualified # 🧑🏼‍🍳 E12.1 cook: medium-light skin tone +1F9D1 1F3FD 200D 1F373 ; fully-qualified # 🧑🏽‍🍳 E12.1 cook: medium skin tone +1F9D1 1F3FE 200D 1F373 ; fully-qualified # 🧑🏾‍🍳 E12.1 cook: medium-dark skin tone +1F9D1 1F3FF 200D 1F373 ; fully-qualified # 🧑🏿‍🍳 E12.1 cook: dark skin tone +1F468 200D 1F373 ; fully-qualified # 👨‍🍳 E4.0 man cook +1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 E4.0 man cook: light skin tone +1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 E4.0 man cook: medium-light skin tone +1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 E4.0 man cook: medium skin tone +1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 E4.0 man cook: medium-dark skin tone +1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 E4.0 man cook: dark skin tone +1F469 200D 1F373 ; fully-qualified # 👩‍🍳 E4.0 woman cook +1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 E4.0 woman cook: light skin tone +1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 E4.0 woman cook: medium-light skin tone +1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 E4.0 woman cook: medium skin tone +1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 E4.0 woman cook: medium-dark skin tone +1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 E4.0 woman cook: dark skin tone +1F9D1 200D 1F527 ; fully-qualified # 🧑‍🔧 E12.1 mechanic +1F9D1 1F3FB 200D 1F527 ; fully-qualified # 🧑🏻‍🔧 E12.1 mechanic: light skin tone +1F9D1 1F3FC 200D 1F527 ; fully-qualified # 🧑🏼‍🔧 E12.1 mechanic: medium-light skin tone +1F9D1 1F3FD 200D 1F527 ; fully-qualified # 🧑🏽‍🔧 E12.1 mechanic: medium skin tone +1F9D1 1F3FE 200D 1F527 ; fully-qualified # 🧑🏾‍🔧 E12.1 mechanic: medium-dark skin tone +1F9D1 1F3FF 200D 1F527 ; fully-qualified # 🧑🏿‍🔧 E12.1 mechanic: dark skin tone +1F468 200D 1F527 ; fully-qualified # 👨‍🔧 E4.0 man mechanic +1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 E4.0 man mechanic: light skin tone +1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 E4.0 man mechanic: medium-light skin tone +1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 E4.0 man mechanic: medium skin tone +1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 E4.0 man mechanic: medium-dark skin tone +1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 E4.0 man mechanic: dark skin tone +1F469 200D 1F527 ; fully-qualified # 👩‍🔧 E4.0 woman mechanic +1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 E4.0 woman mechanic: light skin tone +1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 E4.0 woman mechanic: medium-light skin tone +1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 E4.0 woman mechanic: medium skin tone +1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 E4.0 woman mechanic: medium-dark skin tone +1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 E4.0 woman mechanic: dark skin tone +1F9D1 200D 1F3ED ; fully-qualified # 🧑‍🏭 E12.1 factory worker +1F9D1 1F3FB 200D 1F3ED ; fully-qualified # 🧑🏻‍🏭 E12.1 factory worker: light skin tone +1F9D1 1F3FC 200D 1F3ED ; fully-qualified # 🧑🏼‍🏭 E12.1 factory worker: medium-light skin tone +1F9D1 1F3FD 200D 1F3ED ; fully-qualified # 🧑🏽‍🏭 E12.1 factory worker: medium skin tone +1F9D1 1F3FE 200D 1F3ED ; fully-qualified # 🧑🏾‍🏭 E12.1 factory worker: medium-dark skin tone +1F9D1 1F3FF 200D 1F3ED ; fully-qualified # 🧑🏿‍🏭 E12.1 factory worker: dark skin tone +1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 E4.0 man factory worker +1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 E4.0 man factory worker: light skin tone +1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 E4.0 man factory worker: medium-light skin tone +1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 E4.0 man factory worker: medium skin tone +1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 E4.0 man factory worker: medium-dark skin tone +1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 E4.0 man factory worker: dark skin tone +1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 E4.0 woman factory worker +1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 E4.0 woman factory worker: light skin tone +1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 E4.0 woman factory worker: medium-light skin tone +1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 E4.0 woman factory worker: medium skin tone +1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 E4.0 woman factory worker: medium-dark skin tone +1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 E4.0 woman factory worker: dark skin tone +1F9D1 200D 1F4BC ; fully-qualified # 🧑‍💼 E12.1 office worker +1F9D1 1F3FB 200D 1F4BC ; fully-qualified # 🧑🏻‍💼 E12.1 office worker: light skin tone +1F9D1 1F3FC 200D 1F4BC ; fully-qualified # 🧑🏼‍💼 E12.1 office worker: medium-light skin tone +1F9D1 1F3FD 200D 1F4BC ; fully-qualified # 🧑🏽‍💼 E12.1 office worker: medium skin tone +1F9D1 1F3FE 200D 1F4BC ; fully-qualified # 🧑🏾‍💼 E12.1 office worker: medium-dark skin tone +1F9D1 1F3FF 200D 1F4BC ; fully-qualified # 🧑🏿‍💼 E12.1 office worker: dark skin tone +1F468 200D 1F4BC ; fully-qualified # 👨‍💼 E4.0 man office worker +1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 E4.0 man office worker: light skin tone +1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 E4.0 man office worker: medium-light skin tone +1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 E4.0 man office worker: medium skin tone +1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 E4.0 man office worker: medium-dark skin tone +1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 E4.0 man office worker: dark skin tone +1F469 200D 1F4BC ; fully-qualified # 👩‍💼 E4.0 woman office worker +1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 E4.0 woman office worker: light skin tone +1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 E4.0 woman office worker: medium-light skin tone +1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 E4.0 woman office worker: medium skin tone +1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 E4.0 woman office worker: medium-dark skin tone +1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 E4.0 woman office worker: dark skin tone +1F9D1 200D 1F52C ; fully-qualified # 🧑‍🔬 E12.1 scientist +1F9D1 1F3FB 200D 1F52C ; fully-qualified # 🧑🏻‍🔬 E12.1 scientist: light skin tone +1F9D1 1F3FC 200D 1F52C ; fully-qualified # 🧑🏼‍🔬 E12.1 scientist: medium-light skin tone +1F9D1 1F3FD 200D 1F52C ; fully-qualified # 🧑🏽‍🔬 E12.1 scientist: medium skin tone +1F9D1 1F3FE 200D 1F52C ; fully-qualified # 🧑🏾‍🔬 E12.1 scientist: medium-dark skin tone +1F9D1 1F3FF 200D 1F52C ; fully-qualified # 🧑🏿‍🔬 E12.1 scientist: dark skin tone +1F468 200D 1F52C ; fully-qualified # 👨‍🔬 E4.0 man scientist +1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 E4.0 man scientist: light skin tone +1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 E4.0 man scientist: medium-light skin tone +1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 E4.0 man scientist: medium skin tone +1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 E4.0 man scientist: medium-dark skin tone +1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 E4.0 man scientist: dark skin tone +1F469 200D 1F52C ; fully-qualified # 👩‍🔬 E4.0 woman scientist +1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 E4.0 woman scientist: light skin tone +1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 E4.0 woman scientist: medium-light skin tone +1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 E4.0 woman scientist: medium skin tone +1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 E4.0 woman scientist: medium-dark skin tone +1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 E4.0 woman scientist: dark skin tone +1F9D1 200D 1F4BB ; fully-qualified # 🧑‍💻 E12.1 technologist +1F9D1 1F3FB 200D 1F4BB ; fully-qualified # 🧑🏻‍💻 E12.1 technologist: light skin tone +1F9D1 1F3FC 200D 1F4BB ; fully-qualified # 🧑🏼‍💻 E12.1 technologist: medium-light skin tone +1F9D1 1F3FD 200D 1F4BB ; fully-qualified # 🧑🏽‍💻 E12.1 technologist: medium skin tone +1F9D1 1F3FE 200D 1F4BB ; fully-qualified # 🧑🏾‍💻 E12.1 technologist: medium-dark skin tone +1F9D1 1F3FF 200D 1F4BB ; fully-qualified # 🧑🏿‍💻 E12.1 technologist: dark skin tone +1F468 200D 1F4BB ; fully-qualified # 👨‍💻 E4.0 man technologist +1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 E4.0 man technologist: light skin tone +1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 E4.0 man technologist: medium-light skin tone +1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 E4.0 man technologist: medium skin tone +1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 E4.0 man technologist: medium-dark skin tone +1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 E4.0 man technologist: dark skin tone +1F469 200D 1F4BB ; fully-qualified # 👩‍💻 E4.0 woman technologist +1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 E4.0 woman technologist: light skin tone +1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 E4.0 woman technologist: medium-light skin tone +1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 E4.0 woman technologist: medium skin tone +1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 E4.0 woman technologist: medium-dark skin tone +1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 E4.0 woman technologist: dark skin tone +1F9D1 200D 1F3A4 ; fully-qualified # 🧑‍🎤 E12.1 singer +1F9D1 1F3FB 200D 1F3A4 ; fully-qualified # 🧑🏻‍🎤 E12.1 singer: light skin tone +1F9D1 1F3FC 200D 1F3A4 ; fully-qualified # 🧑🏼‍🎤 E12.1 singer: medium-light skin tone +1F9D1 1F3FD 200D 1F3A4 ; fully-qualified # 🧑🏽‍🎤 E12.1 singer: medium skin tone +1F9D1 1F3FE 200D 1F3A4 ; fully-qualified # 🧑🏾‍🎤 E12.1 singer: medium-dark skin tone +1F9D1 1F3FF 200D 1F3A4 ; fully-qualified # 🧑🏿‍🎤 E12.1 singer: dark skin tone +1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 E4.0 man singer +1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 E4.0 man singer: light skin tone +1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 E4.0 man singer: medium-light skin tone +1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 E4.0 man singer: medium skin tone +1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 E4.0 man singer: medium-dark skin tone +1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 E4.0 man singer: dark skin tone +1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 E4.0 woman singer +1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 E4.0 woman singer: light skin tone +1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 E4.0 woman singer: medium-light skin tone +1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 E4.0 woman singer: medium skin tone +1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 E4.0 woman singer: medium-dark skin tone +1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 E4.0 woman singer: dark skin tone +1F9D1 200D 1F3A8 ; fully-qualified # 🧑‍🎨 E12.1 artist +1F9D1 1F3FB 200D 1F3A8 ; fully-qualified # 🧑🏻‍🎨 E12.1 artist: light skin tone +1F9D1 1F3FC 200D 1F3A8 ; fully-qualified # 🧑🏼‍🎨 E12.1 artist: medium-light skin tone +1F9D1 1F3FD 200D 1F3A8 ; fully-qualified # 🧑🏽‍🎨 E12.1 artist: medium skin tone +1F9D1 1F3FE 200D 1F3A8 ; fully-qualified # 🧑🏾‍🎨 E12.1 artist: medium-dark skin tone +1F9D1 1F3FF 200D 1F3A8 ; fully-qualified # 🧑🏿‍🎨 E12.1 artist: dark skin tone +1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 E4.0 man artist +1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 E4.0 man artist: light skin tone +1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 E4.0 man artist: medium-light skin tone +1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 E4.0 man artist: medium skin tone +1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 E4.0 man artist: medium-dark skin tone +1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 E4.0 man artist: dark skin tone +1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 E4.0 woman artist +1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 E4.0 woman artist: light skin tone +1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 E4.0 woman artist: medium-light skin tone +1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 E4.0 woman artist: medium skin tone +1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 E4.0 woman artist: medium-dark skin tone +1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 E4.0 woman artist: dark skin tone +1F9D1 200D 2708 FE0F ; fully-qualified # 🧑‍✈️ E12.1 pilot +1F9D1 200D 2708 ; minimally-qualified # 🧑‍✈ E12.1 pilot +1F9D1 1F3FB 200D 2708 FE0F ; fully-qualified # 🧑🏻‍✈️ E12.1 pilot: light skin tone +1F9D1 1F3FB 200D 2708 ; minimally-qualified # 🧑🏻‍✈ E12.1 pilot: light skin tone +1F9D1 1F3FC 200D 2708 FE0F ; fully-qualified # 🧑🏼‍✈️ E12.1 pilot: medium-light skin tone +1F9D1 1F3FC 200D 2708 ; minimally-qualified # 🧑🏼‍✈ E12.1 pilot: medium-light skin tone +1F9D1 1F3FD 200D 2708 FE0F ; fully-qualified # 🧑🏽‍✈️ E12.1 pilot: medium skin tone +1F9D1 1F3FD 200D 2708 ; minimally-qualified # 🧑🏽‍✈ E12.1 pilot: medium skin tone +1F9D1 1F3FE 200D 2708 FE0F ; fully-qualified # 🧑🏾‍✈️ E12.1 pilot: medium-dark skin tone +1F9D1 1F3FE 200D 2708 ; minimally-qualified # 🧑🏾‍✈ E12.1 pilot: medium-dark skin tone +1F9D1 1F3FF 200D 2708 FE0F ; fully-qualified # 🧑🏿‍✈️ E12.1 pilot: dark skin tone +1F9D1 1F3FF 200D 2708 ; minimally-qualified # 🧑🏿‍✈ E12.1 pilot: dark skin tone +1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ E4.0 man pilot +1F468 200D 2708 ; minimally-qualified # 👨‍✈ E4.0 man pilot +1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ E4.0 man pilot: light skin tone +1F468 1F3FB 200D 2708 ; minimally-qualified # 👨🏻‍✈ E4.0 man pilot: light skin tone +1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ E4.0 man pilot: medium-light skin tone +1F468 1F3FC 200D 2708 ; minimally-qualified # 👨🏼‍✈ E4.0 man pilot: medium-light skin tone +1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ E4.0 man pilot: medium skin tone +1F468 1F3FD 200D 2708 ; minimally-qualified # 👨🏽‍✈ E4.0 man pilot: medium skin tone +1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ E4.0 man pilot: medium-dark skin tone +1F468 1F3FE 200D 2708 ; minimally-qualified # 👨🏾‍✈ E4.0 man pilot: medium-dark skin tone +1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ E4.0 man pilot: dark skin tone +1F468 1F3FF 200D 2708 ; minimally-qualified # 👨🏿‍✈ E4.0 man pilot: dark skin tone +1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ E4.0 woman pilot +1F469 200D 2708 ; minimally-qualified # 👩‍✈ E4.0 woman pilot +1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ E4.0 woman pilot: light skin tone +1F469 1F3FB 200D 2708 ; minimally-qualified # 👩🏻‍✈ E4.0 woman pilot: light skin tone +1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ E4.0 woman pilot: medium-light skin tone +1F469 1F3FC 200D 2708 ; minimally-qualified # 👩🏼‍✈ E4.0 woman pilot: medium-light skin tone +1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ E4.0 woman pilot: medium skin tone +1F469 1F3FD 200D 2708 ; minimally-qualified # 👩🏽‍✈ E4.0 woman pilot: medium skin tone +1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ E4.0 woman pilot: medium-dark skin tone +1F469 1F3FE 200D 2708 ; minimally-qualified # 👩🏾‍✈ E4.0 woman pilot: medium-dark skin tone +1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ E4.0 woman pilot: dark skin tone +1F469 1F3FF 200D 2708 ; minimally-qualified # 👩🏿‍✈ E4.0 woman pilot: dark skin tone +1F9D1 200D 1F680 ; fully-qualified # 🧑‍🚀 E12.1 astronaut +1F9D1 1F3FB 200D 1F680 ; fully-qualified # 🧑🏻‍🚀 E12.1 astronaut: light skin tone +1F9D1 1F3FC 200D 1F680 ; fully-qualified # 🧑🏼‍🚀 E12.1 astronaut: medium-light skin tone +1F9D1 1F3FD 200D 1F680 ; fully-qualified # 🧑🏽‍🚀 E12.1 astronaut: medium skin tone +1F9D1 1F3FE 200D 1F680 ; fully-qualified # 🧑🏾‍🚀 E12.1 astronaut: medium-dark skin tone +1F9D1 1F3FF 200D 1F680 ; fully-qualified # 🧑🏿‍🚀 E12.1 astronaut: dark skin tone +1F468 200D 1F680 ; fully-qualified # 👨‍🚀 E4.0 man astronaut +1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 E4.0 man astronaut: light skin tone +1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 E4.0 man astronaut: medium-light skin tone +1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 E4.0 man astronaut: medium skin tone +1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 E4.0 man astronaut: medium-dark skin tone +1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 E4.0 man astronaut: dark skin tone +1F469 200D 1F680 ; fully-qualified # 👩‍🚀 E4.0 woman astronaut +1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 E4.0 woman astronaut: light skin tone +1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 E4.0 woman astronaut: medium-light skin tone +1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 E4.0 woman astronaut: medium skin tone +1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 E4.0 woman astronaut: medium-dark skin tone +1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 E4.0 woman astronaut: dark skin tone +1F9D1 200D 1F692 ; fully-qualified # 🧑‍🚒 E12.1 firefighter +1F9D1 1F3FB 200D 1F692 ; fully-qualified # 🧑🏻‍🚒 E12.1 firefighter: light skin tone +1F9D1 1F3FC 200D 1F692 ; fully-qualified # 🧑🏼‍🚒 E12.1 firefighter: medium-light skin tone +1F9D1 1F3FD 200D 1F692 ; fully-qualified # 🧑🏽‍🚒 E12.1 firefighter: medium skin tone +1F9D1 1F3FE 200D 1F692 ; fully-qualified # 🧑🏾‍🚒 E12.1 firefighter: medium-dark skin tone +1F9D1 1F3FF 200D 1F692 ; fully-qualified # 🧑🏿‍🚒 E12.1 firefighter: dark skin tone +1F468 200D 1F692 ; fully-qualified # 👨‍🚒 E4.0 man firefighter +1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 E4.0 man firefighter: light skin tone +1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 E4.0 man firefighter: medium-light skin tone +1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 E4.0 man firefighter: medium skin tone +1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 E4.0 man firefighter: medium-dark skin tone +1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 E4.0 man firefighter: dark skin tone +1F469 200D 1F692 ; fully-qualified # 👩‍🚒 E4.0 woman firefighter +1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 E4.0 woman firefighter: light skin tone +1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 E4.0 woman firefighter: medium-light skin tone +1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 E4.0 woman firefighter: medium skin tone +1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 E4.0 woman firefighter: medium-dark skin tone +1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 E4.0 woman firefighter: dark skin tone +1F46E ; fully-qualified # 👮 E0.6 police officer +1F46E 1F3FB ; fully-qualified # 👮🏻 E1.0 police officer: light skin tone +1F46E 1F3FC ; fully-qualified # 👮🏼 E1.0 police officer: medium-light skin tone +1F46E 1F3FD ; fully-qualified # 👮🏽 E1.0 police officer: medium skin tone +1F46E 1F3FE ; fully-qualified # 👮🏾 E1.0 police officer: medium-dark skin tone +1F46E 1F3FF ; fully-qualified # 👮🏿 E1.0 police officer: dark skin tone +1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ E4.0 man police officer +1F46E 200D 2642 ; minimally-qualified # 👮‍♂ E4.0 man police officer +1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ E4.0 man police officer: light skin tone +1F46E 1F3FB 200D 2642 ; minimally-qualified # 👮🏻‍♂ E4.0 man police officer: light skin tone +1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ E4.0 man police officer: medium-light skin tone +1F46E 1F3FC 200D 2642 ; minimally-qualified # 👮🏼‍♂ E4.0 man police officer: medium-light skin tone +1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ E4.0 man police officer: medium skin tone +1F46E 1F3FD 200D 2642 ; minimally-qualified # 👮🏽‍♂ E4.0 man police officer: medium skin tone +1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ E4.0 man police officer: medium-dark skin tone +1F46E 1F3FE 200D 2642 ; minimally-qualified # 👮🏾‍♂ E4.0 man police officer: medium-dark skin tone +1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ E4.0 man police officer: dark skin tone +1F46E 1F3FF 200D 2642 ; minimally-qualified # 👮🏿‍♂ E4.0 man police officer: dark skin tone +1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ E4.0 woman police officer +1F46E 200D 2640 ; minimally-qualified # 👮‍♀ E4.0 woman police officer +1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ E4.0 woman police officer: light skin tone +1F46E 1F3FB 200D 2640 ; minimally-qualified # 👮🏻‍♀ E4.0 woman police officer: light skin tone +1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ E4.0 woman police officer: medium-light skin tone +1F46E 1F3FC 200D 2640 ; minimally-qualified # 👮🏼‍♀ E4.0 woman police officer: medium-light skin tone +1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ E4.0 woman police officer: medium skin tone +1F46E 1F3FD 200D 2640 ; minimally-qualified # 👮🏽‍♀ E4.0 woman police officer: medium skin tone +1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ E4.0 woman police officer: medium-dark skin tone +1F46E 1F3FE 200D 2640 ; minimally-qualified # 👮🏾‍♀ E4.0 woman police officer: medium-dark skin tone +1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ E4.0 woman police officer: dark skin tone +1F46E 1F3FF 200D 2640 ; minimally-qualified # 👮🏿‍♀ E4.0 woman police officer: dark skin tone +1F575 FE0F ; fully-qualified # 🕵️ E0.7 detective +1F575 ; unqualified # 🕵 E0.7 detective +1F575 1F3FB ; fully-qualified # 🕵🏻 E2.0 detective: light skin tone +1F575 1F3FC ; fully-qualified # 🕵🏼 E2.0 detective: medium-light skin tone +1F575 1F3FD ; fully-qualified # 🕵🏽 E2.0 detective: medium skin tone +1F575 1F3FE ; fully-qualified # 🕵🏾 E2.0 detective: medium-dark skin tone +1F575 1F3FF ; fully-qualified # 🕵🏿 E2.0 detective: dark skin tone +1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ E4.0 man detective +1F575 200D 2642 FE0F ; unqualified # 🕵‍♂️ E4.0 man detective +1F575 FE0F 200D 2642 ; unqualified # 🕵️‍♂ E4.0 man detective +1F575 200D 2642 ; unqualified # 🕵‍♂ E4.0 man detective +1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ E4.0 man detective: light skin tone +1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻‍♂ E4.0 man detective: light skin tone +1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ E4.0 man detective: medium-light skin tone +1F575 1F3FC 200D 2642 ; minimally-qualified # 🕵🏼‍♂ E4.0 man detective: medium-light skin tone +1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ E4.0 man detective: medium skin tone +1F575 1F3FD 200D 2642 ; minimally-qualified # 🕵🏽‍♂ E4.0 man detective: medium skin tone +1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ E4.0 man detective: medium-dark skin tone +1F575 1F3FE 200D 2642 ; minimally-qualified # 🕵🏾‍♂ E4.0 man detective: medium-dark skin tone +1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ E4.0 man detective: dark skin tone +1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿‍♂ E4.0 man detective: dark skin tone +1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ E4.0 woman detective +1F575 200D 2640 FE0F ; unqualified # 🕵‍♀️ E4.0 woman detective +1F575 FE0F 200D 2640 ; unqualified # 🕵️‍♀ E4.0 woman detective +1F575 200D 2640 ; unqualified # 🕵‍♀ E4.0 woman detective +1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ E4.0 woman detective: light skin tone +1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻‍♀ E4.0 woman detective: light skin tone +1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ E4.0 woman detective: medium-light skin tone +1F575 1F3FC 200D 2640 ; minimally-qualified # 🕵🏼‍♀ E4.0 woman detective: medium-light skin tone +1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ E4.0 woman detective: medium skin tone +1F575 1F3FD 200D 2640 ; minimally-qualified # 🕵🏽‍♀ E4.0 woman detective: medium skin tone +1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ E4.0 woman detective: medium-dark skin tone +1F575 1F3FE 200D 2640 ; minimally-qualified # 🕵🏾‍♀ E4.0 woman detective: medium-dark skin tone +1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ E4.0 woman detective: dark skin tone +1F575 1F3FF 200D 2640 ; minimally-qualified # 🕵🏿‍♀ E4.0 woman detective: dark skin tone +1F482 ; fully-qualified # 💂 E0.6 guard +1F482 1F3FB ; fully-qualified # 💂🏻 E1.0 guard: light skin tone +1F482 1F3FC ; fully-qualified # 💂🏼 E1.0 guard: medium-light skin tone +1F482 1F3FD ; fully-qualified # 💂🏽 E1.0 guard: medium skin tone +1F482 1F3FE ; fully-qualified # 💂🏾 E1.0 guard: medium-dark skin tone +1F482 1F3FF ; fully-qualified # 💂🏿 E1.0 guard: dark skin tone +1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ E4.0 man guard +1F482 200D 2642 ; minimally-qualified # 💂‍♂ E4.0 man guard +1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ E4.0 man guard: light skin tone +1F482 1F3FB 200D 2642 ; minimally-qualified # 💂🏻‍♂ E4.0 man guard: light skin tone +1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ E4.0 man guard: medium-light skin tone +1F482 1F3FC 200D 2642 ; minimally-qualified # 💂🏼‍♂ E4.0 man guard: medium-light skin tone +1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ E4.0 man guard: medium skin tone +1F482 1F3FD 200D 2642 ; minimally-qualified # 💂🏽‍♂ E4.0 man guard: medium skin tone +1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ E4.0 man guard: medium-dark skin tone +1F482 1F3FE 200D 2642 ; minimally-qualified # 💂🏾‍♂ E4.0 man guard: medium-dark skin tone +1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ E4.0 man guard: dark skin tone +1F482 1F3FF 200D 2642 ; minimally-qualified # 💂🏿‍♂ E4.0 man guard: dark skin tone +1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ E4.0 woman guard +1F482 200D 2640 ; minimally-qualified # 💂‍♀ E4.0 woman guard +1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ E4.0 woman guard: light skin tone +1F482 1F3FB 200D 2640 ; minimally-qualified # 💂🏻‍♀ E4.0 woman guard: light skin tone +1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ E4.0 woman guard: medium-light skin tone +1F482 1F3FC 200D 2640 ; minimally-qualified # 💂🏼‍♀ E4.0 woman guard: medium-light skin tone +1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ E4.0 woman guard: medium skin tone +1F482 1F3FD 200D 2640 ; minimally-qualified # 💂🏽‍♀ E4.0 woman guard: medium skin tone +1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ E4.0 woman guard: medium-dark skin tone +1F482 1F3FE 200D 2640 ; minimally-qualified # 💂🏾‍♀ E4.0 woman guard: medium-dark skin tone +1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ E4.0 woman guard: dark skin tone +1F482 1F3FF 200D 2640 ; minimally-qualified # 💂🏿‍♀ E4.0 woman guard: dark skin tone +1F977 ; fully-qualified # 🥷 E13.0 ninja +1F977 1F3FB ; fully-qualified # 🥷🏻 E13.0 ninja: light skin tone +1F977 1F3FC ; fully-qualified # 🥷🏼 E13.0 ninja: medium-light skin tone +1F977 1F3FD ; fully-qualified # 🥷🏽 E13.0 ninja: medium skin tone +1F977 1F3FE ; fully-qualified # 🥷🏾 E13.0 ninja: medium-dark skin tone +1F977 1F3FF ; fully-qualified # 🥷🏿 E13.0 ninja: dark skin tone +1F477 ; fully-qualified # 👷 E0.6 construction worker +1F477 1F3FB ; fully-qualified # 👷🏻 E1.0 construction worker: light skin tone +1F477 1F3FC ; fully-qualified # 👷🏼 E1.0 construction worker: medium-light skin tone +1F477 1F3FD ; fully-qualified # 👷🏽 E1.0 construction worker: medium skin tone +1F477 1F3FE ; fully-qualified # 👷🏾 E1.0 construction worker: medium-dark skin tone +1F477 1F3FF ; fully-qualified # 👷🏿 E1.0 construction worker: dark skin tone +1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ E4.0 man construction worker +1F477 200D 2642 ; minimally-qualified # 👷‍♂ E4.0 man construction worker +1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ E4.0 man construction worker: light skin tone +1F477 1F3FB 200D 2642 ; minimally-qualified # 👷🏻‍♂ E4.0 man construction worker: light skin tone +1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ E4.0 man construction worker: medium-light skin tone +1F477 1F3FC 200D 2642 ; minimally-qualified # 👷🏼‍♂ E4.0 man construction worker: medium-light skin tone +1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ E4.0 man construction worker: medium skin tone +1F477 1F3FD 200D 2642 ; minimally-qualified # 👷🏽‍♂ E4.0 man construction worker: medium skin tone +1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ E4.0 man construction worker: medium-dark skin tone +1F477 1F3FE 200D 2642 ; minimally-qualified # 👷🏾‍♂ E4.0 man construction worker: medium-dark skin tone +1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ E4.0 man construction worker: dark skin tone +1F477 1F3FF 200D 2642 ; minimally-qualified # 👷🏿‍♂ E4.0 man construction worker: dark skin tone +1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ E4.0 woman construction worker +1F477 200D 2640 ; minimally-qualified # 👷‍♀ E4.0 woman construction worker +1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ E4.0 woman construction worker: light skin tone +1F477 1F3FB 200D 2640 ; minimally-qualified # 👷🏻‍♀ E4.0 woman construction worker: light skin tone +1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ E4.0 woman construction worker: medium-light skin tone +1F477 1F3FC 200D 2640 ; minimally-qualified # 👷🏼‍♀ E4.0 woman construction worker: medium-light skin tone +1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ E4.0 woman construction worker: medium skin tone +1F477 1F3FD 200D 2640 ; minimally-qualified # 👷🏽‍♀ E4.0 woman construction worker: medium skin tone +1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ E4.0 woman construction worker: medium-dark skin tone +1F477 1F3FE 200D 2640 ; minimally-qualified # 👷🏾‍♀ E4.0 woman construction worker: medium-dark skin tone +1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ E4.0 woman construction worker: dark skin tone +1F477 1F3FF 200D 2640 ; minimally-qualified # 👷🏿‍♀ E4.0 woman construction worker: dark skin tone +1F934 ; fully-qualified # 🤴 E3.0 prince +1F934 1F3FB ; fully-qualified # 🤴🏻 E3.0 prince: light skin tone +1F934 1F3FC ; fully-qualified # 🤴🏼 E3.0 prince: medium-light skin tone +1F934 1F3FD ; fully-qualified # 🤴🏽 E3.0 prince: medium skin tone +1F934 1F3FE ; fully-qualified # 🤴🏾 E3.0 prince: medium-dark skin tone +1F934 1F3FF ; fully-qualified # 🤴🏿 E3.0 prince: dark skin tone +1F478 ; fully-qualified # 👸 E0.6 princess +1F478 1F3FB ; fully-qualified # 👸🏻 E1.0 princess: light skin tone +1F478 1F3FC ; fully-qualified # 👸🏼 E1.0 princess: medium-light skin tone +1F478 1F3FD ; fully-qualified # 👸🏽 E1.0 princess: medium skin tone +1F478 1F3FE ; fully-qualified # 👸🏾 E1.0 princess: medium-dark skin tone +1F478 1F3FF ; fully-qualified # 👸🏿 E1.0 princess: dark skin tone +1F473 ; fully-qualified # 👳 E0.6 person wearing turban +1F473 1F3FB ; fully-qualified # 👳🏻 E1.0 person wearing turban: light skin tone +1F473 1F3FC ; fully-qualified # 👳🏼 E1.0 person wearing turban: medium-light skin tone +1F473 1F3FD ; fully-qualified # 👳🏽 E1.0 person wearing turban: medium skin tone +1F473 1F3FE ; fully-qualified # 👳🏾 E1.0 person wearing turban: medium-dark skin tone +1F473 1F3FF ; fully-qualified # 👳🏿 E1.0 person wearing turban: dark skin tone +1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ E4.0 man wearing turban +1F473 200D 2642 ; minimally-qualified # 👳‍♂ E4.0 man wearing turban +1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ E4.0 man wearing turban: light skin tone +1F473 1F3FB 200D 2642 ; minimally-qualified # 👳🏻‍♂ E4.0 man wearing turban: light skin tone +1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ E4.0 man wearing turban: medium-light skin tone +1F473 1F3FC 200D 2642 ; minimally-qualified # 👳🏼‍♂ E4.0 man wearing turban: medium-light skin tone +1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ E4.0 man wearing turban: medium skin tone +1F473 1F3FD 200D 2642 ; minimally-qualified # 👳🏽‍♂ E4.0 man wearing turban: medium skin tone +1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ E4.0 man wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2642 ; minimally-qualified # 👳🏾‍♂ E4.0 man wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ E4.0 man wearing turban: dark skin tone +1F473 1F3FF 200D 2642 ; minimally-qualified # 👳🏿‍♂ E4.0 man wearing turban: dark skin tone +1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ E4.0 woman wearing turban +1F473 200D 2640 ; minimally-qualified # 👳‍♀ E4.0 woman wearing turban +1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ E4.0 woman wearing turban: light skin tone +1F473 1F3FB 200D 2640 ; minimally-qualified # 👳🏻‍♀ E4.0 woman wearing turban: light skin tone +1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ E4.0 woman wearing turban: medium-light skin tone +1F473 1F3FC 200D 2640 ; minimally-qualified # 👳🏼‍♀ E4.0 woman wearing turban: medium-light skin tone +1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ E4.0 woman wearing turban: medium skin tone +1F473 1F3FD 200D 2640 ; minimally-qualified # 👳🏽‍♀ E4.0 woman wearing turban: medium skin tone +1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ E4.0 woman wearing turban: medium-dark skin tone +1F473 1F3FE 200D 2640 ; minimally-qualified # 👳🏾‍♀ E4.0 woman wearing turban: medium-dark skin tone +1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ E4.0 woman wearing turban: dark skin tone +1F473 1F3FF 200D 2640 ; minimally-qualified # 👳🏿‍♀ E4.0 woman wearing turban: dark skin tone +1F472 ; fully-qualified # 👲 E0.6 person with skullcap +1F472 1F3FB ; fully-qualified # 👲🏻 E1.0 person with skullcap: light skin tone +1F472 1F3FC ; fully-qualified # 👲🏼 E1.0 person with skullcap: medium-light skin tone +1F472 1F3FD ; fully-qualified # 👲🏽 E1.0 person with skullcap: medium skin tone +1F472 1F3FE ; fully-qualified # 👲🏾 E1.0 person with skullcap: medium-dark skin tone +1F472 1F3FF ; fully-qualified # 👲🏿 E1.0 person with skullcap: dark skin tone +1F9D5 ; fully-qualified # 🧕 E5.0 woman with headscarf +1F9D5 1F3FB ; fully-qualified # 🧕🏻 E5.0 woman with headscarf: light skin tone +1F9D5 1F3FC ; fully-qualified # 🧕🏼 E5.0 woman with headscarf: medium-light skin tone +1F9D5 1F3FD ; fully-qualified # 🧕🏽 E5.0 woman with headscarf: medium skin tone +1F9D5 1F3FE ; fully-qualified # 🧕🏾 E5.0 woman with headscarf: medium-dark skin tone +1F9D5 1F3FF ; fully-qualified # 🧕🏿 E5.0 woman with headscarf: dark skin tone +1F935 ; fully-qualified # 🤵 E3.0 person in tuxedo +1F935 1F3FB ; fully-qualified # 🤵🏻 E3.0 person in tuxedo: light skin tone +1F935 1F3FC ; fully-qualified # 🤵🏼 E3.0 person in tuxedo: medium-light skin tone +1F935 1F3FD ; fully-qualified # 🤵🏽 E3.0 person in tuxedo: medium skin tone +1F935 1F3FE ; fully-qualified # 🤵🏾 E3.0 person in tuxedo: medium-dark skin tone +1F935 1F3FF ; fully-qualified # 🤵🏿 E3.0 person in tuxedo: dark skin tone +1F935 200D 2642 FE0F ; fully-qualified # 🤵‍♂️ E13.0 man in tuxedo +1F935 200D 2642 ; minimally-qualified # 🤵‍♂ E13.0 man in tuxedo +1F935 1F3FB 200D 2642 FE0F ; fully-qualified # 🤵🏻‍♂️ E13.0 man in tuxedo: light skin tone +1F935 1F3FB 200D 2642 ; minimally-qualified # 🤵🏻‍♂ E13.0 man in tuxedo: light skin tone +1F935 1F3FC 200D 2642 FE0F ; fully-qualified # 🤵🏼‍♂️ E13.0 man in tuxedo: medium-light skin tone +1F935 1F3FC 200D 2642 ; minimally-qualified # 🤵🏼‍♂ E13.0 man in tuxedo: medium-light skin tone +1F935 1F3FD 200D 2642 FE0F ; fully-qualified # 🤵🏽‍♂️ E13.0 man in tuxedo: medium skin tone +1F935 1F3FD 200D 2642 ; minimally-qualified # 🤵🏽‍♂ E13.0 man in tuxedo: medium skin tone +1F935 1F3FE 200D 2642 FE0F ; fully-qualified # 🤵🏾‍♂️ E13.0 man in tuxedo: medium-dark skin tone +1F935 1F3FE 200D 2642 ; minimally-qualified # 🤵🏾‍♂ E13.0 man in tuxedo: medium-dark skin tone +1F935 1F3FF 200D 2642 FE0F ; fully-qualified # 🤵🏿‍♂️ E13.0 man in tuxedo: dark skin tone +1F935 1F3FF 200D 2642 ; minimally-qualified # 🤵🏿‍♂ E13.0 man in tuxedo: dark skin tone +1F935 200D 2640 FE0F ; fully-qualified # 🤵‍♀️ E13.0 woman in tuxedo +1F935 200D 2640 ; minimally-qualified # 🤵‍♀ E13.0 woman in tuxedo +1F935 1F3FB 200D 2640 FE0F ; fully-qualified # 🤵🏻‍♀️ E13.0 woman in tuxedo: light skin tone +1F935 1F3FB 200D 2640 ; minimally-qualified # 🤵🏻‍♀ E13.0 woman in tuxedo: light skin tone +1F935 1F3FC 200D 2640 FE0F ; fully-qualified # 🤵🏼‍♀️ E13.0 woman in tuxedo: medium-light skin tone +1F935 1F3FC 200D 2640 ; minimally-qualified # 🤵🏼‍♀ E13.0 woman in tuxedo: medium-light skin tone +1F935 1F3FD 200D 2640 FE0F ; fully-qualified # 🤵🏽‍♀️ E13.0 woman in tuxedo: medium skin tone +1F935 1F3FD 200D 2640 ; minimally-qualified # 🤵🏽‍♀ E13.0 woman in tuxedo: medium skin tone +1F935 1F3FE 200D 2640 FE0F ; fully-qualified # 🤵🏾‍♀️ E13.0 woman in tuxedo: medium-dark skin tone +1F935 1F3FE 200D 2640 ; minimally-qualified # 🤵🏾‍♀ E13.0 woman in tuxedo: medium-dark skin tone +1F935 1F3FF 200D 2640 FE0F ; fully-qualified # 🤵🏿‍♀️ E13.0 woman in tuxedo: dark skin tone +1F935 1F3FF 200D 2640 ; minimally-qualified # 🤵🏿‍♀ E13.0 woman in tuxedo: dark skin tone +1F470 ; fully-qualified # 👰 E0.6 person with veil +1F470 1F3FB ; fully-qualified # 👰🏻 E1.0 person with veil: light skin tone +1F470 1F3FC ; fully-qualified # 👰🏼 E1.0 person with veil: medium-light skin tone +1F470 1F3FD ; fully-qualified # 👰🏽 E1.0 person with veil: medium skin tone +1F470 1F3FE ; fully-qualified # 👰🏾 E1.0 person with veil: medium-dark skin tone +1F470 1F3FF ; fully-qualified # 👰🏿 E1.0 person with veil: dark skin tone +1F470 200D 2642 FE0F ; fully-qualified # 👰‍♂️ E13.0 man with veil +1F470 200D 2642 ; minimally-qualified # 👰‍♂ E13.0 man with veil +1F470 1F3FB 200D 2642 FE0F ; fully-qualified # 👰🏻‍♂️ E13.0 man with veil: light skin tone +1F470 1F3FB 200D 2642 ; minimally-qualified # 👰🏻‍♂ E13.0 man with veil: light skin tone +1F470 1F3FC 200D 2642 FE0F ; fully-qualified # 👰🏼‍♂️ E13.0 man with veil: medium-light skin tone +1F470 1F3FC 200D 2642 ; minimally-qualified # 👰🏼‍♂ E13.0 man with veil: medium-light skin tone +1F470 1F3FD 200D 2642 FE0F ; fully-qualified # 👰🏽‍♂️ E13.0 man with veil: medium skin tone +1F470 1F3FD 200D 2642 ; minimally-qualified # 👰🏽‍♂ E13.0 man with veil: medium skin tone +1F470 1F3FE 200D 2642 FE0F ; fully-qualified # 👰🏾‍♂️ E13.0 man with veil: medium-dark skin tone +1F470 1F3FE 200D 2642 ; minimally-qualified # 👰🏾‍♂ E13.0 man with veil: medium-dark skin tone +1F470 1F3FF 200D 2642 FE0F ; fully-qualified # 👰🏿‍♂️ E13.0 man with veil: dark skin tone +1F470 1F3FF 200D 2642 ; minimally-qualified # 👰🏿‍♂ E13.0 man with veil: dark skin tone +1F470 200D 2640 FE0F ; fully-qualified # 👰‍♀️ E13.0 woman with veil +1F470 200D 2640 ; minimally-qualified # 👰‍♀ E13.0 woman with veil +1F470 1F3FB 200D 2640 FE0F ; fully-qualified # 👰🏻‍♀️ E13.0 woman with veil: light skin tone +1F470 1F3FB 200D 2640 ; minimally-qualified # 👰🏻‍♀ E13.0 woman with veil: light skin tone +1F470 1F3FC 200D 2640 FE0F ; fully-qualified # 👰🏼‍♀️ E13.0 woman with veil: medium-light skin tone +1F470 1F3FC 200D 2640 ; minimally-qualified # 👰🏼‍♀ E13.0 woman with veil: medium-light skin tone +1F470 1F3FD 200D 2640 FE0F ; fully-qualified # 👰🏽‍♀️ E13.0 woman with veil: medium skin tone +1F470 1F3FD 200D 2640 ; minimally-qualified # 👰🏽‍♀ E13.0 woman with veil: medium skin tone +1F470 1F3FE 200D 2640 FE0F ; fully-qualified # 👰🏾‍♀️ E13.0 woman with veil: medium-dark skin tone +1F470 1F3FE 200D 2640 ; minimally-qualified # 👰🏾‍♀ E13.0 woman with veil: medium-dark skin tone +1F470 1F3FF 200D 2640 FE0F ; fully-qualified # 👰🏿‍♀️ E13.0 woman with veil: dark skin tone +1F470 1F3FF 200D 2640 ; minimally-qualified # 👰🏿‍♀ E13.0 woman with veil: dark skin tone +1F930 ; fully-qualified # 🤰 E3.0 pregnant woman +1F930 1F3FB ; fully-qualified # 🤰🏻 E3.0 pregnant woman: light skin tone +1F930 1F3FC ; fully-qualified # 🤰🏼 E3.0 pregnant woman: medium-light skin tone +1F930 1F3FD ; fully-qualified # 🤰🏽 E3.0 pregnant woman: medium skin tone +1F930 1F3FE ; fully-qualified # 🤰🏾 E3.0 pregnant woman: medium-dark skin tone +1F930 1F3FF ; fully-qualified # 🤰🏿 E3.0 pregnant woman: dark skin tone +1F931 ; fully-qualified # 🤱 E5.0 breast-feeding +1F931 1F3FB ; fully-qualified # 🤱🏻 E5.0 breast-feeding: light skin tone +1F931 1F3FC ; fully-qualified # 🤱🏼 E5.0 breast-feeding: medium-light skin tone +1F931 1F3FD ; fully-qualified # 🤱🏽 E5.0 breast-feeding: medium skin tone +1F931 1F3FE ; fully-qualified # 🤱🏾 E5.0 breast-feeding: medium-dark skin tone +1F931 1F3FF ; fully-qualified # 🤱🏿 E5.0 breast-feeding: dark skin tone +1F469 200D 1F37C ; fully-qualified # 👩‍🍼 E13.0 woman feeding baby +1F469 1F3FB 200D 1F37C ; fully-qualified # 👩🏻‍🍼 E13.0 woman feeding baby: light skin tone +1F469 1F3FC 200D 1F37C ; fully-qualified # 👩🏼‍🍼 E13.0 woman feeding baby: medium-light skin tone +1F469 1F3FD 200D 1F37C ; fully-qualified # 👩🏽‍🍼 E13.0 woman feeding baby: medium skin tone +1F469 1F3FE 200D 1F37C ; fully-qualified # 👩🏾‍🍼 E13.0 woman feeding baby: medium-dark skin tone +1F469 1F3FF 200D 1F37C ; fully-qualified # 👩🏿‍🍼 E13.0 woman feeding baby: dark skin tone +1F468 200D 1F37C ; fully-qualified # 👨‍🍼 E13.0 man feeding baby +1F468 1F3FB 200D 1F37C ; fully-qualified # 👨🏻‍🍼 E13.0 man feeding baby: light skin tone +1F468 1F3FC 200D 1F37C ; fully-qualified # 👨🏼‍🍼 E13.0 man feeding baby: medium-light skin tone +1F468 1F3FD 200D 1F37C ; fully-qualified # 👨🏽‍🍼 E13.0 man feeding baby: medium skin tone +1F468 1F3FE 200D 1F37C ; fully-qualified # 👨🏾‍🍼 E13.0 man feeding baby: medium-dark skin tone +1F468 1F3FF 200D 1F37C ; fully-qualified # 👨🏿‍🍼 E13.0 man feeding baby: dark skin tone +1F9D1 200D 1F37C ; fully-qualified # 🧑‍🍼 E13.0 person feeding baby +1F9D1 1F3FB 200D 1F37C ; fully-qualified # 🧑🏻‍🍼 E13.0 person feeding baby: light skin tone +1F9D1 1F3FC 200D 1F37C ; fully-qualified # 🧑🏼‍🍼 E13.0 person feeding baby: medium-light skin tone +1F9D1 1F3FD 200D 1F37C ; fully-qualified # 🧑🏽‍🍼 E13.0 person feeding baby: medium skin tone +1F9D1 1F3FE 200D 1F37C ; fully-qualified # 🧑🏾‍🍼 E13.0 person feeding baby: medium-dark skin tone +1F9D1 1F3FF 200D 1F37C ; fully-qualified # 🧑🏿‍🍼 E13.0 person feeding baby: dark skin tone + +# subgroup: person-fantasy +1F47C ; fully-qualified # 👼 E0.6 baby angel +1F47C 1F3FB ; fully-qualified # 👼🏻 E1.0 baby angel: light skin tone +1F47C 1F3FC ; fully-qualified # 👼🏼 E1.0 baby angel: medium-light skin tone +1F47C 1F3FD ; fully-qualified # 👼🏽 E1.0 baby angel: medium skin tone +1F47C 1F3FE ; fully-qualified # 👼🏾 E1.0 baby angel: medium-dark skin tone +1F47C 1F3FF ; fully-qualified # 👼🏿 E1.0 baby angel: dark skin tone +1F385 ; fully-qualified # 🎅 E0.6 Santa Claus +1F385 1F3FB ; fully-qualified # 🎅🏻 E1.0 Santa Claus: light skin tone +1F385 1F3FC ; fully-qualified # 🎅🏼 E1.0 Santa Claus: medium-light skin tone +1F385 1F3FD ; fully-qualified # 🎅🏽 E1.0 Santa Claus: medium skin tone +1F385 1F3FE ; fully-qualified # 🎅🏾 E1.0 Santa Claus: medium-dark skin tone +1F385 1F3FF ; fully-qualified # 🎅🏿 E1.0 Santa Claus: dark skin tone +1F936 ; fully-qualified # 🤶 E3.0 Mrs. Claus +1F936 1F3FB ; fully-qualified # 🤶🏻 E3.0 Mrs. Claus: light skin tone +1F936 1F3FC ; fully-qualified # 🤶🏼 E3.0 Mrs. Claus: medium-light skin tone +1F936 1F3FD ; fully-qualified # 🤶🏽 E3.0 Mrs. Claus: medium skin tone +1F936 1F3FE ; fully-qualified # 🤶🏾 E3.0 Mrs. Claus: medium-dark skin tone +1F936 1F3FF ; fully-qualified # 🤶🏿 E3.0 Mrs. Claus: dark skin tone +1F9D1 200D 1F384 ; fully-qualified # 🧑‍🎄 E13.0 mx claus +1F9D1 1F3FB 200D 1F384 ; fully-qualified # 🧑🏻‍🎄 E13.0 mx claus: light skin tone +1F9D1 1F3FC 200D 1F384 ; fully-qualified # 🧑🏼‍🎄 E13.0 mx claus: medium-light skin tone +1F9D1 1F3FD 200D 1F384 ; fully-qualified # 🧑🏽‍🎄 E13.0 mx claus: medium skin tone +1F9D1 1F3FE 200D 1F384 ; fully-qualified # 🧑🏾‍🎄 E13.0 mx claus: medium-dark skin tone +1F9D1 1F3FF 200D 1F384 ; fully-qualified # 🧑🏿‍🎄 E13.0 mx claus: dark skin tone +1F9B8 ; fully-qualified # 🦸 E11.0 superhero +1F9B8 1F3FB ; fully-qualified # 🦸🏻 E11.0 superhero: light skin tone +1F9B8 1F3FC ; fully-qualified # 🦸🏼 E11.0 superhero: medium-light skin tone +1F9B8 1F3FD ; fully-qualified # 🦸🏽 E11.0 superhero: medium skin tone +1F9B8 1F3FE ; fully-qualified # 🦸🏾 E11.0 superhero: medium-dark skin tone +1F9B8 1F3FF ; fully-qualified # 🦸🏿 E11.0 superhero: dark skin tone +1F9B8 200D 2642 FE0F ; fully-qualified # 🦸‍♂️ E11.0 man superhero +1F9B8 200D 2642 ; minimally-qualified # 🦸‍♂ E11.0 man superhero +1F9B8 1F3FB 200D 2642 FE0F ; fully-qualified # 🦸🏻‍♂️ E11.0 man superhero: light skin tone +1F9B8 1F3FB 200D 2642 ; minimally-qualified # 🦸🏻‍♂ E11.0 man superhero: light skin tone +1F9B8 1F3FC 200D 2642 FE0F ; fully-qualified # 🦸🏼‍♂️ E11.0 man superhero: medium-light skin tone +1F9B8 1F3FC 200D 2642 ; minimally-qualified # 🦸🏼‍♂ E11.0 man superhero: medium-light skin tone +1F9B8 1F3FD 200D 2642 FE0F ; fully-qualified # 🦸🏽‍♂️ E11.0 man superhero: medium skin tone +1F9B8 1F3FD 200D 2642 ; minimally-qualified # 🦸🏽‍♂ E11.0 man superhero: medium skin tone +1F9B8 1F3FE 200D 2642 FE0F ; fully-qualified # 🦸🏾‍♂️ E11.0 man superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2642 ; minimally-qualified # 🦸🏾‍♂ E11.0 man superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2642 FE0F ; fully-qualified # 🦸🏿‍♂️ E11.0 man superhero: dark skin tone +1F9B8 1F3FF 200D 2642 ; minimally-qualified # 🦸🏿‍♂ E11.0 man superhero: dark skin tone +1F9B8 200D 2640 FE0F ; fully-qualified # 🦸‍♀️ E11.0 woman superhero +1F9B8 200D 2640 ; minimally-qualified # 🦸‍♀ E11.0 woman superhero +1F9B8 1F3FB 200D 2640 FE0F ; fully-qualified # 🦸🏻‍♀️ E11.0 woman superhero: light skin tone +1F9B8 1F3FB 200D 2640 ; minimally-qualified # 🦸🏻‍♀ E11.0 woman superhero: light skin tone +1F9B8 1F3FC 200D 2640 FE0F ; fully-qualified # 🦸🏼‍♀️ E11.0 woman superhero: medium-light skin tone +1F9B8 1F3FC 200D 2640 ; minimally-qualified # 🦸🏼‍♀ E11.0 woman superhero: medium-light skin tone +1F9B8 1F3FD 200D 2640 FE0F ; fully-qualified # 🦸🏽‍♀️ E11.0 woman superhero: medium skin tone +1F9B8 1F3FD 200D 2640 ; minimally-qualified # 🦸🏽‍♀ E11.0 woman superhero: medium skin tone +1F9B8 1F3FE 200D 2640 FE0F ; fully-qualified # 🦸🏾‍♀️ E11.0 woman superhero: medium-dark skin tone +1F9B8 1F3FE 200D 2640 ; minimally-qualified # 🦸🏾‍♀ E11.0 woman superhero: medium-dark skin tone +1F9B8 1F3FF 200D 2640 FE0F ; fully-qualified # 🦸🏿‍♀️ E11.0 woman superhero: dark skin tone +1F9B8 1F3FF 200D 2640 ; minimally-qualified # 🦸🏿‍♀ E11.0 woman superhero: dark skin tone +1F9B9 ; fully-qualified # 🦹 E11.0 supervillain +1F9B9 1F3FB ; fully-qualified # 🦹🏻 E11.0 supervillain: light skin tone +1F9B9 1F3FC ; fully-qualified # 🦹🏼 E11.0 supervillain: medium-light skin tone +1F9B9 1F3FD ; fully-qualified # 🦹🏽 E11.0 supervillain: medium skin tone +1F9B9 1F3FE ; fully-qualified # 🦹🏾 E11.0 supervillain: medium-dark skin tone +1F9B9 1F3FF ; fully-qualified # 🦹🏿 E11.0 supervillain: dark skin tone +1F9B9 200D 2642 FE0F ; fully-qualified # 🦹‍♂️ E11.0 man supervillain +1F9B9 200D 2642 ; minimally-qualified # 🦹‍♂ E11.0 man supervillain +1F9B9 1F3FB 200D 2642 FE0F ; fully-qualified # 🦹🏻‍♂️ E11.0 man supervillain: light skin tone +1F9B9 1F3FB 200D 2642 ; minimally-qualified # 🦹🏻‍♂ E11.0 man supervillain: light skin tone +1F9B9 1F3FC 200D 2642 FE0F ; fully-qualified # 🦹🏼‍♂️ E11.0 man supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2642 ; minimally-qualified # 🦹🏼‍♂ E11.0 man supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2642 FE0F ; fully-qualified # 🦹🏽‍♂️ E11.0 man supervillain: medium skin tone +1F9B9 1F3FD 200D 2642 ; minimally-qualified # 🦹🏽‍♂ E11.0 man supervillain: medium skin tone +1F9B9 1F3FE 200D 2642 FE0F ; fully-qualified # 🦹🏾‍♂️ E11.0 man supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2642 ; minimally-qualified # 🦹🏾‍♂ E11.0 man supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2642 FE0F ; fully-qualified # 🦹🏿‍♂️ E11.0 man supervillain: dark skin tone +1F9B9 1F3FF 200D 2642 ; minimally-qualified # 🦹🏿‍♂ E11.0 man supervillain: dark skin tone +1F9B9 200D 2640 FE0F ; fully-qualified # 🦹‍♀️ E11.0 woman supervillain +1F9B9 200D 2640 ; minimally-qualified # 🦹‍♀ E11.0 woman supervillain +1F9B9 1F3FB 200D 2640 FE0F ; fully-qualified # 🦹🏻‍♀️ E11.0 woman supervillain: light skin tone +1F9B9 1F3FB 200D 2640 ; minimally-qualified # 🦹🏻‍♀ E11.0 woman supervillain: light skin tone +1F9B9 1F3FC 200D 2640 FE0F ; fully-qualified # 🦹🏼‍♀️ E11.0 woman supervillain: medium-light skin tone +1F9B9 1F3FC 200D 2640 ; minimally-qualified # 🦹🏼‍♀ E11.0 woman supervillain: medium-light skin tone +1F9B9 1F3FD 200D 2640 FE0F ; fully-qualified # 🦹🏽‍♀️ E11.0 woman supervillain: medium skin tone +1F9B9 1F3FD 200D 2640 ; minimally-qualified # 🦹🏽‍♀ E11.0 woman supervillain: medium skin tone +1F9B9 1F3FE 200D 2640 FE0F ; fully-qualified # 🦹🏾‍♀️ E11.0 woman supervillain: medium-dark skin tone +1F9B9 1F3FE 200D 2640 ; minimally-qualified # 🦹🏾‍♀ E11.0 woman supervillain: medium-dark skin tone +1F9B9 1F3FF 200D 2640 FE0F ; fully-qualified # 🦹🏿‍♀️ E11.0 woman supervillain: dark skin tone +1F9B9 1F3FF 200D 2640 ; minimally-qualified # 🦹🏿‍♀ E11.0 woman supervillain: dark skin tone +1F9D9 ; fully-qualified # 🧙 E5.0 mage +1F9D9 1F3FB ; fully-qualified # 🧙🏻 E5.0 mage: light skin tone +1F9D9 1F3FC ; fully-qualified # 🧙🏼 E5.0 mage: medium-light skin tone +1F9D9 1F3FD ; fully-qualified # 🧙🏽 E5.0 mage: medium skin tone +1F9D9 1F3FE ; fully-qualified # 🧙🏾 E5.0 mage: medium-dark skin tone +1F9D9 1F3FF ; fully-qualified # 🧙🏿 E5.0 mage: dark skin tone +1F9D9 200D 2642 FE0F ; fully-qualified # 🧙‍♂️ E5.0 man mage +1F9D9 200D 2642 ; minimally-qualified # 🧙‍♂ E5.0 man mage +1F9D9 1F3FB 200D 2642 FE0F ; fully-qualified # 🧙🏻‍♂️ E5.0 man mage: light skin tone +1F9D9 1F3FB 200D 2642 ; minimally-qualified # 🧙🏻‍♂ E5.0 man mage: light skin tone +1F9D9 1F3FC 200D 2642 FE0F ; fully-qualified # 🧙🏼‍♂️ E5.0 man mage: medium-light skin tone +1F9D9 1F3FC 200D 2642 ; minimally-qualified # 🧙🏼‍♂ E5.0 man mage: medium-light skin tone +1F9D9 1F3FD 200D 2642 FE0F ; fully-qualified # 🧙🏽‍♂️ E5.0 man mage: medium skin tone +1F9D9 1F3FD 200D 2642 ; minimally-qualified # 🧙🏽‍♂ E5.0 man mage: medium skin tone +1F9D9 1F3FE 200D 2642 FE0F ; fully-qualified # 🧙🏾‍♂️ E5.0 man mage: medium-dark skin tone +1F9D9 1F3FE 200D 2642 ; minimally-qualified # 🧙🏾‍♂ E5.0 man mage: medium-dark skin tone +1F9D9 1F3FF 200D 2642 FE0F ; fully-qualified # 🧙🏿‍♂️ E5.0 man mage: dark skin tone +1F9D9 1F3FF 200D 2642 ; minimally-qualified # 🧙🏿‍♂ E5.0 man mage: dark skin tone +1F9D9 200D 2640 FE0F ; fully-qualified # 🧙‍♀️ E5.0 woman mage +1F9D9 200D 2640 ; minimally-qualified # 🧙‍♀ E5.0 woman mage +1F9D9 1F3FB 200D 2640 FE0F ; fully-qualified # 🧙🏻‍♀️ E5.0 woman mage: light skin tone +1F9D9 1F3FB 200D 2640 ; minimally-qualified # 🧙🏻‍♀ E5.0 woman mage: light skin tone +1F9D9 1F3FC 200D 2640 FE0F ; fully-qualified # 🧙🏼‍♀️ E5.0 woman mage: medium-light skin tone +1F9D9 1F3FC 200D 2640 ; minimally-qualified # 🧙🏼‍♀ E5.0 woman mage: medium-light skin tone +1F9D9 1F3FD 200D 2640 FE0F ; fully-qualified # 🧙🏽‍♀️ E5.0 woman mage: medium skin tone +1F9D9 1F3FD 200D 2640 ; minimally-qualified # 🧙🏽‍♀ E5.0 woman mage: medium skin tone +1F9D9 1F3FE 200D 2640 FE0F ; fully-qualified # 🧙🏾‍♀️ E5.0 woman mage: medium-dark skin tone +1F9D9 1F3FE 200D 2640 ; minimally-qualified # 🧙🏾‍♀ E5.0 woman mage: medium-dark skin tone +1F9D9 1F3FF 200D 2640 FE0F ; fully-qualified # 🧙🏿‍♀️ E5.0 woman mage: dark skin tone +1F9D9 1F3FF 200D 2640 ; minimally-qualified # 🧙🏿‍♀ E5.0 woman mage: dark skin tone +1F9DA ; fully-qualified # 🧚 E5.0 fairy +1F9DA 1F3FB ; fully-qualified # 🧚🏻 E5.0 fairy: light skin tone +1F9DA 1F3FC ; fully-qualified # 🧚🏼 E5.0 fairy: medium-light skin tone +1F9DA 1F3FD ; fully-qualified # 🧚🏽 E5.0 fairy: medium skin tone +1F9DA 1F3FE ; fully-qualified # 🧚🏾 E5.0 fairy: medium-dark skin tone +1F9DA 1F3FF ; fully-qualified # 🧚🏿 E5.0 fairy: dark skin tone +1F9DA 200D 2642 FE0F ; fully-qualified # 🧚‍♂️ E5.0 man fairy +1F9DA 200D 2642 ; minimally-qualified # 🧚‍♂ E5.0 man fairy +1F9DA 1F3FB 200D 2642 FE0F ; fully-qualified # 🧚🏻‍♂️ E5.0 man fairy: light skin tone +1F9DA 1F3FB 200D 2642 ; minimally-qualified # 🧚🏻‍♂ E5.0 man fairy: light skin tone +1F9DA 1F3FC 200D 2642 FE0F ; fully-qualified # 🧚🏼‍♂️ E5.0 man fairy: medium-light skin tone +1F9DA 1F3FC 200D 2642 ; minimally-qualified # 🧚🏼‍♂ E5.0 man fairy: medium-light skin tone +1F9DA 1F3FD 200D 2642 FE0F ; fully-qualified # 🧚🏽‍♂️ E5.0 man fairy: medium skin tone +1F9DA 1F3FD 200D 2642 ; minimally-qualified # 🧚🏽‍♂ E5.0 man fairy: medium skin tone +1F9DA 1F3FE 200D 2642 FE0F ; fully-qualified # 🧚🏾‍♂️ E5.0 man fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2642 ; minimally-qualified # 🧚🏾‍♂ E5.0 man fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2642 FE0F ; fully-qualified # 🧚🏿‍♂️ E5.0 man fairy: dark skin tone +1F9DA 1F3FF 200D 2642 ; minimally-qualified # 🧚🏿‍♂ E5.0 man fairy: dark skin tone +1F9DA 200D 2640 FE0F ; fully-qualified # 🧚‍♀️ E5.0 woman fairy +1F9DA 200D 2640 ; minimally-qualified # 🧚‍♀ E5.0 woman fairy +1F9DA 1F3FB 200D 2640 FE0F ; fully-qualified # 🧚🏻‍♀️ E5.0 woman fairy: light skin tone +1F9DA 1F3FB 200D 2640 ; minimally-qualified # 🧚🏻‍♀ E5.0 woman fairy: light skin tone +1F9DA 1F3FC 200D 2640 FE0F ; fully-qualified # 🧚🏼‍♀️ E5.0 woman fairy: medium-light skin tone +1F9DA 1F3FC 200D 2640 ; minimally-qualified # 🧚🏼‍♀ E5.0 woman fairy: medium-light skin tone +1F9DA 1F3FD 200D 2640 FE0F ; fully-qualified # 🧚🏽‍♀️ E5.0 woman fairy: medium skin tone +1F9DA 1F3FD 200D 2640 ; minimally-qualified # 🧚🏽‍♀ E5.0 woman fairy: medium skin tone +1F9DA 1F3FE 200D 2640 FE0F ; fully-qualified # 🧚🏾‍♀️ E5.0 woman fairy: medium-dark skin tone +1F9DA 1F3FE 200D 2640 ; minimally-qualified # 🧚🏾‍♀ E5.0 woman fairy: medium-dark skin tone +1F9DA 1F3FF 200D 2640 FE0F ; fully-qualified # 🧚🏿‍♀️ E5.0 woman fairy: dark skin tone +1F9DA 1F3FF 200D 2640 ; minimally-qualified # 🧚🏿‍♀ E5.0 woman fairy: dark skin tone +1F9DB ; fully-qualified # 🧛 E5.0 vampire +1F9DB 1F3FB ; fully-qualified # 🧛🏻 E5.0 vampire: light skin tone +1F9DB 1F3FC ; fully-qualified # 🧛🏼 E5.0 vampire: medium-light skin tone +1F9DB 1F3FD ; fully-qualified # 🧛🏽 E5.0 vampire: medium skin tone +1F9DB 1F3FE ; fully-qualified # 🧛🏾 E5.0 vampire: medium-dark skin tone +1F9DB 1F3FF ; fully-qualified # 🧛🏿 E5.0 vampire: dark skin tone +1F9DB 200D 2642 FE0F ; fully-qualified # 🧛‍♂️ E5.0 man vampire +1F9DB 200D 2642 ; minimally-qualified # 🧛‍♂ E5.0 man vampire +1F9DB 1F3FB 200D 2642 FE0F ; fully-qualified # 🧛🏻‍♂️ E5.0 man vampire: light skin tone +1F9DB 1F3FB 200D 2642 ; minimally-qualified # 🧛🏻‍♂ E5.0 man vampire: light skin tone +1F9DB 1F3FC 200D 2642 FE0F ; fully-qualified # 🧛🏼‍♂️ E5.0 man vampire: medium-light skin tone +1F9DB 1F3FC 200D 2642 ; minimally-qualified # 🧛🏼‍♂ E5.0 man vampire: medium-light skin tone +1F9DB 1F3FD 200D 2642 FE0F ; fully-qualified # 🧛🏽‍♂️ E5.0 man vampire: medium skin tone +1F9DB 1F3FD 200D 2642 ; minimally-qualified # 🧛🏽‍♂ E5.0 man vampire: medium skin tone +1F9DB 1F3FE 200D 2642 FE0F ; fully-qualified # 🧛🏾‍♂️ E5.0 man vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2642 ; minimally-qualified # 🧛🏾‍♂ E5.0 man vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2642 FE0F ; fully-qualified # 🧛🏿‍♂️ E5.0 man vampire: dark skin tone +1F9DB 1F3FF 200D 2642 ; minimally-qualified # 🧛🏿‍♂ E5.0 man vampire: dark skin tone +1F9DB 200D 2640 FE0F ; fully-qualified # 🧛‍♀️ E5.0 woman vampire +1F9DB 200D 2640 ; minimally-qualified # 🧛‍♀ E5.0 woman vampire +1F9DB 1F3FB 200D 2640 FE0F ; fully-qualified # 🧛🏻‍♀️ E5.0 woman vampire: light skin tone +1F9DB 1F3FB 200D 2640 ; minimally-qualified # 🧛🏻‍♀ E5.0 woman vampire: light skin tone +1F9DB 1F3FC 200D 2640 FE0F ; fully-qualified # 🧛🏼‍♀️ E5.0 woman vampire: medium-light skin tone +1F9DB 1F3FC 200D 2640 ; minimally-qualified # 🧛🏼‍♀ E5.0 woman vampire: medium-light skin tone +1F9DB 1F3FD 200D 2640 FE0F ; fully-qualified # 🧛🏽‍♀️ E5.0 woman vampire: medium skin tone +1F9DB 1F3FD 200D 2640 ; minimally-qualified # 🧛🏽‍♀ E5.0 woman vampire: medium skin tone +1F9DB 1F3FE 200D 2640 FE0F ; fully-qualified # 🧛🏾‍♀️ E5.0 woman vampire: medium-dark skin tone +1F9DB 1F3FE 200D 2640 ; minimally-qualified # 🧛🏾‍♀ E5.0 woman vampire: medium-dark skin tone +1F9DB 1F3FF 200D 2640 FE0F ; fully-qualified # 🧛🏿‍♀️ E5.0 woman vampire: dark skin tone +1F9DB 1F3FF 200D 2640 ; minimally-qualified # 🧛🏿‍♀ E5.0 woman vampire: dark skin tone +1F9DC ; fully-qualified # 🧜 E5.0 merperson +1F9DC 1F3FB ; fully-qualified # 🧜🏻 E5.0 merperson: light skin tone +1F9DC 1F3FC ; fully-qualified # 🧜🏼 E5.0 merperson: medium-light skin tone +1F9DC 1F3FD ; fully-qualified # 🧜🏽 E5.0 merperson: medium skin tone +1F9DC 1F3FE ; fully-qualified # 🧜🏾 E5.0 merperson: medium-dark skin tone +1F9DC 1F3FF ; fully-qualified # 🧜🏿 E5.0 merperson: dark skin tone +1F9DC 200D 2642 FE0F ; fully-qualified # 🧜‍♂️ E5.0 merman +1F9DC 200D 2642 ; minimally-qualified # 🧜‍♂ E5.0 merman +1F9DC 1F3FB 200D 2642 FE0F ; fully-qualified # 🧜🏻‍♂️ E5.0 merman: light skin tone +1F9DC 1F3FB 200D 2642 ; minimally-qualified # 🧜🏻‍♂ E5.0 merman: light skin tone +1F9DC 1F3FC 200D 2642 FE0F ; fully-qualified # 🧜🏼‍♂️ E5.0 merman: medium-light skin tone +1F9DC 1F3FC 200D 2642 ; minimally-qualified # 🧜🏼‍♂ E5.0 merman: medium-light skin tone +1F9DC 1F3FD 200D 2642 FE0F ; fully-qualified # 🧜🏽‍♂️ E5.0 merman: medium skin tone +1F9DC 1F3FD 200D 2642 ; minimally-qualified # 🧜🏽‍♂ E5.0 merman: medium skin tone +1F9DC 1F3FE 200D 2642 FE0F ; fully-qualified # 🧜🏾‍♂️ E5.0 merman: medium-dark skin tone +1F9DC 1F3FE 200D 2642 ; minimally-qualified # 🧜🏾‍♂ E5.0 merman: medium-dark skin tone +1F9DC 1F3FF 200D 2642 FE0F ; fully-qualified # 🧜🏿‍♂️ E5.0 merman: dark skin tone +1F9DC 1F3FF 200D 2642 ; minimally-qualified # 🧜🏿‍♂ E5.0 merman: dark skin tone +1F9DC 200D 2640 FE0F ; fully-qualified # 🧜‍♀️ E5.0 mermaid +1F9DC 200D 2640 ; minimally-qualified # 🧜‍♀ E5.0 mermaid +1F9DC 1F3FB 200D 2640 FE0F ; fully-qualified # 🧜🏻‍♀️ E5.0 mermaid: light skin tone +1F9DC 1F3FB 200D 2640 ; minimally-qualified # 🧜🏻‍♀ E5.0 mermaid: light skin tone +1F9DC 1F3FC 200D 2640 FE0F ; fully-qualified # 🧜🏼‍♀️ E5.0 mermaid: medium-light skin tone +1F9DC 1F3FC 200D 2640 ; minimally-qualified # 🧜🏼‍♀ E5.0 mermaid: medium-light skin tone +1F9DC 1F3FD 200D 2640 FE0F ; fully-qualified # 🧜🏽‍♀️ E5.0 mermaid: medium skin tone +1F9DC 1F3FD 200D 2640 ; minimally-qualified # 🧜🏽‍♀ E5.0 mermaid: medium skin tone +1F9DC 1F3FE 200D 2640 FE0F ; fully-qualified # 🧜🏾‍♀️ E5.0 mermaid: medium-dark skin tone +1F9DC 1F3FE 200D 2640 ; minimally-qualified # 🧜🏾‍♀ E5.0 mermaid: medium-dark skin tone +1F9DC 1F3FF 200D 2640 FE0F ; fully-qualified # 🧜🏿‍♀️ E5.0 mermaid: dark skin tone +1F9DC 1F3FF 200D 2640 ; minimally-qualified # 🧜🏿‍♀ E5.0 mermaid: dark skin tone +1F9DD ; fully-qualified # 🧝 E5.0 elf +1F9DD 1F3FB ; fully-qualified # 🧝🏻 E5.0 elf: light skin tone +1F9DD 1F3FC ; fully-qualified # 🧝🏼 E5.0 elf: medium-light skin tone +1F9DD 1F3FD ; fully-qualified # 🧝🏽 E5.0 elf: medium skin tone +1F9DD 1F3FE ; fully-qualified # 🧝🏾 E5.0 elf: medium-dark skin tone +1F9DD 1F3FF ; fully-qualified # 🧝🏿 E5.0 elf: dark skin tone +1F9DD 200D 2642 FE0F ; fully-qualified # 🧝‍♂️ E5.0 man elf +1F9DD 200D 2642 ; minimally-qualified # 🧝‍♂ E5.0 man elf +1F9DD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧝🏻‍♂️ E5.0 man elf: light skin tone +1F9DD 1F3FB 200D 2642 ; minimally-qualified # 🧝🏻‍♂ E5.0 man elf: light skin tone +1F9DD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧝🏼‍♂️ E5.0 man elf: medium-light skin tone +1F9DD 1F3FC 200D 2642 ; minimally-qualified # 🧝🏼‍♂ E5.0 man elf: medium-light skin tone +1F9DD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧝🏽‍♂️ E5.0 man elf: medium skin tone +1F9DD 1F3FD 200D 2642 ; minimally-qualified # 🧝🏽‍♂ E5.0 man elf: medium skin tone +1F9DD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧝🏾‍♂️ E5.0 man elf: medium-dark skin tone +1F9DD 1F3FE 200D 2642 ; minimally-qualified # 🧝🏾‍♂ E5.0 man elf: medium-dark skin tone +1F9DD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧝🏿‍♂️ E5.0 man elf: dark skin tone +1F9DD 1F3FF 200D 2642 ; minimally-qualified # 🧝🏿‍♂ E5.0 man elf: dark skin tone +1F9DD 200D 2640 FE0F ; fully-qualified # 🧝‍♀️ E5.0 woman elf +1F9DD 200D 2640 ; minimally-qualified # 🧝‍♀ E5.0 woman elf +1F9DD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧝🏻‍♀️ E5.0 woman elf: light skin tone +1F9DD 1F3FB 200D 2640 ; minimally-qualified # 🧝🏻‍♀ E5.0 woman elf: light skin tone +1F9DD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧝🏼‍♀️ E5.0 woman elf: medium-light skin tone +1F9DD 1F3FC 200D 2640 ; minimally-qualified # 🧝🏼‍♀ E5.0 woman elf: medium-light skin tone +1F9DD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧝🏽‍♀️ E5.0 woman elf: medium skin tone +1F9DD 1F3FD 200D 2640 ; minimally-qualified # 🧝🏽‍♀ E5.0 woman elf: medium skin tone +1F9DD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧝🏾‍♀️ E5.0 woman elf: medium-dark skin tone +1F9DD 1F3FE 200D 2640 ; minimally-qualified # 🧝🏾‍♀ E5.0 woman elf: medium-dark skin tone +1F9DD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧝🏿‍♀️ E5.0 woman elf: dark skin tone +1F9DD 1F3FF 200D 2640 ; minimally-qualified # 🧝🏿‍♀ E5.0 woman elf: dark skin tone +1F9DE ; fully-qualified # 🧞 E5.0 genie +1F9DE 200D 2642 FE0F ; fully-qualified # 🧞‍♂️ E5.0 man genie +1F9DE 200D 2642 ; minimally-qualified # 🧞‍♂ E5.0 man genie +1F9DE 200D 2640 FE0F ; fully-qualified # 🧞‍♀️ E5.0 woman genie +1F9DE 200D 2640 ; minimally-qualified # 🧞‍♀ E5.0 woman genie +1F9DF ; fully-qualified # 🧟 E5.0 zombie +1F9DF 200D 2642 FE0F ; fully-qualified # 🧟‍♂️ E5.0 man zombie +1F9DF 200D 2642 ; minimally-qualified # 🧟‍♂ E5.0 man zombie +1F9DF 200D 2640 FE0F ; fully-qualified # 🧟‍♀️ E5.0 woman zombie +1F9DF 200D 2640 ; minimally-qualified # 🧟‍♀ E5.0 woman zombie + +# subgroup: person-activity +1F486 ; fully-qualified # 💆 E0.6 person getting massage +1F486 1F3FB ; fully-qualified # 💆🏻 E1.0 person getting massage: light skin tone +1F486 1F3FC ; fully-qualified # 💆🏼 E1.0 person getting massage: medium-light skin tone +1F486 1F3FD ; fully-qualified # 💆🏽 E1.0 person getting massage: medium skin tone +1F486 1F3FE ; fully-qualified # 💆🏾 E1.0 person getting massage: medium-dark skin tone +1F486 1F3FF ; fully-qualified # 💆🏿 E1.0 person getting massage: dark skin tone +1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ E4.0 man getting massage +1F486 200D 2642 ; minimally-qualified # 💆‍♂ E4.0 man getting massage +1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ E4.0 man getting massage: light skin tone +1F486 1F3FB 200D 2642 ; minimally-qualified # 💆🏻‍♂ E4.0 man getting massage: light skin tone +1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ E4.0 man getting massage: medium-light skin tone +1F486 1F3FC 200D 2642 ; minimally-qualified # 💆🏼‍♂ E4.0 man getting massage: medium-light skin tone +1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ E4.0 man getting massage: medium skin tone +1F486 1F3FD 200D 2642 ; minimally-qualified # 💆🏽‍♂ E4.0 man getting massage: medium skin tone +1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ E4.0 man getting massage: medium-dark skin tone +1F486 1F3FE 200D 2642 ; minimally-qualified # 💆🏾‍♂ E4.0 man getting massage: medium-dark skin tone +1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ E4.0 man getting massage: dark skin tone +1F486 1F3FF 200D 2642 ; minimally-qualified # 💆🏿‍♂ E4.0 man getting massage: dark skin tone +1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ E4.0 woman getting massage +1F486 200D 2640 ; minimally-qualified # 💆‍♀ E4.0 woman getting massage +1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ E4.0 woman getting massage: light skin tone +1F486 1F3FB 200D 2640 ; minimally-qualified # 💆🏻‍♀ E4.0 woman getting massage: light skin tone +1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ E4.0 woman getting massage: medium-light skin tone +1F486 1F3FC 200D 2640 ; minimally-qualified # 💆🏼‍♀ E4.0 woman getting massage: medium-light skin tone +1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ E4.0 woman getting massage: medium skin tone +1F486 1F3FD 200D 2640 ; minimally-qualified # 💆🏽‍♀ E4.0 woman getting massage: medium skin tone +1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ E4.0 woman getting massage: medium-dark skin tone +1F486 1F3FE 200D 2640 ; minimally-qualified # 💆🏾‍♀ E4.0 woman getting massage: medium-dark skin tone +1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ E4.0 woman getting massage: dark skin tone +1F486 1F3FF 200D 2640 ; minimally-qualified # 💆🏿‍♀ E4.0 woman getting massage: dark skin tone +1F487 ; fully-qualified # 💇 E0.6 person getting haircut +1F487 1F3FB ; fully-qualified # 💇🏻 E1.0 person getting haircut: light skin tone +1F487 1F3FC ; fully-qualified # 💇🏼 E1.0 person getting haircut: medium-light skin tone +1F487 1F3FD ; fully-qualified # 💇🏽 E1.0 person getting haircut: medium skin tone +1F487 1F3FE ; fully-qualified # 💇🏾 E1.0 person getting haircut: medium-dark skin tone +1F487 1F3FF ; fully-qualified # 💇🏿 E1.0 person getting haircut: dark skin tone +1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ E4.0 man getting haircut +1F487 200D 2642 ; minimally-qualified # 💇‍♂ E4.0 man getting haircut +1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ E4.0 man getting haircut: light skin tone +1F487 1F3FB 200D 2642 ; minimally-qualified # 💇🏻‍♂ E4.0 man getting haircut: light skin tone +1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ E4.0 man getting haircut: medium-light skin tone +1F487 1F3FC 200D 2642 ; minimally-qualified # 💇🏼‍♂ E4.0 man getting haircut: medium-light skin tone +1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ E4.0 man getting haircut: medium skin tone +1F487 1F3FD 200D 2642 ; minimally-qualified # 💇🏽‍♂ E4.0 man getting haircut: medium skin tone +1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ E4.0 man getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2642 ; minimally-qualified # 💇🏾‍♂ E4.0 man getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ E4.0 man getting haircut: dark skin tone +1F487 1F3FF 200D 2642 ; minimally-qualified # 💇🏿‍♂ E4.0 man getting haircut: dark skin tone +1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ E4.0 woman getting haircut +1F487 200D 2640 ; minimally-qualified # 💇‍♀ E4.0 woman getting haircut +1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ E4.0 woman getting haircut: light skin tone +1F487 1F3FB 200D 2640 ; minimally-qualified # 💇🏻‍♀ E4.0 woman getting haircut: light skin tone +1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ E4.0 woman getting haircut: medium-light skin tone +1F487 1F3FC 200D 2640 ; minimally-qualified # 💇🏼‍♀ E4.0 woman getting haircut: medium-light skin tone +1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ E4.0 woman getting haircut: medium skin tone +1F487 1F3FD 200D 2640 ; minimally-qualified # 💇🏽‍♀ E4.0 woman getting haircut: medium skin tone +1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ E4.0 woman getting haircut: medium-dark skin tone +1F487 1F3FE 200D 2640 ; minimally-qualified # 💇🏾‍♀ E4.0 woman getting haircut: medium-dark skin tone +1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ E4.0 woman getting haircut: dark skin tone +1F487 1F3FF 200D 2640 ; minimally-qualified # 💇🏿‍♀ E4.0 woman getting haircut: dark skin tone +1F6B6 ; fully-qualified # 🚶 E0.6 person walking +1F6B6 1F3FB ; fully-qualified # 🚶🏻 E1.0 person walking: light skin tone +1F6B6 1F3FC ; fully-qualified # 🚶🏼 E1.0 person walking: medium-light skin tone +1F6B6 1F3FD ; fully-qualified # 🚶🏽 E1.0 person walking: medium skin tone +1F6B6 1F3FE ; fully-qualified # 🚶🏾 E1.0 person walking: medium-dark skin tone +1F6B6 1F3FF ; fully-qualified # 🚶🏿 E1.0 person walking: dark skin tone +1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ E4.0 man walking +1F6B6 200D 2642 ; minimally-qualified # 🚶‍♂ E4.0 man walking +1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ E4.0 man walking: light skin tone +1F6B6 1F3FB 200D 2642 ; minimally-qualified # 🚶🏻‍♂ E4.0 man walking: light skin tone +1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ E4.0 man walking: medium-light skin tone +1F6B6 1F3FC 200D 2642 ; minimally-qualified # 🚶🏼‍♂ E4.0 man walking: medium-light skin tone +1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ E4.0 man walking: medium skin tone +1F6B6 1F3FD 200D 2642 ; minimally-qualified # 🚶🏽‍♂ E4.0 man walking: medium skin tone +1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ E4.0 man walking: medium-dark skin tone +1F6B6 1F3FE 200D 2642 ; minimally-qualified # 🚶🏾‍♂ E4.0 man walking: medium-dark skin tone +1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ E4.0 man walking: dark skin tone +1F6B6 1F3FF 200D 2642 ; minimally-qualified # 🚶🏿‍♂ E4.0 man walking: dark skin tone +1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ E4.0 woman walking +1F6B6 200D 2640 ; minimally-qualified # 🚶‍♀ E4.0 woman walking +1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ E4.0 woman walking: light skin tone +1F6B6 1F3FB 200D 2640 ; minimally-qualified # 🚶🏻‍♀ E4.0 woman walking: light skin tone +1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ E4.0 woman walking: medium-light skin tone +1F6B6 1F3FC 200D 2640 ; minimally-qualified # 🚶🏼‍♀ E4.0 woman walking: medium-light skin tone +1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ E4.0 woman walking: medium skin tone +1F6B6 1F3FD 200D 2640 ; minimally-qualified # 🚶🏽‍♀ E4.0 woman walking: medium skin tone +1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ E4.0 woman walking: medium-dark skin tone +1F6B6 1F3FE 200D 2640 ; minimally-qualified # 🚶🏾‍♀ E4.0 woman walking: medium-dark skin tone +1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ E4.0 woman walking: dark skin tone +1F6B6 1F3FF 200D 2640 ; minimally-qualified # 🚶🏿‍♀ E4.0 woman walking: dark skin tone +1F9CD ; fully-qualified # 🧍 E12.0 person standing +1F9CD 1F3FB ; fully-qualified # 🧍🏻 E12.0 person standing: light skin tone +1F9CD 1F3FC ; fully-qualified # 🧍🏼 E12.0 person standing: medium-light skin tone +1F9CD 1F3FD ; fully-qualified # 🧍🏽 E12.0 person standing: medium skin tone +1F9CD 1F3FE ; fully-qualified # 🧍🏾 E12.0 person standing: medium-dark skin tone +1F9CD 1F3FF ; fully-qualified # 🧍🏿 E12.0 person standing: dark skin tone +1F9CD 200D 2642 FE0F ; fully-qualified # 🧍‍♂️ E12.0 man standing +1F9CD 200D 2642 ; minimally-qualified # 🧍‍♂ E12.0 man standing +1F9CD 1F3FB 200D 2642 FE0F ; fully-qualified # 🧍🏻‍♂️ E12.0 man standing: light skin tone +1F9CD 1F3FB 200D 2642 ; minimally-qualified # 🧍🏻‍♂ E12.0 man standing: light skin tone +1F9CD 1F3FC 200D 2642 FE0F ; fully-qualified # 🧍🏼‍♂️ E12.0 man standing: medium-light skin tone +1F9CD 1F3FC 200D 2642 ; minimally-qualified # 🧍🏼‍♂ E12.0 man standing: medium-light skin tone +1F9CD 1F3FD 200D 2642 FE0F ; fully-qualified # 🧍🏽‍♂️ E12.0 man standing: medium skin tone +1F9CD 1F3FD 200D 2642 ; minimally-qualified # 🧍🏽‍♂ E12.0 man standing: medium skin tone +1F9CD 1F3FE 200D 2642 FE0F ; fully-qualified # 🧍🏾‍♂️ E12.0 man standing: medium-dark skin tone +1F9CD 1F3FE 200D 2642 ; minimally-qualified # 🧍🏾‍♂ E12.0 man standing: medium-dark skin tone +1F9CD 1F3FF 200D 2642 FE0F ; fully-qualified # 🧍🏿‍♂️ E12.0 man standing: dark skin tone +1F9CD 1F3FF 200D 2642 ; minimally-qualified # 🧍🏿‍♂ E12.0 man standing: dark skin tone +1F9CD 200D 2640 FE0F ; fully-qualified # 🧍‍♀️ E12.0 woman standing +1F9CD 200D 2640 ; minimally-qualified # 🧍‍♀ E12.0 woman standing +1F9CD 1F3FB 200D 2640 FE0F ; fully-qualified # 🧍🏻‍♀️ E12.0 woman standing: light skin tone +1F9CD 1F3FB 200D 2640 ; minimally-qualified # 🧍🏻‍♀ E12.0 woman standing: light skin tone +1F9CD 1F3FC 200D 2640 FE0F ; fully-qualified # 🧍🏼‍♀️ E12.0 woman standing: medium-light skin tone +1F9CD 1F3FC 200D 2640 ; minimally-qualified # 🧍🏼‍♀ E12.0 woman standing: medium-light skin tone +1F9CD 1F3FD 200D 2640 FE0F ; fully-qualified # 🧍🏽‍♀️ E12.0 woman standing: medium skin tone +1F9CD 1F3FD 200D 2640 ; minimally-qualified # 🧍🏽‍♀ E12.0 woman standing: medium skin tone +1F9CD 1F3FE 200D 2640 FE0F ; fully-qualified # 🧍🏾‍♀️ E12.0 woman standing: medium-dark skin tone +1F9CD 1F3FE 200D 2640 ; minimally-qualified # 🧍🏾‍♀ E12.0 woman standing: medium-dark skin tone +1F9CD 1F3FF 200D 2640 FE0F ; fully-qualified # 🧍🏿‍♀️ E12.0 woman standing: dark skin tone +1F9CD 1F3FF 200D 2640 ; minimally-qualified # 🧍🏿‍♀ E12.0 woman standing: dark skin tone +1F9CE ; fully-qualified # 🧎 E12.0 person kneeling +1F9CE 1F3FB ; fully-qualified # 🧎🏻 E12.0 person kneeling: light skin tone +1F9CE 1F3FC ; fully-qualified # 🧎🏼 E12.0 person kneeling: medium-light skin tone +1F9CE 1F3FD ; fully-qualified # 🧎🏽 E12.0 person kneeling: medium skin tone +1F9CE 1F3FE ; fully-qualified # 🧎🏾 E12.0 person kneeling: medium-dark skin tone +1F9CE 1F3FF ; fully-qualified # 🧎🏿 E12.0 person kneeling: dark skin tone +1F9CE 200D 2642 FE0F ; fully-qualified # 🧎‍♂️ E12.0 man kneeling +1F9CE 200D 2642 ; minimally-qualified # 🧎‍♂ E12.0 man kneeling +1F9CE 1F3FB 200D 2642 FE0F ; fully-qualified # 🧎🏻‍♂️ E12.0 man kneeling: light skin tone +1F9CE 1F3FB 200D 2642 ; minimally-qualified # 🧎🏻‍♂ E12.0 man kneeling: light skin tone +1F9CE 1F3FC 200D 2642 FE0F ; fully-qualified # 🧎🏼‍♂️ E12.0 man kneeling: medium-light skin tone +1F9CE 1F3FC 200D 2642 ; minimally-qualified # 🧎🏼‍♂ E12.0 man kneeling: medium-light skin tone +1F9CE 1F3FD 200D 2642 FE0F ; fully-qualified # 🧎🏽‍♂️ E12.0 man kneeling: medium skin tone +1F9CE 1F3FD 200D 2642 ; minimally-qualified # 🧎🏽‍♂ E12.0 man kneeling: medium skin tone +1F9CE 1F3FE 200D 2642 FE0F ; fully-qualified # 🧎🏾‍♂️ E12.0 man kneeling: medium-dark skin tone +1F9CE 1F3FE 200D 2642 ; minimally-qualified # 🧎🏾‍♂ E12.0 man kneeling: medium-dark skin tone +1F9CE 1F3FF 200D 2642 FE0F ; fully-qualified # 🧎🏿‍♂️ E12.0 man kneeling: dark skin tone +1F9CE 1F3FF 200D 2642 ; minimally-qualified # 🧎🏿‍♂ E12.0 man kneeling: dark skin tone +1F9CE 200D 2640 FE0F ; fully-qualified # 🧎‍♀️ E12.0 woman kneeling +1F9CE 200D 2640 ; minimally-qualified # 🧎‍♀ E12.0 woman kneeling +1F9CE 1F3FB 200D 2640 FE0F ; fully-qualified # 🧎🏻‍♀️ E12.0 woman kneeling: light skin tone +1F9CE 1F3FB 200D 2640 ; minimally-qualified # 🧎🏻‍♀ E12.0 woman kneeling: light skin tone +1F9CE 1F3FC 200D 2640 FE0F ; fully-qualified # 🧎🏼‍♀️ E12.0 woman kneeling: medium-light skin tone +1F9CE 1F3FC 200D 2640 ; minimally-qualified # 🧎🏼‍♀ E12.0 woman kneeling: medium-light skin tone +1F9CE 1F3FD 200D 2640 FE0F ; fully-qualified # 🧎🏽‍♀️ E12.0 woman kneeling: medium skin tone +1F9CE 1F3FD 200D 2640 ; minimally-qualified # 🧎🏽‍♀ E12.0 woman kneeling: medium skin tone +1F9CE 1F3FE 200D 2640 FE0F ; fully-qualified # 🧎🏾‍♀️ E12.0 woman kneeling: medium-dark skin tone +1F9CE 1F3FE 200D 2640 ; minimally-qualified # 🧎🏾‍♀ E12.0 woman kneeling: medium-dark skin tone +1F9CE 1F3FF 200D 2640 FE0F ; fully-qualified # 🧎🏿‍♀️ E12.0 woman kneeling: dark skin tone +1F9CE 1F3FF 200D 2640 ; minimally-qualified # 🧎🏿‍♀ E12.0 woman kneeling: dark skin tone +1F9D1 200D 1F9AF ; fully-qualified # 🧑‍🦯 E12.1 person with white cane +1F9D1 1F3FB 200D 1F9AF ; fully-qualified # 🧑🏻‍🦯 E12.1 person with white cane: light skin tone +1F9D1 1F3FC 200D 1F9AF ; fully-qualified # 🧑🏼‍🦯 E12.1 person with white cane: medium-light skin tone +1F9D1 1F3FD 200D 1F9AF ; fully-qualified # 🧑🏽‍🦯 E12.1 person with white cane: medium skin tone +1F9D1 1F3FE 200D 1F9AF ; fully-qualified # 🧑🏾‍🦯 E12.1 person with white cane: medium-dark skin tone +1F9D1 1F3FF 200D 1F9AF ; fully-qualified # 🧑🏿‍🦯 E12.1 person with white cane: dark skin tone +1F468 200D 1F9AF ; fully-qualified # 👨‍🦯 E12.0 man with white cane +1F468 1F3FB 200D 1F9AF ; fully-qualified # 👨🏻‍🦯 E12.0 man with white cane: light skin tone +1F468 1F3FC 200D 1F9AF ; fully-qualified # 👨🏼‍🦯 E12.0 man with white cane: medium-light skin tone +1F468 1F3FD 200D 1F9AF ; fully-qualified # 👨🏽‍🦯 E12.0 man with white cane: medium skin tone +1F468 1F3FE 200D 1F9AF ; fully-qualified # 👨🏾‍🦯 E12.0 man with white cane: medium-dark skin tone +1F468 1F3FF 200D 1F9AF ; fully-qualified # 👨🏿‍🦯 E12.0 man with white cane: dark skin tone +1F469 200D 1F9AF ; fully-qualified # 👩‍🦯 E12.0 woman with white cane +1F469 1F3FB 200D 1F9AF ; fully-qualified # 👩🏻‍🦯 E12.0 woman with white cane: light skin tone +1F469 1F3FC 200D 1F9AF ; fully-qualified # 👩🏼‍🦯 E12.0 woman with white cane: medium-light skin tone +1F469 1F3FD 200D 1F9AF ; fully-qualified # 👩🏽‍🦯 E12.0 woman with white cane: medium skin tone +1F469 1F3FE 200D 1F9AF ; fully-qualified # 👩🏾‍🦯 E12.0 woman with white cane: medium-dark skin tone +1F469 1F3FF 200D 1F9AF ; fully-qualified # 👩🏿‍🦯 E12.0 woman with white cane: dark skin tone +1F9D1 200D 1F9BC ; fully-qualified # 🧑‍🦼 E12.1 person in motorized wheelchair +1F9D1 1F3FB 200D 1F9BC ; fully-qualified # 🧑🏻‍🦼 E12.1 person in motorized wheelchair: light skin tone +1F9D1 1F3FC 200D 1F9BC ; fully-qualified # 🧑🏼‍🦼 E12.1 person in motorized wheelchair: medium-light skin tone +1F9D1 1F3FD 200D 1F9BC ; fully-qualified # 🧑🏽‍🦼 E12.1 person in motorized wheelchair: medium skin tone +1F9D1 1F3FE 200D 1F9BC ; fully-qualified # 🧑🏾‍🦼 E12.1 person in motorized wheelchair: medium-dark skin tone +1F9D1 1F3FF 200D 1F9BC ; fully-qualified # 🧑🏿‍🦼 E12.1 person in motorized wheelchair: dark skin tone +1F468 200D 1F9BC ; fully-qualified # 👨‍🦼 E12.0 man in motorized wheelchair +1F468 1F3FB 200D 1F9BC ; fully-qualified # 👨🏻‍🦼 E12.0 man in motorized wheelchair: light skin tone +1F468 1F3FC 200D 1F9BC ; fully-qualified # 👨🏼‍🦼 E12.0 man in motorized wheelchair: medium-light skin tone +1F468 1F3FD 200D 1F9BC ; fully-qualified # 👨🏽‍🦼 E12.0 man in motorized wheelchair: medium skin tone +1F468 1F3FE 200D 1F9BC ; fully-qualified # 👨🏾‍🦼 E12.0 man in motorized wheelchair: medium-dark skin tone +1F468 1F3FF 200D 1F9BC ; fully-qualified # 👨🏿‍🦼 E12.0 man in motorized wheelchair: dark skin tone +1F469 200D 1F9BC ; fully-qualified # 👩‍🦼 E12.0 woman in motorized wheelchair +1F469 1F3FB 200D 1F9BC ; fully-qualified # 👩🏻‍🦼 E12.0 woman in motorized wheelchair: light skin tone +1F469 1F3FC 200D 1F9BC ; fully-qualified # 👩🏼‍🦼 E12.0 woman in motorized wheelchair: medium-light skin tone +1F469 1F3FD 200D 1F9BC ; fully-qualified # 👩🏽‍🦼 E12.0 woman in motorized wheelchair: medium skin tone +1F469 1F3FE 200D 1F9BC ; fully-qualified # 👩🏾‍🦼 E12.0 woman in motorized wheelchair: medium-dark skin tone +1F469 1F3FF 200D 1F9BC ; fully-qualified # 👩🏿‍🦼 E12.0 woman in motorized wheelchair: dark skin tone +1F9D1 200D 1F9BD ; fully-qualified # 🧑‍🦽 E12.1 person in manual wheelchair +1F9D1 1F3FB 200D 1F9BD ; fully-qualified # 🧑🏻‍🦽 E12.1 person in manual wheelchair: light skin tone +1F9D1 1F3FC 200D 1F9BD ; fully-qualified # 🧑🏼‍🦽 E12.1 person in manual wheelchair: medium-light skin tone +1F9D1 1F3FD 200D 1F9BD ; fully-qualified # 🧑🏽‍🦽 E12.1 person in manual wheelchair: medium skin tone +1F9D1 1F3FE 200D 1F9BD ; fully-qualified # 🧑🏾‍🦽 E12.1 person in manual wheelchair: medium-dark skin tone +1F9D1 1F3FF 200D 1F9BD ; fully-qualified # 🧑🏿‍🦽 E12.1 person in manual wheelchair: dark skin tone +1F468 200D 1F9BD ; fully-qualified # 👨‍🦽 E12.0 man in manual wheelchair +1F468 1F3FB 200D 1F9BD ; fully-qualified # 👨🏻‍🦽 E12.0 man in manual wheelchair: light skin tone +1F468 1F3FC 200D 1F9BD ; fully-qualified # 👨🏼‍🦽 E12.0 man in manual wheelchair: medium-light skin tone +1F468 1F3FD 200D 1F9BD ; fully-qualified # 👨🏽‍🦽 E12.0 man in manual wheelchair: medium skin tone +1F468 1F3FE 200D 1F9BD ; fully-qualified # 👨🏾‍🦽 E12.0 man in manual wheelchair: medium-dark skin tone +1F468 1F3FF 200D 1F9BD ; fully-qualified # 👨🏿‍🦽 E12.0 man in manual wheelchair: dark skin tone +1F469 200D 1F9BD ; fully-qualified # 👩‍🦽 E12.0 woman in manual wheelchair +1F469 1F3FB 200D 1F9BD ; fully-qualified # 👩🏻‍🦽 E12.0 woman in manual wheelchair: light skin tone +1F469 1F3FC 200D 1F9BD ; fully-qualified # 👩🏼‍🦽 E12.0 woman in manual wheelchair: medium-light skin tone +1F469 1F3FD 200D 1F9BD ; fully-qualified # 👩🏽‍🦽 E12.0 woman in manual wheelchair: medium skin tone +1F469 1F3FE 200D 1F9BD ; fully-qualified # 👩🏾‍🦽 E12.0 woman in manual wheelchair: medium-dark skin tone +1F469 1F3FF 200D 1F9BD ; fully-qualified # 👩🏿‍🦽 E12.0 woman in manual wheelchair: dark skin tone +1F3C3 ; fully-qualified # 🏃 E0.6 person running +1F3C3 1F3FB ; fully-qualified # 🏃🏻 E1.0 person running: light skin tone +1F3C3 1F3FC ; fully-qualified # 🏃🏼 E1.0 person running: medium-light skin tone +1F3C3 1F3FD ; fully-qualified # 🏃🏽 E1.0 person running: medium skin tone +1F3C3 1F3FE ; fully-qualified # 🏃🏾 E1.0 person running: medium-dark skin tone +1F3C3 1F3FF ; fully-qualified # 🏃🏿 E1.0 person running: dark skin tone +1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ E4.0 man running +1F3C3 200D 2642 ; minimally-qualified # 🏃‍♂ E4.0 man running +1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ E4.0 man running: light skin tone +1F3C3 1F3FB 200D 2642 ; minimally-qualified # 🏃🏻‍♂ E4.0 man running: light skin tone +1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ E4.0 man running: medium-light skin tone +1F3C3 1F3FC 200D 2642 ; minimally-qualified # 🏃🏼‍♂ E4.0 man running: medium-light skin tone +1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ E4.0 man running: medium skin tone +1F3C3 1F3FD 200D 2642 ; minimally-qualified # 🏃🏽‍♂ E4.0 man running: medium skin tone +1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ E4.0 man running: medium-dark skin tone +1F3C3 1F3FE 200D 2642 ; minimally-qualified # 🏃🏾‍♂ E4.0 man running: medium-dark skin tone +1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ E4.0 man running: dark skin tone +1F3C3 1F3FF 200D 2642 ; minimally-qualified # 🏃🏿‍♂ E4.0 man running: dark skin tone +1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ E4.0 woman running +1F3C3 200D 2640 ; minimally-qualified # 🏃‍♀ E4.0 woman running +1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ E4.0 woman running: light skin tone +1F3C3 1F3FB 200D 2640 ; minimally-qualified # 🏃🏻‍♀ E4.0 woman running: light skin tone +1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ E4.0 woman running: medium-light skin tone +1F3C3 1F3FC 200D 2640 ; minimally-qualified # 🏃🏼‍♀ E4.0 woman running: medium-light skin tone +1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ E4.0 woman running: medium skin tone +1F3C3 1F3FD 200D 2640 ; minimally-qualified # 🏃🏽‍♀ E4.0 woman running: medium skin tone +1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ E4.0 woman running: medium-dark skin tone +1F3C3 1F3FE 200D 2640 ; minimally-qualified # 🏃🏾‍♀ E4.0 woman running: medium-dark skin tone +1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ E4.0 woman running: dark skin tone +1F3C3 1F3FF 200D 2640 ; minimally-qualified # 🏃🏿‍♀ E4.0 woman running: dark skin tone +1F483 ; fully-qualified # 💃 E0.6 woman dancing +1F483 1F3FB ; fully-qualified # 💃🏻 E1.0 woman dancing: light skin tone +1F483 1F3FC ; fully-qualified # 💃🏼 E1.0 woman dancing: medium-light skin tone +1F483 1F3FD ; fully-qualified # 💃🏽 E1.0 woman dancing: medium skin tone +1F483 1F3FE ; fully-qualified # 💃🏾 E1.0 woman dancing: medium-dark skin tone +1F483 1F3FF ; fully-qualified # 💃🏿 E1.0 woman dancing: dark skin tone +1F57A ; fully-qualified # 🕺 E3.0 man dancing +1F57A 1F3FB ; fully-qualified # 🕺🏻 E3.0 man dancing: light skin tone +1F57A 1F3FC ; fully-qualified # 🕺🏼 E3.0 man dancing: medium-light skin tone +1F57A 1F3FD ; fully-qualified # 🕺🏽 E3.0 man dancing: medium skin tone +1F57A 1F3FE ; fully-qualified # 🕺🏾 E3.0 man dancing: medium-dark skin tone +1F57A 1F3FF ; fully-qualified # 🕺🏿 E3.0 man dancing: dark skin tone +1F574 FE0F ; fully-qualified # 🕴️ E0.7 person in suit levitating +1F574 ; unqualified # 🕴 E0.7 person in suit levitating +1F574 1F3FB ; fully-qualified # 🕴🏻 E4.0 person in suit levitating: light skin tone +1F574 1F3FC ; fully-qualified # 🕴🏼 E4.0 person in suit levitating: medium-light skin tone +1F574 1F3FD ; fully-qualified # 🕴🏽 E4.0 person in suit levitating: medium skin tone +1F574 1F3FE ; fully-qualified # 🕴🏾 E4.0 person in suit levitating: medium-dark skin tone +1F574 1F3FF ; fully-qualified # 🕴🏿 E4.0 person in suit levitating: dark skin tone +1F46F ; fully-qualified # 👯 E0.6 people with bunny ears +1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ E4.0 men with bunny ears +1F46F 200D 2642 ; minimally-qualified # 👯‍♂ E4.0 men with bunny ears +1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ E4.0 women with bunny ears +1F46F 200D 2640 ; minimally-qualified # 👯‍♀ E4.0 women with bunny ears +1F9D6 ; fully-qualified # 🧖 E5.0 person in steamy room +1F9D6 1F3FB ; fully-qualified # 🧖🏻 E5.0 person in steamy room: light skin tone +1F9D6 1F3FC ; fully-qualified # 🧖🏼 E5.0 person in steamy room: medium-light skin tone +1F9D6 1F3FD ; fully-qualified # 🧖🏽 E5.0 person in steamy room: medium skin tone +1F9D6 1F3FE ; fully-qualified # 🧖🏾 E5.0 person in steamy room: medium-dark skin tone +1F9D6 1F3FF ; fully-qualified # 🧖🏿 E5.0 person in steamy room: dark skin tone +1F9D6 200D 2642 FE0F ; fully-qualified # 🧖‍♂️ E5.0 man in steamy room +1F9D6 200D 2642 ; minimally-qualified # 🧖‍♂ E5.0 man in steamy room +1F9D6 1F3FB 200D 2642 FE0F ; fully-qualified # 🧖🏻‍♂️ E5.0 man in steamy room: light skin tone +1F9D6 1F3FB 200D 2642 ; minimally-qualified # 🧖🏻‍♂ E5.0 man in steamy room: light skin tone +1F9D6 1F3FC 200D 2642 FE0F ; fully-qualified # 🧖🏼‍♂️ E5.0 man in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2642 ; minimally-qualified # 🧖🏼‍♂ E5.0 man in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2642 FE0F ; fully-qualified # 🧖🏽‍♂️ E5.0 man in steamy room: medium skin tone +1F9D6 1F3FD 200D 2642 ; minimally-qualified # 🧖🏽‍♂ E5.0 man in steamy room: medium skin tone +1F9D6 1F3FE 200D 2642 FE0F ; fully-qualified # 🧖🏾‍♂️ E5.0 man in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2642 ; minimally-qualified # 🧖🏾‍♂ E5.0 man in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2642 FE0F ; fully-qualified # 🧖🏿‍♂️ E5.0 man in steamy room: dark skin tone +1F9D6 1F3FF 200D 2642 ; minimally-qualified # 🧖🏿‍♂ E5.0 man in steamy room: dark skin tone +1F9D6 200D 2640 FE0F ; fully-qualified # 🧖‍♀️ E5.0 woman in steamy room +1F9D6 200D 2640 ; minimally-qualified # 🧖‍♀ E5.0 woman in steamy room +1F9D6 1F3FB 200D 2640 FE0F ; fully-qualified # 🧖🏻‍♀️ E5.0 woman in steamy room: light skin tone +1F9D6 1F3FB 200D 2640 ; minimally-qualified # 🧖🏻‍♀ E5.0 woman in steamy room: light skin tone +1F9D6 1F3FC 200D 2640 FE0F ; fully-qualified # 🧖🏼‍♀️ E5.0 woman in steamy room: medium-light skin tone +1F9D6 1F3FC 200D 2640 ; minimally-qualified # 🧖🏼‍♀ E5.0 woman in steamy room: medium-light skin tone +1F9D6 1F3FD 200D 2640 FE0F ; fully-qualified # 🧖🏽‍♀️ E5.0 woman in steamy room: medium skin tone +1F9D6 1F3FD 200D 2640 ; minimally-qualified # 🧖🏽‍♀ E5.0 woman in steamy room: medium skin tone +1F9D6 1F3FE 200D 2640 FE0F ; fully-qualified # 🧖🏾‍♀️ E5.0 woman in steamy room: medium-dark skin tone +1F9D6 1F3FE 200D 2640 ; minimally-qualified # 🧖🏾‍♀ E5.0 woman in steamy room: medium-dark skin tone +1F9D6 1F3FF 200D 2640 FE0F ; fully-qualified # 🧖🏿‍♀️ E5.0 woman in steamy room: dark skin tone +1F9D6 1F3FF 200D 2640 ; minimally-qualified # 🧖🏿‍♀ E5.0 woman in steamy room: dark skin tone +1F9D7 ; fully-qualified # 🧗 E5.0 person climbing +1F9D7 1F3FB ; fully-qualified # 🧗🏻 E5.0 person climbing: light skin tone +1F9D7 1F3FC ; fully-qualified # 🧗🏼 E5.0 person climbing: medium-light skin tone +1F9D7 1F3FD ; fully-qualified # 🧗🏽 E5.0 person climbing: medium skin tone +1F9D7 1F3FE ; fully-qualified # 🧗🏾 E5.0 person climbing: medium-dark skin tone +1F9D7 1F3FF ; fully-qualified # 🧗🏿 E5.0 person climbing: dark skin tone +1F9D7 200D 2642 FE0F ; fully-qualified # 🧗‍♂️ E5.0 man climbing +1F9D7 200D 2642 ; minimally-qualified # 🧗‍♂ E5.0 man climbing +1F9D7 1F3FB 200D 2642 FE0F ; fully-qualified # 🧗🏻‍♂️ E5.0 man climbing: light skin tone +1F9D7 1F3FB 200D 2642 ; minimally-qualified # 🧗🏻‍♂ E5.0 man climbing: light skin tone +1F9D7 1F3FC 200D 2642 FE0F ; fully-qualified # 🧗🏼‍♂️ E5.0 man climbing: medium-light skin tone +1F9D7 1F3FC 200D 2642 ; minimally-qualified # 🧗🏼‍♂ E5.0 man climbing: medium-light skin tone +1F9D7 1F3FD 200D 2642 FE0F ; fully-qualified # 🧗🏽‍♂️ E5.0 man climbing: medium skin tone +1F9D7 1F3FD 200D 2642 ; minimally-qualified # 🧗🏽‍♂ E5.0 man climbing: medium skin tone +1F9D7 1F3FE 200D 2642 FE0F ; fully-qualified # 🧗🏾‍♂️ E5.0 man climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2642 ; minimally-qualified # 🧗🏾‍♂ E5.0 man climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2642 FE0F ; fully-qualified # 🧗🏿‍♂️ E5.0 man climbing: dark skin tone +1F9D7 1F3FF 200D 2642 ; minimally-qualified # 🧗🏿‍♂ E5.0 man climbing: dark skin tone +1F9D7 200D 2640 FE0F ; fully-qualified # 🧗‍♀️ E5.0 woman climbing +1F9D7 200D 2640 ; minimally-qualified # 🧗‍♀ E5.0 woman climbing +1F9D7 1F3FB 200D 2640 FE0F ; fully-qualified # 🧗🏻‍♀️ E5.0 woman climbing: light skin tone +1F9D7 1F3FB 200D 2640 ; minimally-qualified # 🧗🏻‍♀ E5.0 woman climbing: light skin tone +1F9D7 1F3FC 200D 2640 FE0F ; fully-qualified # 🧗🏼‍♀️ E5.0 woman climbing: medium-light skin tone +1F9D7 1F3FC 200D 2640 ; minimally-qualified # 🧗🏼‍♀ E5.0 woman climbing: medium-light skin tone +1F9D7 1F3FD 200D 2640 FE0F ; fully-qualified # 🧗🏽‍♀️ E5.0 woman climbing: medium skin tone +1F9D7 1F3FD 200D 2640 ; minimally-qualified # 🧗🏽‍♀ E5.0 woman climbing: medium skin tone +1F9D7 1F3FE 200D 2640 FE0F ; fully-qualified # 🧗🏾‍♀️ E5.0 woman climbing: medium-dark skin tone +1F9D7 1F3FE 200D 2640 ; minimally-qualified # 🧗🏾‍♀ E5.0 woman climbing: medium-dark skin tone +1F9D7 1F3FF 200D 2640 FE0F ; fully-qualified # 🧗🏿‍♀️ E5.0 woman climbing: dark skin tone +1F9D7 1F3FF 200D 2640 ; minimally-qualified # 🧗🏿‍♀ E5.0 woman climbing: dark skin tone + +# subgroup: person-sport +1F93A ; fully-qualified # 🤺 E3.0 person fencing +1F3C7 ; fully-qualified # 🏇 E1.0 horse racing +1F3C7 1F3FB ; fully-qualified # 🏇🏻 E1.0 horse racing: light skin tone +1F3C7 1F3FC ; fully-qualified # 🏇🏼 E1.0 horse racing: medium-light skin tone +1F3C7 1F3FD ; fully-qualified # 🏇🏽 E1.0 horse racing: medium skin tone +1F3C7 1F3FE ; fully-qualified # 🏇🏾 E1.0 horse racing: medium-dark skin tone +1F3C7 1F3FF ; fully-qualified # 🏇🏿 E1.0 horse racing: dark skin tone +26F7 FE0F ; fully-qualified # ⛷️ E0.7 skier +26F7 ; unqualified # ⛷ E0.7 skier +1F3C2 ; fully-qualified # 🏂 E0.6 snowboarder +1F3C2 1F3FB ; fully-qualified # 🏂🏻 E1.0 snowboarder: light skin tone +1F3C2 1F3FC ; fully-qualified # 🏂🏼 E1.0 snowboarder: medium-light skin tone +1F3C2 1F3FD ; fully-qualified # 🏂🏽 E1.0 snowboarder: medium skin tone +1F3C2 1F3FE ; fully-qualified # 🏂🏾 E1.0 snowboarder: medium-dark skin tone +1F3C2 1F3FF ; fully-qualified # 🏂🏿 E1.0 snowboarder: dark skin tone +1F3CC FE0F ; fully-qualified # 🏌️ E0.7 person golfing +1F3CC ; unqualified # 🏌 E0.7 person golfing +1F3CC 1F3FB ; fully-qualified # 🏌🏻 E4.0 person golfing: light skin tone +1F3CC 1F3FC ; fully-qualified # 🏌🏼 E4.0 person golfing: medium-light skin tone +1F3CC 1F3FD ; fully-qualified # 🏌🏽 E4.0 person golfing: medium skin tone +1F3CC 1F3FE ; fully-qualified # 🏌🏾 E4.0 person golfing: medium-dark skin tone +1F3CC 1F3FF ; fully-qualified # 🏌🏿 E4.0 person golfing: dark skin tone +1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ E4.0 man golfing +1F3CC 200D 2642 FE0F ; unqualified # 🏌‍♂️ E4.0 man golfing +1F3CC FE0F 200D 2642 ; unqualified # 🏌️‍♂ E4.0 man golfing +1F3CC 200D 2642 ; unqualified # 🏌‍♂ E4.0 man golfing +1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ E4.0 man golfing: light skin tone +1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻‍♂ E4.0 man golfing: light skin tone +1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ E4.0 man golfing: medium-light skin tone +1F3CC 1F3FC 200D 2642 ; minimally-qualified # 🏌🏼‍♂ E4.0 man golfing: medium-light skin tone +1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ E4.0 man golfing: medium skin tone +1F3CC 1F3FD 200D 2642 ; minimally-qualified # 🏌🏽‍♂ E4.0 man golfing: medium skin tone +1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ E4.0 man golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2642 ; minimally-qualified # 🏌🏾‍♂ E4.0 man golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ E4.0 man golfing: dark skin tone +1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿‍♂ E4.0 man golfing: dark skin tone +1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ E4.0 woman golfing +1F3CC 200D 2640 FE0F ; unqualified # 🏌‍♀️ E4.0 woman golfing +1F3CC FE0F 200D 2640 ; unqualified # 🏌️‍♀ E4.0 woman golfing +1F3CC 200D 2640 ; unqualified # 🏌‍♀ E4.0 woman golfing +1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ E4.0 woman golfing: light skin tone +1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻‍♀ E4.0 woman golfing: light skin tone +1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ E4.0 woman golfing: medium-light skin tone +1F3CC 1F3FC 200D 2640 ; minimally-qualified # 🏌🏼‍♀ E4.0 woman golfing: medium-light skin tone +1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ E4.0 woman golfing: medium skin tone +1F3CC 1F3FD 200D 2640 ; minimally-qualified # 🏌🏽‍♀ E4.0 woman golfing: medium skin tone +1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ E4.0 woman golfing: medium-dark skin tone +1F3CC 1F3FE 200D 2640 ; minimally-qualified # 🏌🏾‍♀ E4.0 woman golfing: medium-dark skin tone +1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ E4.0 woman golfing: dark skin tone +1F3CC 1F3FF 200D 2640 ; minimally-qualified # 🏌🏿‍♀ E4.0 woman golfing: dark skin tone +1F3C4 ; fully-qualified # 🏄 E0.6 person surfing +1F3C4 1F3FB ; fully-qualified # 🏄🏻 E1.0 person surfing: light skin tone +1F3C4 1F3FC ; fully-qualified # 🏄🏼 E1.0 person surfing: medium-light skin tone +1F3C4 1F3FD ; fully-qualified # 🏄🏽 E1.0 person surfing: medium skin tone +1F3C4 1F3FE ; fully-qualified # 🏄🏾 E1.0 person surfing: medium-dark skin tone +1F3C4 1F3FF ; fully-qualified # 🏄🏿 E1.0 person surfing: dark skin tone +1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ E4.0 man surfing +1F3C4 200D 2642 ; minimally-qualified # 🏄‍♂ E4.0 man surfing +1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ E4.0 man surfing: light skin tone +1F3C4 1F3FB 200D 2642 ; minimally-qualified # 🏄🏻‍♂ E4.0 man surfing: light skin tone +1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ E4.0 man surfing: medium-light skin tone +1F3C4 1F3FC 200D 2642 ; minimally-qualified # 🏄🏼‍♂ E4.0 man surfing: medium-light skin tone +1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ E4.0 man surfing: medium skin tone +1F3C4 1F3FD 200D 2642 ; minimally-qualified # 🏄🏽‍♂ E4.0 man surfing: medium skin tone +1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ E4.0 man surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2642 ; minimally-qualified # 🏄🏾‍♂ E4.0 man surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ E4.0 man surfing: dark skin tone +1F3C4 1F3FF 200D 2642 ; minimally-qualified # 🏄🏿‍♂ E4.0 man surfing: dark skin tone +1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ E4.0 woman surfing +1F3C4 200D 2640 ; minimally-qualified # 🏄‍♀ E4.0 woman surfing +1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ E4.0 woman surfing: light skin tone +1F3C4 1F3FB 200D 2640 ; minimally-qualified # 🏄🏻‍♀ E4.0 woman surfing: light skin tone +1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ E4.0 woman surfing: medium-light skin tone +1F3C4 1F3FC 200D 2640 ; minimally-qualified # 🏄🏼‍♀ E4.0 woman surfing: medium-light skin tone +1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ E4.0 woman surfing: medium skin tone +1F3C4 1F3FD 200D 2640 ; minimally-qualified # 🏄🏽‍♀ E4.0 woman surfing: medium skin tone +1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ E4.0 woman surfing: medium-dark skin tone +1F3C4 1F3FE 200D 2640 ; minimally-qualified # 🏄🏾‍♀ E4.0 woman surfing: medium-dark skin tone +1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ E4.0 woman surfing: dark skin tone +1F3C4 1F3FF 200D 2640 ; minimally-qualified # 🏄🏿‍♀ E4.0 woman surfing: dark skin tone +1F6A3 ; fully-qualified # 🚣 E1.0 person rowing boat +1F6A3 1F3FB ; fully-qualified # 🚣🏻 E1.0 person rowing boat: light skin tone +1F6A3 1F3FC ; fully-qualified # 🚣🏼 E1.0 person rowing boat: medium-light skin tone +1F6A3 1F3FD ; fully-qualified # 🚣🏽 E1.0 person rowing boat: medium skin tone +1F6A3 1F3FE ; fully-qualified # 🚣🏾 E1.0 person rowing boat: medium-dark skin tone +1F6A3 1F3FF ; fully-qualified # 🚣🏿 E1.0 person rowing boat: dark skin tone +1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ E4.0 man rowing boat +1F6A3 200D 2642 ; minimally-qualified # 🚣‍♂ E4.0 man rowing boat +1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ E4.0 man rowing boat: light skin tone +1F6A3 1F3FB 200D 2642 ; minimally-qualified # 🚣🏻‍♂ E4.0 man rowing boat: light skin tone +1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ E4.0 man rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2642 ; minimally-qualified # 🚣🏼‍♂ E4.0 man rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ E4.0 man rowing boat: medium skin tone +1F6A3 1F3FD 200D 2642 ; minimally-qualified # 🚣🏽‍♂ E4.0 man rowing boat: medium skin tone +1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ E4.0 man rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2642 ; minimally-qualified # 🚣🏾‍♂ E4.0 man rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ E4.0 man rowing boat: dark skin tone +1F6A3 1F3FF 200D 2642 ; minimally-qualified # 🚣🏿‍♂ E4.0 man rowing boat: dark skin tone +1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ E4.0 woman rowing boat +1F6A3 200D 2640 ; minimally-qualified # 🚣‍♀ E4.0 woman rowing boat +1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ E4.0 woman rowing boat: light skin tone +1F6A3 1F3FB 200D 2640 ; minimally-qualified # 🚣🏻‍♀ E4.0 woman rowing boat: light skin tone +1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ E4.0 woman rowing boat: medium-light skin tone +1F6A3 1F3FC 200D 2640 ; minimally-qualified # 🚣🏼‍♀ E4.0 woman rowing boat: medium-light skin tone +1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ E4.0 woman rowing boat: medium skin tone +1F6A3 1F3FD 200D 2640 ; minimally-qualified # 🚣🏽‍♀ E4.0 woman rowing boat: medium skin tone +1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ E4.0 woman rowing boat: medium-dark skin tone +1F6A3 1F3FE 200D 2640 ; minimally-qualified # 🚣🏾‍♀ E4.0 woman rowing boat: medium-dark skin tone +1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ E4.0 woman rowing boat: dark skin tone +1F6A3 1F3FF 200D 2640 ; minimally-qualified # 🚣🏿‍♀ E4.0 woman rowing boat: dark skin tone +1F3CA ; fully-qualified # 🏊 E0.6 person swimming +1F3CA 1F3FB ; fully-qualified # 🏊🏻 E1.0 person swimming: light skin tone +1F3CA 1F3FC ; fully-qualified # 🏊🏼 E1.0 person swimming: medium-light skin tone +1F3CA 1F3FD ; fully-qualified # 🏊🏽 E1.0 person swimming: medium skin tone +1F3CA 1F3FE ; fully-qualified # 🏊🏾 E1.0 person swimming: medium-dark skin tone +1F3CA 1F3FF ; fully-qualified # 🏊🏿 E1.0 person swimming: dark skin tone +1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ E4.0 man swimming +1F3CA 200D 2642 ; minimally-qualified # 🏊‍♂ E4.0 man swimming +1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ E4.0 man swimming: light skin tone +1F3CA 1F3FB 200D 2642 ; minimally-qualified # 🏊🏻‍♂ E4.0 man swimming: light skin tone +1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ E4.0 man swimming: medium-light skin tone +1F3CA 1F3FC 200D 2642 ; minimally-qualified # 🏊🏼‍♂ E4.0 man swimming: medium-light skin tone +1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ E4.0 man swimming: medium skin tone +1F3CA 1F3FD 200D 2642 ; minimally-qualified # 🏊🏽‍♂ E4.0 man swimming: medium skin tone +1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ E4.0 man swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2642 ; minimally-qualified # 🏊🏾‍♂ E4.0 man swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ E4.0 man swimming: dark skin tone +1F3CA 1F3FF 200D 2642 ; minimally-qualified # 🏊🏿‍♂ E4.0 man swimming: dark skin tone +1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ E4.0 woman swimming +1F3CA 200D 2640 ; minimally-qualified # 🏊‍♀ E4.0 woman swimming +1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ E4.0 woman swimming: light skin tone +1F3CA 1F3FB 200D 2640 ; minimally-qualified # 🏊🏻‍♀ E4.0 woman swimming: light skin tone +1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ E4.0 woman swimming: medium-light skin tone +1F3CA 1F3FC 200D 2640 ; minimally-qualified # 🏊🏼‍♀ E4.0 woman swimming: medium-light skin tone +1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ E4.0 woman swimming: medium skin tone +1F3CA 1F3FD 200D 2640 ; minimally-qualified # 🏊🏽‍♀ E4.0 woman swimming: medium skin tone +1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ E4.0 woman swimming: medium-dark skin tone +1F3CA 1F3FE 200D 2640 ; minimally-qualified # 🏊🏾‍♀ E4.0 woman swimming: medium-dark skin tone +1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ E4.0 woman swimming: dark skin tone +1F3CA 1F3FF 200D 2640 ; minimally-qualified # 🏊🏿‍♀ E4.0 woman swimming: dark skin tone +26F9 FE0F ; fully-qualified # ⛹️ E0.7 person bouncing ball +26F9 ; unqualified # ⛹ E0.7 person bouncing ball +26F9 1F3FB ; fully-qualified # ⛹🏻 E2.0 person bouncing ball: light skin tone +26F9 1F3FC ; fully-qualified # ⛹🏼 E2.0 person bouncing ball: medium-light skin tone +26F9 1F3FD ; fully-qualified # ⛹🏽 E2.0 person bouncing ball: medium skin tone +26F9 1F3FE ; fully-qualified # ⛹🏾 E2.0 person bouncing ball: medium-dark skin tone +26F9 1F3FF ; fully-qualified # ⛹🏿 E2.0 person bouncing ball: dark skin tone +26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ E4.0 man bouncing ball +26F9 200D 2642 FE0F ; unqualified # ⛹‍♂️ E4.0 man bouncing ball +26F9 FE0F 200D 2642 ; unqualified # ⛹️‍♂ E4.0 man bouncing ball +26F9 200D 2642 ; unqualified # ⛹‍♂ E4.0 man bouncing ball +26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ E4.0 man bouncing ball: light skin tone +26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻‍♂ E4.0 man bouncing ball: light skin tone +26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ E4.0 man bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2642 ; minimally-qualified # ⛹🏼‍♂ E4.0 man bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ E4.0 man bouncing ball: medium skin tone +26F9 1F3FD 200D 2642 ; minimally-qualified # ⛹🏽‍♂ E4.0 man bouncing ball: medium skin tone +26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ E4.0 man bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2642 ; minimally-qualified # ⛹🏾‍♂ E4.0 man bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ E4.0 man bouncing ball: dark skin tone +26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿‍♂ E4.0 man bouncing ball: dark skin tone +26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ E4.0 woman bouncing ball +26F9 200D 2640 FE0F ; unqualified # ⛹‍♀️ E4.0 woman bouncing ball +26F9 FE0F 200D 2640 ; unqualified # ⛹️‍♀ E4.0 woman bouncing ball +26F9 200D 2640 ; unqualified # ⛹‍♀ E4.0 woman bouncing ball +26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ E4.0 woman bouncing ball: light skin tone +26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻‍♀ E4.0 woman bouncing ball: light skin tone +26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ E4.0 woman bouncing ball: medium-light skin tone +26F9 1F3FC 200D 2640 ; minimally-qualified # ⛹🏼‍♀ E4.0 woman bouncing ball: medium-light skin tone +26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ E4.0 woman bouncing ball: medium skin tone +26F9 1F3FD 200D 2640 ; minimally-qualified # ⛹🏽‍♀ E4.0 woman bouncing ball: medium skin tone +26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ E4.0 woman bouncing ball: medium-dark skin tone +26F9 1F3FE 200D 2640 ; minimally-qualified # ⛹🏾‍♀ E4.0 woman bouncing ball: medium-dark skin tone +26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ E4.0 woman bouncing ball: dark skin tone +26F9 1F3FF 200D 2640 ; minimally-qualified # ⛹🏿‍♀ E4.0 woman bouncing ball: dark skin tone +1F3CB FE0F ; fully-qualified # 🏋️ E0.7 person lifting weights +1F3CB ; unqualified # 🏋 E0.7 person lifting weights +1F3CB 1F3FB ; fully-qualified # 🏋🏻 E2.0 person lifting weights: light skin tone +1F3CB 1F3FC ; fully-qualified # 🏋🏼 E2.0 person lifting weights: medium-light skin tone +1F3CB 1F3FD ; fully-qualified # 🏋🏽 E2.0 person lifting weights: medium skin tone +1F3CB 1F3FE ; fully-qualified # 🏋🏾 E2.0 person lifting weights: medium-dark skin tone +1F3CB 1F3FF ; fully-qualified # 🏋🏿 E2.0 person lifting weights: dark skin tone +1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ E4.0 man lifting weights +1F3CB 200D 2642 FE0F ; unqualified # 🏋‍♂️ E4.0 man lifting weights +1F3CB FE0F 200D 2642 ; unqualified # 🏋️‍♂ E4.0 man lifting weights +1F3CB 200D 2642 ; unqualified # 🏋‍♂ E4.0 man lifting weights +1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ E4.0 man lifting weights: light skin tone +1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻‍♂ E4.0 man lifting weights: light skin tone +1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ E4.0 man lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2642 ; minimally-qualified # 🏋🏼‍♂ E4.0 man lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ E4.0 man lifting weights: medium skin tone +1F3CB 1F3FD 200D 2642 ; minimally-qualified # 🏋🏽‍♂ E4.0 man lifting weights: medium skin tone +1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ E4.0 man lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2642 ; minimally-qualified # 🏋🏾‍♂ E4.0 man lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ E4.0 man lifting weights: dark skin tone +1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿‍♂ E4.0 man lifting weights: dark skin tone +1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ E4.0 woman lifting weights +1F3CB 200D 2640 FE0F ; unqualified # 🏋‍♀️ E4.0 woman lifting weights +1F3CB FE0F 200D 2640 ; unqualified # 🏋️‍♀ E4.0 woman lifting weights +1F3CB 200D 2640 ; unqualified # 🏋‍♀ E4.0 woman lifting weights +1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ E4.0 woman lifting weights: light skin tone +1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻‍♀ E4.0 woman lifting weights: light skin tone +1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ E4.0 woman lifting weights: medium-light skin tone +1F3CB 1F3FC 200D 2640 ; minimally-qualified # 🏋🏼‍♀ E4.0 woman lifting weights: medium-light skin tone +1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ E4.0 woman lifting weights: medium skin tone +1F3CB 1F3FD 200D 2640 ; minimally-qualified # 🏋🏽‍♀ E4.0 woman lifting weights: medium skin tone +1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ E4.0 woman lifting weights: medium-dark skin tone +1F3CB 1F3FE 200D 2640 ; minimally-qualified # 🏋🏾‍♀ E4.0 woman lifting weights: medium-dark skin tone +1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ E4.0 woman lifting weights: dark skin tone +1F3CB 1F3FF 200D 2640 ; minimally-qualified # 🏋🏿‍♀ E4.0 woman lifting weights: dark skin tone +1F6B4 ; fully-qualified # 🚴 E1.0 person biking +1F6B4 1F3FB ; fully-qualified # 🚴🏻 E1.0 person biking: light skin tone +1F6B4 1F3FC ; fully-qualified # 🚴🏼 E1.0 person biking: medium-light skin tone +1F6B4 1F3FD ; fully-qualified # 🚴🏽 E1.0 person biking: medium skin tone +1F6B4 1F3FE ; fully-qualified # 🚴🏾 E1.0 person biking: medium-dark skin tone +1F6B4 1F3FF ; fully-qualified # 🚴🏿 E1.0 person biking: dark skin tone +1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ E4.0 man biking +1F6B4 200D 2642 ; minimally-qualified # 🚴‍♂ E4.0 man biking +1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ E4.0 man biking: light skin tone +1F6B4 1F3FB 200D 2642 ; minimally-qualified # 🚴🏻‍♂ E4.0 man biking: light skin tone +1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ E4.0 man biking: medium-light skin tone +1F6B4 1F3FC 200D 2642 ; minimally-qualified # 🚴🏼‍♂ E4.0 man biking: medium-light skin tone +1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ E4.0 man biking: medium skin tone +1F6B4 1F3FD 200D 2642 ; minimally-qualified # 🚴🏽‍♂ E4.0 man biking: medium skin tone +1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ E4.0 man biking: medium-dark skin tone +1F6B4 1F3FE 200D 2642 ; minimally-qualified # 🚴🏾‍♂ E4.0 man biking: medium-dark skin tone +1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ E4.0 man biking: dark skin tone +1F6B4 1F3FF 200D 2642 ; minimally-qualified # 🚴🏿‍♂ E4.0 man biking: dark skin tone +1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ E4.0 woman biking +1F6B4 200D 2640 ; minimally-qualified # 🚴‍♀ E4.0 woman biking +1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ E4.0 woman biking: light skin tone +1F6B4 1F3FB 200D 2640 ; minimally-qualified # 🚴🏻‍♀ E4.0 woman biking: light skin tone +1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ E4.0 woman biking: medium-light skin tone +1F6B4 1F3FC 200D 2640 ; minimally-qualified # 🚴🏼‍♀ E4.0 woman biking: medium-light skin tone +1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ E4.0 woman biking: medium skin tone +1F6B4 1F3FD 200D 2640 ; minimally-qualified # 🚴🏽‍♀ E4.0 woman biking: medium skin tone +1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ E4.0 woman biking: medium-dark skin tone +1F6B4 1F3FE 200D 2640 ; minimally-qualified # 🚴🏾‍♀ E4.0 woman biking: medium-dark skin tone +1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ E4.0 woman biking: dark skin tone +1F6B4 1F3FF 200D 2640 ; minimally-qualified # 🚴🏿‍♀ E4.0 woman biking: dark skin tone +1F6B5 ; fully-qualified # 🚵 E1.0 person mountain biking +1F6B5 1F3FB ; fully-qualified # 🚵🏻 E1.0 person mountain biking: light skin tone +1F6B5 1F3FC ; fully-qualified # 🚵🏼 E1.0 person mountain biking: medium-light skin tone +1F6B5 1F3FD ; fully-qualified # 🚵🏽 E1.0 person mountain biking: medium skin tone +1F6B5 1F3FE ; fully-qualified # 🚵🏾 E1.0 person mountain biking: medium-dark skin tone +1F6B5 1F3FF ; fully-qualified # 🚵🏿 E1.0 person mountain biking: dark skin tone +1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ E4.0 man mountain biking +1F6B5 200D 2642 ; minimally-qualified # 🚵‍♂ E4.0 man mountain biking +1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ E4.0 man mountain biking: light skin tone +1F6B5 1F3FB 200D 2642 ; minimally-qualified # 🚵🏻‍♂ E4.0 man mountain biking: light skin tone +1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ E4.0 man mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2642 ; minimally-qualified # 🚵🏼‍♂ E4.0 man mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ E4.0 man mountain biking: medium skin tone +1F6B5 1F3FD 200D 2642 ; minimally-qualified # 🚵🏽‍♂ E4.0 man mountain biking: medium skin tone +1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ E4.0 man mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2642 ; minimally-qualified # 🚵🏾‍♂ E4.0 man mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ E4.0 man mountain biking: dark skin tone +1F6B5 1F3FF 200D 2642 ; minimally-qualified # 🚵🏿‍♂ E4.0 man mountain biking: dark skin tone +1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ E4.0 woman mountain biking +1F6B5 200D 2640 ; minimally-qualified # 🚵‍♀ E4.0 woman mountain biking +1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ E4.0 woman mountain biking: light skin tone +1F6B5 1F3FB 200D 2640 ; minimally-qualified # 🚵🏻‍♀ E4.0 woman mountain biking: light skin tone +1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ E4.0 woman mountain biking: medium-light skin tone +1F6B5 1F3FC 200D 2640 ; minimally-qualified # 🚵🏼‍♀ E4.0 woman mountain biking: medium-light skin tone +1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ E4.0 woman mountain biking: medium skin tone +1F6B5 1F3FD 200D 2640 ; minimally-qualified # 🚵🏽‍♀ E4.0 woman mountain biking: medium skin tone +1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ E4.0 woman mountain biking: medium-dark skin tone +1F6B5 1F3FE 200D 2640 ; minimally-qualified # 🚵🏾‍♀ E4.0 woman mountain biking: medium-dark skin tone +1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ E4.0 woman mountain biking: dark skin tone +1F6B5 1F3FF 200D 2640 ; minimally-qualified # 🚵🏿‍♀ E4.0 woman mountain biking: dark skin tone +1F938 ; fully-qualified # 🤸 E3.0 person cartwheeling +1F938 1F3FB ; fully-qualified # 🤸🏻 E3.0 person cartwheeling: light skin tone +1F938 1F3FC ; fully-qualified # 🤸🏼 E3.0 person cartwheeling: medium-light skin tone +1F938 1F3FD ; fully-qualified # 🤸🏽 E3.0 person cartwheeling: medium skin tone +1F938 1F3FE ; fully-qualified # 🤸🏾 E3.0 person cartwheeling: medium-dark skin tone +1F938 1F3FF ; fully-qualified # 🤸🏿 E3.0 person cartwheeling: dark skin tone +1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ E4.0 man cartwheeling +1F938 200D 2642 ; minimally-qualified # 🤸‍♂ E4.0 man cartwheeling +1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ E4.0 man cartwheeling: light skin tone +1F938 1F3FB 200D 2642 ; minimally-qualified # 🤸🏻‍♂ E4.0 man cartwheeling: light skin tone +1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ E4.0 man cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2642 ; minimally-qualified # 🤸🏼‍♂ E4.0 man cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ E4.0 man cartwheeling: medium skin tone +1F938 1F3FD 200D 2642 ; minimally-qualified # 🤸🏽‍♂ E4.0 man cartwheeling: medium skin tone +1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ E4.0 man cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2642 ; minimally-qualified # 🤸🏾‍♂ E4.0 man cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ E4.0 man cartwheeling: dark skin tone +1F938 1F3FF 200D 2642 ; minimally-qualified # 🤸🏿‍♂ E4.0 man cartwheeling: dark skin tone +1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ E4.0 woman cartwheeling +1F938 200D 2640 ; minimally-qualified # 🤸‍♀ E4.0 woman cartwheeling +1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ E4.0 woman cartwheeling: light skin tone +1F938 1F3FB 200D 2640 ; minimally-qualified # 🤸🏻‍♀ E4.0 woman cartwheeling: light skin tone +1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ E4.0 woman cartwheeling: medium-light skin tone +1F938 1F3FC 200D 2640 ; minimally-qualified # 🤸🏼‍♀ E4.0 woman cartwheeling: medium-light skin tone +1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ E4.0 woman cartwheeling: medium skin tone +1F938 1F3FD 200D 2640 ; minimally-qualified # 🤸🏽‍♀ E4.0 woman cartwheeling: medium skin tone +1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ E4.0 woman cartwheeling: medium-dark skin tone +1F938 1F3FE 200D 2640 ; minimally-qualified # 🤸🏾‍♀ E4.0 woman cartwheeling: medium-dark skin tone +1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ E4.0 woman cartwheeling: dark skin tone +1F938 1F3FF 200D 2640 ; minimally-qualified # 🤸🏿‍♀ E4.0 woman cartwheeling: dark skin tone +1F93C ; fully-qualified # 🤼 E3.0 people wrestling +1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ E4.0 men wrestling +1F93C 200D 2642 ; minimally-qualified # 🤼‍♂ E4.0 men wrestling +1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ E4.0 women wrestling +1F93C 200D 2640 ; minimally-qualified # 🤼‍♀ E4.0 women wrestling +1F93D ; fully-qualified # 🤽 E3.0 person playing water polo +1F93D 1F3FB ; fully-qualified # 🤽🏻 E3.0 person playing water polo: light skin tone +1F93D 1F3FC ; fully-qualified # 🤽🏼 E3.0 person playing water polo: medium-light skin tone +1F93D 1F3FD ; fully-qualified # 🤽🏽 E3.0 person playing water polo: medium skin tone +1F93D 1F3FE ; fully-qualified # 🤽🏾 E3.0 person playing water polo: medium-dark skin tone +1F93D 1F3FF ; fully-qualified # 🤽🏿 E3.0 person playing water polo: dark skin tone +1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ E4.0 man playing water polo +1F93D 200D 2642 ; minimally-qualified # 🤽‍♂ E4.0 man playing water polo +1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ E4.0 man playing water polo: light skin tone +1F93D 1F3FB 200D 2642 ; minimally-qualified # 🤽🏻‍♂ E4.0 man playing water polo: light skin tone +1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ E4.0 man playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2642 ; minimally-qualified # 🤽🏼‍♂ E4.0 man playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ E4.0 man playing water polo: medium skin tone +1F93D 1F3FD 200D 2642 ; minimally-qualified # 🤽🏽‍♂ E4.0 man playing water polo: medium skin tone +1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ E4.0 man playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2642 ; minimally-qualified # 🤽🏾‍♂ E4.0 man playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ E4.0 man playing water polo: dark skin tone +1F93D 1F3FF 200D 2642 ; minimally-qualified # 🤽🏿‍♂ E4.0 man playing water polo: dark skin tone +1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ E4.0 woman playing water polo +1F93D 200D 2640 ; minimally-qualified # 🤽‍♀ E4.0 woman playing water polo +1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ E4.0 woman playing water polo: light skin tone +1F93D 1F3FB 200D 2640 ; minimally-qualified # 🤽🏻‍♀ E4.0 woman playing water polo: light skin tone +1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ E4.0 woman playing water polo: medium-light skin tone +1F93D 1F3FC 200D 2640 ; minimally-qualified # 🤽🏼‍♀ E4.0 woman playing water polo: medium-light skin tone +1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ E4.0 woman playing water polo: medium skin tone +1F93D 1F3FD 200D 2640 ; minimally-qualified # 🤽🏽‍♀ E4.0 woman playing water polo: medium skin tone +1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ E4.0 woman playing water polo: medium-dark skin tone +1F93D 1F3FE 200D 2640 ; minimally-qualified # 🤽🏾‍♀ E4.0 woman playing water polo: medium-dark skin tone +1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ E4.0 woman playing water polo: dark skin tone +1F93D 1F3FF 200D 2640 ; minimally-qualified # 🤽🏿‍♀ E4.0 woman playing water polo: dark skin tone +1F93E ; fully-qualified # 🤾 E3.0 person playing handball +1F93E 1F3FB ; fully-qualified # 🤾🏻 E3.0 person playing handball: light skin tone +1F93E 1F3FC ; fully-qualified # 🤾🏼 E3.0 person playing handball: medium-light skin tone +1F93E 1F3FD ; fully-qualified # 🤾🏽 E3.0 person playing handball: medium skin tone +1F93E 1F3FE ; fully-qualified # 🤾🏾 E3.0 person playing handball: medium-dark skin tone +1F93E 1F3FF ; fully-qualified # 🤾🏿 E3.0 person playing handball: dark skin tone +1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ E4.0 man playing handball +1F93E 200D 2642 ; minimally-qualified # 🤾‍♂ E4.0 man playing handball +1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ E4.0 man playing handball: light skin tone +1F93E 1F3FB 200D 2642 ; minimally-qualified # 🤾🏻‍♂ E4.0 man playing handball: light skin tone +1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ E4.0 man playing handball: medium-light skin tone +1F93E 1F3FC 200D 2642 ; minimally-qualified # 🤾🏼‍♂ E4.0 man playing handball: medium-light skin tone +1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ E4.0 man playing handball: medium skin tone +1F93E 1F3FD 200D 2642 ; minimally-qualified # 🤾🏽‍♂ E4.0 man playing handball: medium skin tone +1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ E4.0 man playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2642 ; minimally-qualified # 🤾🏾‍♂ E4.0 man playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ E4.0 man playing handball: dark skin tone +1F93E 1F3FF 200D 2642 ; minimally-qualified # 🤾🏿‍♂ E4.0 man playing handball: dark skin tone +1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ E4.0 woman playing handball +1F93E 200D 2640 ; minimally-qualified # 🤾‍♀ E4.0 woman playing handball +1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ E4.0 woman playing handball: light skin tone +1F93E 1F3FB 200D 2640 ; minimally-qualified # 🤾🏻‍♀ E4.0 woman playing handball: light skin tone +1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ E4.0 woman playing handball: medium-light skin tone +1F93E 1F3FC 200D 2640 ; minimally-qualified # 🤾🏼‍♀ E4.0 woman playing handball: medium-light skin tone +1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ E4.0 woman playing handball: medium skin tone +1F93E 1F3FD 200D 2640 ; minimally-qualified # 🤾🏽‍♀ E4.0 woman playing handball: medium skin tone +1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ E4.0 woman playing handball: medium-dark skin tone +1F93E 1F3FE 200D 2640 ; minimally-qualified # 🤾🏾‍♀ E4.0 woman playing handball: medium-dark skin tone +1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ E4.0 woman playing handball: dark skin tone +1F93E 1F3FF 200D 2640 ; minimally-qualified # 🤾🏿‍♀ E4.0 woman playing handball: dark skin tone +1F939 ; fully-qualified # 🤹 E3.0 person juggling +1F939 1F3FB ; fully-qualified # 🤹🏻 E3.0 person juggling: light skin tone +1F939 1F3FC ; fully-qualified # 🤹🏼 E3.0 person juggling: medium-light skin tone +1F939 1F3FD ; fully-qualified # 🤹🏽 E3.0 person juggling: medium skin tone +1F939 1F3FE ; fully-qualified # 🤹🏾 E3.0 person juggling: medium-dark skin tone +1F939 1F3FF ; fully-qualified # 🤹🏿 E3.0 person juggling: dark skin tone +1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ E4.0 man juggling +1F939 200D 2642 ; minimally-qualified # 🤹‍♂ E4.0 man juggling +1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ E4.0 man juggling: light skin tone +1F939 1F3FB 200D 2642 ; minimally-qualified # 🤹🏻‍♂ E4.0 man juggling: light skin tone +1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ E4.0 man juggling: medium-light skin tone +1F939 1F3FC 200D 2642 ; minimally-qualified # 🤹🏼‍♂ E4.0 man juggling: medium-light skin tone +1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ E4.0 man juggling: medium skin tone +1F939 1F3FD 200D 2642 ; minimally-qualified # 🤹🏽‍♂ E4.0 man juggling: medium skin tone +1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ E4.0 man juggling: medium-dark skin tone +1F939 1F3FE 200D 2642 ; minimally-qualified # 🤹🏾‍♂ E4.0 man juggling: medium-dark skin tone +1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ E4.0 man juggling: dark skin tone +1F939 1F3FF 200D 2642 ; minimally-qualified # 🤹🏿‍♂ E4.0 man juggling: dark skin tone +1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ E4.0 woman juggling +1F939 200D 2640 ; minimally-qualified # 🤹‍♀ E4.0 woman juggling +1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ E4.0 woman juggling: light skin tone +1F939 1F3FB 200D 2640 ; minimally-qualified # 🤹🏻‍♀ E4.0 woman juggling: light skin tone +1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ E4.0 woman juggling: medium-light skin tone +1F939 1F3FC 200D 2640 ; minimally-qualified # 🤹🏼‍♀ E4.0 woman juggling: medium-light skin tone +1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ E4.0 woman juggling: medium skin tone +1F939 1F3FD 200D 2640 ; minimally-qualified # 🤹🏽‍♀ E4.0 woman juggling: medium skin tone +1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ E4.0 woman juggling: medium-dark skin tone +1F939 1F3FE 200D 2640 ; minimally-qualified # 🤹🏾‍♀ E4.0 woman juggling: medium-dark skin tone +1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ E4.0 woman juggling: dark skin tone +1F939 1F3FF 200D 2640 ; minimally-qualified # 🤹🏿‍♀ E4.0 woman juggling: dark skin tone + +# subgroup: person-resting +1F9D8 ; fully-qualified # 🧘 E5.0 person in lotus position +1F9D8 1F3FB ; fully-qualified # 🧘🏻 E5.0 person in lotus position: light skin tone +1F9D8 1F3FC ; fully-qualified # 🧘🏼 E5.0 person in lotus position: medium-light skin tone +1F9D8 1F3FD ; fully-qualified # 🧘🏽 E5.0 person in lotus position: medium skin tone +1F9D8 1F3FE ; fully-qualified # 🧘🏾 E5.0 person in lotus position: medium-dark skin tone +1F9D8 1F3FF ; fully-qualified # 🧘🏿 E5.0 person in lotus position: dark skin tone +1F9D8 200D 2642 FE0F ; fully-qualified # 🧘‍♂️ E5.0 man in lotus position +1F9D8 200D 2642 ; minimally-qualified # 🧘‍♂ E5.0 man in lotus position +1F9D8 1F3FB 200D 2642 FE0F ; fully-qualified # 🧘🏻‍♂️ E5.0 man in lotus position: light skin tone +1F9D8 1F3FB 200D 2642 ; minimally-qualified # 🧘🏻‍♂ E5.0 man in lotus position: light skin tone +1F9D8 1F3FC 200D 2642 FE0F ; fully-qualified # 🧘🏼‍♂️ E5.0 man in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2642 ; minimally-qualified # 🧘🏼‍♂ E5.0 man in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2642 FE0F ; fully-qualified # 🧘🏽‍♂️ E5.0 man in lotus position: medium skin tone +1F9D8 1F3FD 200D 2642 ; minimally-qualified # 🧘🏽‍♂ E5.0 man in lotus position: medium skin tone +1F9D8 1F3FE 200D 2642 FE0F ; fully-qualified # 🧘🏾‍♂️ E5.0 man in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2642 ; minimally-qualified # 🧘🏾‍♂ E5.0 man in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2642 FE0F ; fully-qualified # 🧘🏿‍♂️ E5.0 man in lotus position: dark skin tone +1F9D8 1F3FF 200D 2642 ; minimally-qualified # 🧘🏿‍♂ E5.0 man in lotus position: dark skin tone +1F9D8 200D 2640 FE0F ; fully-qualified # 🧘‍♀️ E5.0 woman in lotus position +1F9D8 200D 2640 ; minimally-qualified # 🧘‍♀ E5.0 woman in lotus position +1F9D8 1F3FB 200D 2640 FE0F ; fully-qualified # 🧘🏻‍♀️ E5.0 woman in lotus position: light skin tone +1F9D8 1F3FB 200D 2640 ; minimally-qualified # 🧘🏻‍♀ E5.0 woman in lotus position: light skin tone +1F9D8 1F3FC 200D 2640 FE0F ; fully-qualified # 🧘🏼‍♀️ E5.0 woman in lotus position: medium-light skin tone +1F9D8 1F3FC 200D 2640 ; minimally-qualified # 🧘🏼‍♀ E5.0 woman in lotus position: medium-light skin tone +1F9D8 1F3FD 200D 2640 FE0F ; fully-qualified # 🧘🏽‍♀️ E5.0 woman in lotus position: medium skin tone +1F9D8 1F3FD 200D 2640 ; minimally-qualified # 🧘🏽‍♀ E5.0 woman in lotus position: medium skin tone +1F9D8 1F3FE 200D 2640 FE0F ; fully-qualified # 🧘🏾‍♀️ E5.0 woman in lotus position: medium-dark skin tone +1F9D8 1F3FE 200D 2640 ; minimally-qualified # 🧘🏾‍♀ E5.0 woman in lotus position: medium-dark skin tone +1F9D8 1F3FF 200D 2640 FE0F ; fully-qualified # 🧘🏿‍♀️ E5.0 woman in lotus position: dark skin tone +1F9D8 1F3FF 200D 2640 ; minimally-qualified # 🧘🏿‍♀ E5.0 woman in lotus position: dark skin tone +1F6C0 ; fully-qualified # 🛀 E0.6 person taking bath +1F6C0 1F3FB ; fully-qualified # 🛀🏻 E1.0 person taking bath: light skin tone +1F6C0 1F3FC ; fully-qualified # 🛀🏼 E1.0 person taking bath: medium-light skin tone +1F6C0 1F3FD ; fully-qualified # 🛀🏽 E1.0 person taking bath: medium skin tone +1F6C0 1F3FE ; fully-qualified # 🛀🏾 E1.0 person taking bath: medium-dark skin tone +1F6C0 1F3FF ; fully-qualified # 🛀🏿 E1.0 person taking bath: dark skin tone +1F6CC ; fully-qualified # 🛌 E1.0 person in bed +1F6CC 1F3FB ; fully-qualified # 🛌🏻 E4.0 person in bed: light skin tone +1F6CC 1F3FC ; fully-qualified # 🛌🏼 E4.0 person in bed: medium-light skin tone +1F6CC 1F3FD ; fully-qualified # 🛌🏽 E4.0 person in bed: medium skin tone +1F6CC 1F3FE ; fully-qualified # 🛌🏾 E4.0 person in bed: medium-dark skin tone +1F6CC 1F3FF ; fully-qualified # 🛌🏿 E4.0 person in bed: dark skin tone + +# subgroup: family +1F9D1 200D 1F91D 200D 1F9D1 ; fully-qualified # 🧑‍🤝‍🧑 E12.0 people holding hands +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏻‍🤝‍🧑🏻 E12.0 people holding hands: light skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍🤝‍🧑🏼 E12.1 people holding hands: light skin tone, medium-light skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍🤝‍🧑🏽 E12.1 people holding hands: light skin tone, medium skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍🤝‍🧑🏾 E12.1 people holding hands: light skin tone, medium-dark skin tone +1F9D1 1F3FB 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍🤝‍🧑🏿 E12.1 people holding hands: light skin tone, dark skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍🤝‍🧑🏻 E12.0 people holding hands: medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏼‍🤝‍🧑🏼 E12.0 people holding hands: medium-light skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍🤝‍🧑🏽 E12.1 people holding hands: medium-light skin tone, medium skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍🤝‍🧑🏾 E12.1 people holding hands: medium-light skin tone, medium-dark skin tone +1F9D1 1F3FC 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍🤝‍🧑🏿 E12.1 people holding hands: medium-light skin tone, dark skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍🤝‍🧑🏻 E12.0 people holding hands: medium skin tone, light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍🤝‍🧑🏼 E12.0 people holding hands: medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏽‍🤝‍🧑🏽 E12.0 people holding hands: medium skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍🤝‍🧑🏾 E12.1 people holding hands: medium skin tone, medium-dark skin tone +1F9D1 1F3FD 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍🤝‍🧑🏿 E12.1 people holding hands: medium skin tone, dark skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍🤝‍🧑🏻 E12.0 people holding hands: medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍🤝‍🧑🏼 E12.0 people holding hands: medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍🤝‍🧑🏽 E12.0 people holding hands: medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏾‍🤝‍🧑🏾 E12.0 people holding hands: medium-dark skin tone +1F9D1 1F3FE 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍🤝‍🧑🏿 E12.1 people holding hands: medium-dark skin tone, dark skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍🤝‍🧑🏻 E12.0 people holding hands: dark skin tone, light skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍🤝‍🧑🏼 E12.0 people holding hands: dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍🤝‍🧑🏽 E12.0 people holding hands: dark skin tone, medium skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍🤝‍🧑🏾 E12.0 people holding hands: dark skin tone, medium-dark skin tone +1F9D1 1F3FF 200D 1F91D 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏿‍🤝‍🧑🏿 E12.0 people holding hands: dark skin tone +1F46D ; fully-qualified # 👭 E1.0 women holding hands +1F46D 1F3FB ; fully-qualified # 👭🏻 E12.0 women holding hands: light skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍🤝‍👩🏼 E12.1 women holding hands: light skin tone, medium-light skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍🤝‍👩🏽 E12.1 women holding hands: light skin tone, medium skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍🤝‍👩🏾 E12.1 women holding hands: light skin tone, medium-dark skin tone +1F469 1F3FB 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍🤝‍👩🏿 E12.1 women holding hands: light skin tone, dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍🤝‍👩🏻 E12.0 women holding hands: medium-light skin tone, light skin tone +1F46D 1F3FC ; fully-qualified # 👭🏼 E12.0 women holding hands: medium-light skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍🤝‍👩🏽 E12.1 women holding hands: medium-light skin tone, medium skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍🤝‍👩🏾 E12.1 women holding hands: medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍🤝‍👩🏿 E12.1 women holding hands: medium-light skin tone, dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍🤝‍👩🏻 E12.0 women holding hands: medium skin tone, light skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍🤝‍👩🏼 E12.0 women holding hands: medium skin tone, medium-light skin tone +1F46D 1F3FD ; fully-qualified # 👭🏽 E12.0 women holding hands: medium skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍🤝‍👩🏾 E12.1 women holding hands: medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍🤝‍👩🏿 E12.1 women holding hands: medium skin tone, dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍🤝‍👩🏻 E12.0 women holding hands: medium-dark skin tone, light skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍🤝‍👩🏼 E12.0 women holding hands: medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍🤝‍👩🏽 E12.0 women holding hands: medium-dark skin tone, medium skin tone +1F46D 1F3FE ; fully-qualified # 👭🏾 E12.0 women holding hands: medium-dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍🤝‍👩🏿 E12.1 women holding hands: medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍🤝‍👩🏻 E12.0 women holding hands: dark skin tone, light skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍🤝‍👩🏼 E12.0 women holding hands: dark skin tone, medium-light skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍🤝‍👩🏽 E12.0 women holding hands: dark skin tone, medium skin tone +1F469 1F3FF 200D 1F91D 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍🤝‍👩🏾 E12.0 women holding hands: dark skin tone, medium-dark skin tone +1F46D 1F3FF ; fully-qualified # 👭🏿 E12.0 women holding hands: dark skin tone +1F46B ; fully-qualified # 👫 E0.6 woman and man holding hands +1F46B 1F3FB ; fully-qualified # 👫🏻 E12.0 woman and man holding hands: light skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍🤝‍👨🏼 E12.0 woman and man holding hands: light skin tone, medium-light skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍🤝‍👨🏽 E12.0 woman and man holding hands: light skin tone, medium skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍🤝‍👨🏾 E12.0 woman and man holding hands: light skin tone, medium-dark skin tone +1F469 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍🤝‍👨🏿 E12.0 woman and man holding hands: light skin tone, dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍🤝‍👨🏻 E12.0 woman and man holding hands: medium-light skin tone, light skin tone +1F46B 1F3FC ; fully-qualified # 👫🏼 E12.0 woman and man holding hands: medium-light skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍🤝‍👨🏽 E12.0 woman and man holding hands: medium-light skin tone, medium skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍🤝‍👨🏾 E12.0 woman and man holding hands: medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍🤝‍👨🏿 E12.0 woman and man holding hands: medium-light skin tone, dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍🤝‍👨🏻 E12.0 woman and man holding hands: medium skin tone, light skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍🤝‍👨🏼 E12.0 woman and man holding hands: medium skin tone, medium-light skin tone +1F46B 1F3FD ; fully-qualified # 👫🏽 E12.0 woman and man holding hands: medium skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍🤝‍👨🏾 E12.0 woman and man holding hands: medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍🤝‍👨🏿 E12.0 woman and man holding hands: medium skin tone, dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍🤝‍👨🏻 E12.0 woman and man holding hands: medium-dark skin tone, light skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍🤝‍👨🏼 E12.0 woman and man holding hands: medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍🤝‍👨🏽 E12.0 woman and man holding hands: medium-dark skin tone, medium skin tone +1F46B 1F3FE ; fully-qualified # 👫🏾 E12.0 woman and man holding hands: medium-dark skin tone +1F469 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍🤝‍👨🏿 E12.0 woman and man holding hands: medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍🤝‍👨🏻 E12.0 woman and man holding hands: dark skin tone, light skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍🤝‍👨🏼 E12.0 woman and man holding hands: dark skin tone, medium-light skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍🤝‍👨🏽 E12.0 woman and man holding hands: dark skin tone, medium skin tone +1F469 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍🤝‍👨🏾 E12.0 woman and man holding hands: dark skin tone, medium-dark skin tone +1F46B 1F3FF ; fully-qualified # 👫🏿 E12.0 woman and man holding hands: dark skin tone +1F46C ; fully-qualified # 👬 E1.0 men holding hands +1F46C 1F3FB ; fully-qualified # 👬🏻 E12.0 men holding hands: light skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍🤝‍👨🏼 E12.1 men holding hands: light skin tone, medium-light skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍🤝‍👨🏽 E12.1 men holding hands: light skin tone, medium skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍🤝‍👨🏾 E12.1 men holding hands: light skin tone, medium-dark skin tone +1F468 1F3FB 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍🤝‍👨🏿 E12.1 men holding hands: light skin tone, dark skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍🤝‍👨🏻 E12.0 men holding hands: medium-light skin tone, light skin tone +1F46C 1F3FC ; fully-qualified # 👬🏼 E12.0 men holding hands: medium-light skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍🤝‍👨🏽 E12.1 men holding hands: medium-light skin tone, medium skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍🤝‍👨🏾 E12.1 men holding hands: medium-light skin tone, medium-dark skin tone +1F468 1F3FC 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍🤝‍👨🏿 E12.1 men holding hands: medium-light skin tone, dark skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍🤝‍👨🏻 E12.0 men holding hands: medium skin tone, light skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍🤝‍👨🏼 E12.0 men holding hands: medium skin tone, medium-light skin tone +1F46C 1F3FD ; fully-qualified # 👬🏽 E12.0 men holding hands: medium skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍🤝‍👨🏾 E12.1 men holding hands: medium skin tone, medium-dark skin tone +1F468 1F3FD 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍🤝‍👨🏿 E12.1 men holding hands: medium skin tone, dark skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍🤝‍👨🏻 E12.0 men holding hands: medium-dark skin tone, light skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍🤝‍👨🏼 E12.0 men holding hands: medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍🤝‍👨🏽 E12.0 men holding hands: medium-dark skin tone, medium skin tone +1F46C 1F3FE ; fully-qualified # 👬🏾 E12.0 men holding hands: medium-dark skin tone +1F468 1F3FE 200D 1F91D 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍🤝‍👨🏿 E12.1 men holding hands: medium-dark skin tone, dark skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍🤝‍👨🏻 E12.0 men holding hands: dark skin tone, light skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍🤝‍👨🏼 E12.0 men holding hands: dark skin tone, medium-light skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍🤝‍👨🏽 E12.0 men holding hands: dark skin tone, medium skin tone +1F468 1F3FF 200D 1F91D 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍🤝‍👨🏾 E12.0 men holding hands: dark skin tone, medium-dark skin tone +1F46C 1F3FF ; fully-qualified # 👬🏿 E12.0 men holding hands: dark skin tone +1F48F ; fully-qualified # 💏 E0.6 kiss +1F48F 1F3FB ; fully-qualified # 💏🏻 E13.1 kiss: light skin tone +1F48F 1F3FC ; fully-qualified # 💏🏼 E13.1 kiss: medium-light skin tone +1F48F 1F3FD ; fully-qualified # 💏🏽 E13.1 kiss: medium skin tone +1F48F 1F3FE ; fully-qualified # 💏🏾 E13.1 kiss: medium-dark skin tone +1F48F 1F3FF ; fully-qualified # 💏🏿 E13.1 kiss: dark skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, light skin tone, medium-light skin tone +1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, light skin tone, medium-light skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, light skin tone, medium skin tone +1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, light skin tone, medium skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, light skin tone, medium-dark skin tone +1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, light skin tone, medium-dark skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, light skin tone, dark skin tone +1F9D1 1F3FB 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏻‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, light skin tone, dark skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, medium-light skin tone, medium skin tone +1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, medium-light skin tone, medium skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, medium-light skin tone, medium-dark skin tone +1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, medium-light skin tone, medium-dark skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, medium-light skin tone, dark skin tone +1F9D1 1F3FC 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏼‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, medium-light skin tone, dark skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, medium skin tone, light skin tone +1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, medium skin tone, light skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, medium skin tone, medium-dark skin tone +1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, medium skin tone, medium-dark skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, medium skin tone, dark skin tone +1F9D1 1F3FD 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏽‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, medium skin tone, dark skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍❤️‍💋‍🧑🏿 E13.1 kiss: person, person, medium-dark skin tone, dark skin tone +1F9D1 1F3FE 200D 2764 200D 1F48B 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏾‍❤‍💋‍🧑🏿 E13.1 kiss: person, person, medium-dark skin tone, dark skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏻 E13.1 kiss: person, person, dark skin tone, light skin tone +1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏻 E13.1 kiss: person, person, dark skin tone, light skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏼 E13.1 kiss: person, person, dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏼 E13.1 kiss: person, person, dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏽 E13.1 kiss: person, person, dark skin tone, medium skin tone +1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏽 E13.1 kiss: person, person, dark skin tone, medium skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍❤️‍💋‍🧑🏾 E13.1 kiss: person, person, dark skin tone, medium-dark skin tone +1F9D1 1F3FF 200D 2764 200D 1F48B 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏿‍❤‍💋‍🧑🏾 E13.1 kiss: person, person, dark skin tone, medium-dark skin tone +1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 E2.0 kiss: woman, man +1F469 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👩‍❤‍💋‍👨 E2.0 kiss: woman, man +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, light skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, light skin tone, dark skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏻‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, light skin tone, dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, medium-light skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, medium-light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, medium-light skin tone, dark skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏼‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, medium-light skin tone, dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, medium skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, medium skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, medium skin tone, dark skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏽‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, medium skin tone, dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, medium-dark skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, medium-dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, medium-dark skin tone, dark skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏾‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏻 E13.1 kiss: woman, man, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏻 E13.1 kiss: woman, man, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏼 E13.1 kiss: woman, man, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏼 E13.1 kiss: woman, man, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏽 E13.1 kiss: woman, man, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏽 E13.1 kiss: woman, man, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏾 E13.1 kiss: woman, man, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏾 E13.1 kiss: woman, man, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👩🏿‍❤️‍💋‍👨🏿 E13.1 kiss: woman, man, dark skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👩🏿‍❤‍💋‍👨🏿 E13.1 kiss: woman, man, dark skin tone +1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 E2.0 kiss: man, man +1F468 200D 2764 200D 1F48B 200D 1F468 ; minimally-qualified # 👨‍❤‍💋‍👨 E2.0 kiss: man, man +1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, light skin tone +1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏻 E13.1 kiss: man, man, light skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, light skin tone, medium-light skin tone +1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏼 E13.1 kiss: man, man, light skin tone, medium-light skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, light skin tone, medium skin tone +1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏽 E13.1 kiss: man, man, light skin tone, medium skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, light skin tone, medium-dark skin tone +1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏾 E13.1 kiss: man, man, light skin tone, medium-dark skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, light skin tone, dark skin tone +1F468 1F3FB 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏻‍❤‍💋‍👨🏿 E13.1 kiss: man, man, light skin tone, dark skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, medium-light skin tone, light skin tone +1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏻 E13.1 kiss: man, man, medium-light skin tone, light skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, medium-light skin tone +1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏼 E13.1 kiss: man, man, medium-light skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, medium-light skin tone, medium skin tone +1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏽 E13.1 kiss: man, man, medium-light skin tone, medium skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, medium-light skin tone, medium-dark skin tone +1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏾 E13.1 kiss: man, man, medium-light skin tone, medium-dark skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, medium-light skin tone, dark skin tone +1F468 1F3FC 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏼‍❤‍💋‍👨🏿 E13.1 kiss: man, man, medium-light skin tone, dark skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, medium skin tone, light skin tone +1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏻 E13.1 kiss: man, man, medium skin tone, light skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, medium skin tone, medium-light skin tone +1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏼 E13.1 kiss: man, man, medium skin tone, medium-light skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, medium skin tone +1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏽 E13.1 kiss: man, man, medium skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, medium skin tone, medium-dark skin tone +1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏾 E13.1 kiss: man, man, medium skin tone, medium-dark skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, medium skin tone, dark skin tone +1F468 1F3FD 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏽‍❤‍💋‍👨🏿 E13.1 kiss: man, man, medium skin tone, dark skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, medium-dark skin tone, light skin tone +1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏻 E13.1 kiss: man, man, medium-dark skin tone, light skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏼 E13.1 kiss: man, man, medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, medium-dark skin tone, medium skin tone +1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏽 E13.1 kiss: man, man, medium-dark skin tone, medium skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, medium-dark skin tone +1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏾 E13.1 kiss: man, man, medium-dark skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, medium-dark skin tone, dark skin tone +1F468 1F3FE 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏾‍❤‍💋‍👨🏿 E13.1 kiss: man, man, medium-dark skin tone, dark skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏻 E13.1 kiss: man, man, dark skin tone, light skin tone +1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FB ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏻 E13.1 kiss: man, man, dark skin tone, light skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏼 E13.1 kiss: man, man, dark skin tone, medium-light skin tone +1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FC ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏼 E13.1 kiss: man, man, dark skin tone, medium-light skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏽 E13.1 kiss: man, man, dark skin tone, medium skin tone +1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FD ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏽 E13.1 kiss: man, man, dark skin tone, medium skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏾 E13.1 kiss: man, man, dark skin tone, medium-dark skin tone +1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FE ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏾 E13.1 kiss: man, man, dark skin tone, medium-dark skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F468 1F3FF ; fully-qualified # 👨🏿‍❤️‍💋‍👨🏿 E13.1 kiss: man, man, dark skin tone +1F468 1F3FF 200D 2764 200D 1F48B 200D 1F468 1F3FF ; minimally-qualified # 👨🏿‍❤‍💋‍👨🏿 E13.1 kiss: man, man, dark skin tone +1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 E2.0 kiss: woman, woman +1F469 200D 2764 200D 1F48B 200D 1F469 ; minimally-qualified # 👩‍❤‍💋‍👩 E2.0 kiss: woman, woman +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, light skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, light skin tone, dark skin tone +1F469 1F3FB 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏻‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, light skin tone, dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-light skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-light skin tone, dark skin tone +1F469 1F3FC 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏼‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-light skin tone, dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, medium skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, medium skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, medium skin tone, dark skin tone +1F469 1F3FD 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏽‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, medium skin tone, dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-dark skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, medium-dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-dark skin tone, dark skin tone +1F469 1F3FE 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏾‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏻 E13.1 kiss: woman, woman, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FB ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏻 E13.1 kiss: woman, woman, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏼 E13.1 kiss: woman, woman, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FC ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏼 E13.1 kiss: woman, woman, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏽 E13.1 kiss: woman, woman, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FD ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏽 E13.1 kiss: woman, woman, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏾 E13.1 kiss: woman, woman, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FE ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏾 E13.1 kiss: woman, woman, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F48B 200D 1F469 1F3FF ; fully-qualified # 👩🏿‍❤️‍💋‍👩🏿 E13.1 kiss: woman, woman, dark skin tone +1F469 1F3FF 200D 2764 200D 1F48B 200D 1F469 1F3FF ; minimally-qualified # 👩🏿‍❤‍💋‍👩🏿 E13.1 kiss: woman, woman, dark skin tone +1F491 ; fully-qualified # 💑 E0.6 couple with heart +1F491 1F3FB ; fully-qualified # 💑🏻 E13.1 couple with heart: light skin tone +1F491 1F3FC ; fully-qualified # 💑🏼 E13.1 couple with heart: medium-light skin tone +1F491 1F3FD ; fully-qualified # 💑🏽 E13.1 couple with heart: medium skin tone +1F491 1F3FE ; fully-qualified # 💑🏾 E13.1 couple with heart: medium-dark skin tone +1F491 1F3FF ; fully-qualified # 💑🏿 E13.1 couple with heart: dark skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏻‍❤️‍🧑🏼 E13.1 couple with heart: person, person, light skin tone, medium-light skin tone +1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏻‍❤‍🧑🏼 E13.1 couple with heart: person, person, light skin tone, medium-light skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏻‍❤️‍🧑🏽 E13.1 couple with heart: person, person, light skin tone, medium skin tone +1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏻‍❤‍🧑🏽 E13.1 couple with heart: person, person, light skin tone, medium skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏻‍❤️‍🧑🏾 E13.1 couple with heart: person, person, light skin tone, medium-dark skin tone +1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏻‍❤‍🧑🏾 E13.1 couple with heart: person, person, light skin tone, medium-dark skin tone +1F9D1 1F3FB 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏻‍❤️‍🧑🏿 E13.1 couple with heart: person, person, light skin tone, dark skin tone +1F9D1 1F3FB 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏻‍❤‍🧑🏿 E13.1 couple with heart: person, person, light skin tone, dark skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏼‍❤️‍🧑🏻 E13.1 couple with heart: person, person, medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏼‍❤‍🧑🏻 E13.1 couple with heart: person, person, medium-light skin tone, light skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏼‍❤️‍🧑🏽 E13.1 couple with heart: person, person, medium-light skin tone, medium skin tone +1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏼‍❤‍🧑🏽 E13.1 couple with heart: person, person, medium-light skin tone, medium skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏼‍❤️‍🧑🏾 E13.1 couple with heart: person, person, medium-light skin tone, medium-dark skin tone +1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏼‍❤‍🧑🏾 E13.1 couple with heart: person, person, medium-light skin tone, medium-dark skin tone +1F9D1 1F3FC 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏼‍❤️‍🧑🏿 E13.1 couple with heart: person, person, medium-light skin tone, dark skin tone +1F9D1 1F3FC 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏼‍❤‍🧑🏿 E13.1 couple with heart: person, person, medium-light skin tone, dark skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏽‍❤️‍🧑🏻 E13.1 couple with heart: person, person, medium skin tone, light skin tone +1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏽‍❤‍🧑🏻 E13.1 couple with heart: person, person, medium skin tone, light skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏽‍❤️‍🧑🏼 E13.1 couple with heart: person, person, medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏽‍❤‍🧑🏼 E13.1 couple with heart: person, person, medium skin tone, medium-light skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏽‍❤️‍🧑🏾 E13.1 couple with heart: person, person, medium skin tone, medium-dark skin tone +1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏽‍❤‍🧑🏾 E13.1 couple with heart: person, person, medium skin tone, medium-dark skin tone +1F9D1 1F3FD 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏽‍❤️‍🧑🏿 E13.1 couple with heart: person, person, medium skin tone, dark skin tone +1F9D1 1F3FD 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏽‍❤‍🧑🏿 E13.1 couple with heart: person, person, medium skin tone, dark skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏾‍❤️‍🧑🏻 E13.1 couple with heart: person, person, medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏾‍❤‍🧑🏻 E13.1 couple with heart: person, person, medium-dark skin tone, light skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏾‍❤️‍🧑🏼 E13.1 couple with heart: person, person, medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏾‍❤‍🧑🏼 E13.1 couple with heart: person, person, medium-dark skin tone, medium-light skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏾‍❤️‍🧑🏽 E13.1 couple with heart: person, person, medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏾‍❤‍🧑🏽 E13.1 couple with heart: person, person, medium-dark skin tone, medium skin tone +1F9D1 1F3FE 200D 2764 FE0F 200D 1F9D1 1F3FF ; fully-qualified # 🧑🏾‍❤️‍🧑🏿 E13.1 couple with heart: person, person, medium-dark skin tone, dark skin tone +1F9D1 1F3FE 200D 2764 200D 1F9D1 1F3FF ; minimally-qualified # 🧑🏾‍❤‍🧑🏿 E13.1 couple with heart: person, person, medium-dark skin tone, dark skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FB ; fully-qualified # 🧑🏿‍❤️‍🧑🏻 E13.1 couple with heart: person, person, dark skin tone, light skin tone +1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FB ; minimally-qualified # 🧑🏿‍❤‍🧑🏻 E13.1 couple with heart: person, person, dark skin tone, light skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FC ; fully-qualified # 🧑🏿‍❤️‍🧑🏼 E13.1 couple with heart: person, person, dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FC ; minimally-qualified # 🧑🏿‍❤‍🧑🏼 E13.1 couple with heart: person, person, dark skin tone, medium-light skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FD ; fully-qualified # 🧑🏿‍❤️‍🧑🏽 E13.1 couple with heart: person, person, dark skin tone, medium skin tone +1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FD ; minimally-qualified # 🧑🏿‍❤‍🧑🏽 E13.1 couple with heart: person, person, dark skin tone, medium skin tone +1F9D1 1F3FF 200D 2764 FE0F 200D 1F9D1 1F3FE ; fully-qualified # 🧑🏿‍❤️‍🧑🏾 E13.1 couple with heart: person, person, dark skin tone, medium-dark skin tone +1F9D1 1F3FF 200D 2764 200D 1F9D1 1F3FE ; minimally-qualified # 🧑🏿‍❤‍🧑🏾 E13.1 couple with heart: person, person, dark skin tone, medium-dark skin tone +1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 E2.0 couple with heart: woman, man +1F469 200D 2764 200D 1F468 ; minimally-qualified # 👩‍❤‍👨 E2.0 couple with heart: woman, man +1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏻‍❤️‍👨🏻 E13.1 couple with heart: woman, man, light skin tone +1F469 1F3FB 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏻‍❤‍👨🏻 E13.1 couple with heart: woman, man, light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏻‍❤️‍👨🏼 E13.1 couple with heart: woman, man, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏻‍❤‍👨🏼 E13.1 couple with heart: woman, man, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏻‍❤️‍👨🏽 E13.1 couple with heart: woman, man, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏻‍❤‍👨🏽 E13.1 couple with heart: woman, man, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏻‍❤️‍👨🏾 E13.1 couple with heart: woman, man, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏻‍❤‍👨🏾 E13.1 couple with heart: woman, man, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏻‍❤️‍👨🏿 E13.1 couple with heart: woman, man, light skin tone, dark skin tone +1F469 1F3FB 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏻‍❤‍👨🏿 E13.1 couple with heart: woman, man, light skin tone, dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏼‍❤️‍👨🏻 E13.1 couple with heart: woman, man, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏼‍❤‍👨🏻 E13.1 couple with heart: woman, man, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏼‍❤️‍👨🏼 E13.1 couple with heart: woman, man, medium-light skin tone +1F469 1F3FC 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏼‍❤‍👨🏼 E13.1 couple with heart: woman, man, medium-light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏼‍❤️‍👨🏽 E13.1 couple with heart: woman, man, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏼‍❤‍👨🏽 E13.1 couple with heart: woman, man, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏼‍❤️‍👨🏾 E13.1 couple with heart: woman, man, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏼‍❤‍👨🏾 E13.1 couple with heart: woman, man, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏼‍❤️‍👨🏿 E13.1 couple with heart: woman, man, medium-light skin tone, dark skin tone +1F469 1F3FC 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏼‍❤‍👨🏿 E13.1 couple with heart: woman, man, medium-light skin tone, dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏽‍❤️‍👨🏻 E13.1 couple with heart: woman, man, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏽‍❤‍👨🏻 E13.1 couple with heart: woman, man, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏽‍❤️‍👨🏼 E13.1 couple with heart: woman, man, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏽‍❤‍👨🏼 E13.1 couple with heart: woman, man, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏽‍❤️‍👨🏽 E13.1 couple with heart: woman, man, medium skin tone +1F469 1F3FD 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏽‍❤‍👨🏽 E13.1 couple with heart: woman, man, medium skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏽‍❤️‍👨🏾 E13.1 couple with heart: woman, man, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏽‍❤‍👨🏾 E13.1 couple with heart: woman, man, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏽‍❤️‍👨🏿 E13.1 couple with heart: woman, man, medium skin tone, dark skin tone +1F469 1F3FD 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏽‍❤‍👨🏿 E13.1 couple with heart: woman, man, medium skin tone, dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏾‍❤️‍👨🏻 E13.1 couple with heart: woman, man, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏾‍❤‍👨🏻 E13.1 couple with heart: woman, man, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏾‍❤️‍👨🏼 E13.1 couple with heart: woman, man, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏾‍❤‍👨🏼 E13.1 couple with heart: woman, man, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏾‍❤️‍👨🏽 E13.1 couple with heart: woman, man, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏾‍❤‍👨🏽 E13.1 couple with heart: woman, man, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏾‍❤️‍👨🏾 E13.1 couple with heart: woman, man, medium-dark skin tone +1F469 1F3FE 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏾‍❤‍👨🏾 E13.1 couple with heart: woman, man, medium-dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏾‍❤️‍👨🏿 E13.1 couple with heart: woman, man, medium-dark skin tone, dark skin tone +1F469 1F3FE 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏾‍❤‍👨🏿 E13.1 couple with heart: woman, man, medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👩🏿‍❤️‍👨🏻 E13.1 couple with heart: woman, man, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👩🏿‍❤‍👨🏻 E13.1 couple with heart: woman, man, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👩🏿‍❤️‍👨🏼 E13.1 couple with heart: woman, man, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👩🏿‍❤‍👨🏼 E13.1 couple with heart: woman, man, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👩🏿‍❤️‍👨🏽 E13.1 couple with heart: woman, man, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👩🏿‍❤‍👨🏽 E13.1 couple with heart: woman, man, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👩🏿‍❤️‍👨🏾 E13.1 couple with heart: woman, man, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👩🏿‍❤‍👨🏾 E13.1 couple with heart: woman, man, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👩🏿‍❤️‍👨🏿 E13.1 couple with heart: woman, man, dark skin tone +1F469 1F3FF 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👩🏿‍❤‍👨🏿 E13.1 couple with heart: woman, man, dark skin tone +1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 E2.0 couple with heart: man, man +1F468 200D 2764 200D 1F468 ; minimally-qualified # 👨‍❤‍👨 E2.0 couple with heart: man, man +1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏻‍❤️‍👨🏻 E13.1 couple with heart: man, man, light skin tone +1F468 1F3FB 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏻‍❤‍👨🏻 E13.1 couple with heart: man, man, light skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏻‍❤️‍👨🏼 E13.1 couple with heart: man, man, light skin tone, medium-light skin tone +1F468 1F3FB 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏻‍❤‍👨🏼 E13.1 couple with heart: man, man, light skin tone, medium-light skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏻‍❤️‍👨🏽 E13.1 couple with heart: man, man, light skin tone, medium skin tone +1F468 1F3FB 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏻‍❤‍👨🏽 E13.1 couple with heart: man, man, light skin tone, medium skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏻‍❤️‍👨🏾 E13.1 couple with heart: man, man, light skin tone, medium-dark skin tone +1F468 1F3FB 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏻‍❤‍👨🏾 E13.1 couple with heart: man, man, light skin tone, medium-dark skin tone +1F468 1F3FB 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏻‍❤️‍👨🏿 E13.1 couple with heart: man, man, light skin tone, dark skin tone +1F468 1F3FB 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏻‍❤‍👨🏿 E13.1 couple with heart: man, man, light skin tone, dark skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏼‍❤️‍👨🏻 E13.1 couple with heart: man, man, medium-light skin tone, light skin tone +1F468 1F3FC 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏼‍❤‍👨🏻 E13.1 couple with heart: man, man, medium-light skin tone, light skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏼‍❤️‍👨🏼 E13.1 couple with heart: man, man, medium-light skin tone +1F468 1F3FC 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏼‍❤‍👨🏼 E13.1 couple with heart: man, man, medium-light skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏼‍❤️‍👨🏽 E13.1 couple with heart: man, man, medium-light skin tone, medium skin tone +1F468 1F3FC 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏼‍❤‍👨🏽 E13.1 couple with heart: man, man, medium-light skin tone, medium skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏼‍❤️‍👨🏾 E13.1 couple with heart: man, man, medium-light skin tone, medium-dark skin tone +1F468 1F3FC 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏼‍❤‍👨🏾 E13.1 couple with heart: man, man, medium-light skin tone, medium-dark skin tone +1F468 1F3FC 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏼‍❤️‍👨🏿 E13.1 couple with heart: man, man, medium-light skin tone, dark skin tone +1F468 1F3FC 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏼‍❤‍👨🏿 E13.1 couple with heart: man, man, medium-light skin tone, dark skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏽‍❤️‍👨🏻 E13.1 couple with heart: man, man, medium skin tone, light skin tone +1F468 1F3FD 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏽‍❤‍👨🏻 E13.1 couple with heart: man, man, medium skin tone, light skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏽‍❤️‍👨🏼 E13.1 couple with heart: man, man, medium skin tone, medium-light skin tone +1F468 1F3FD 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏽‍❤‍👨🏼 E13.1 couple with heart: man, man, medium skin tone, medium-light skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏽‍❤️‍👨🏽 E13.1 couple with heart: man, man, medium skin tone +1F468 1F3FD 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏽‍❤‍👨🏽 E13.1 couple with heart: man, man, medium skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏽‍❤️‍👨🏾 E13.1 couple with heart: man, man, medium skin tone, medium-dark skin tone +1F468 1F3FD 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏽‍❤‍👨🏾 E13.1 couple with heart: man, man, medium skin tone, medium-dark skin tone +1F468 1F3FD 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏽‍❤️‍👨🏿 E13.1 couple with heart: man, man, medium skin tone, dark skin tone +1F468 1F3FD 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏽‍❤‍👨🏿 E13.1 couple with heart: man, man, medium skin tone, dark skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏾‍❤️‍👨🏻 E13.1 couple with heart: man, man, medium-dark skin tone, light skin tone +1F468 1F3FE 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏾‍❤‍👨🏻 E13.1 couple with heart: man, man, medium-dark skin tone, light skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏾‍❤️‍👨🏼 E13.1 couple with heart: man, man, medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏾‍❤‍👨🏼 E13.1 couple with heart: man, man, medium-dark skin tone, medium-light skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏾‍❤️‍👨🏽 E13.1 couple with heart: man, man, medium-dark skin tone, medium skin tone +1F468 1F3FE 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏾‍❤‍👨🏽 E13.1 couple with heart: man, man, medium-dark skin tone, medium skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏾‍❤️‍👨🏾 E13.1 couple with heart: man, man, medium-dark skin tone +1F468 1F3FE 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏾‍❤‍👨🏾 E13.1 couple with heart: man, man, medium-dark skin tone +1F468 1F3FE 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏾‍❤️‍👨🏿 E13.1 couple with heart: man, man, medium-dark skin tone, dark skin tone +1F468 1F3FE 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏾‍❤‍👨🏿 E13.1 couple with heart: man, man, medium-dark skin tone, dark skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FB ; fully-qualified # 👨🏿‍❤️‍👨🏻 E13.1 couple with heart: man, man, dark skin tone, light skin tone +1F468 1F3FF 200D 2764 200D 1F468 1F3FB ; minimally-qualified # 👨🏿‍❤‍👨🏻 E13.1 couple with heart: man, man, dark skin tone, light skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FC ; fully-qualified # 👨🏿‍❤️‍👨🏼 E13.1 couple with heart: man, man, dark skin tone, medium-light skin tone +1F468 1F3FF 200D 2764 200D 1F468 1F3FC ; minimally-qualified # 👨🏿‍❤‍👨🏼 E13.1 couple with heart: man, man, dark skin tone, medium-light skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FD ; fully-qualified # 👨🏿‍❤️‍👨🏽 E13.1 couple with heart: man, man, dark skin tone, medium skin tone +1F468 1F3FF 200D 2764 200D 1F468 1F3FD ; minimally-qualified # 👨🏿‍❤‍👨🏽 E13.1 couple with heart: man, man, dark skin tone, medium skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FE ; fully-qualified # 👨🏿‍❤️‍👨🏾 E13.1 couple with heart: man, man, dark skin tone, medium-dark skin tone +1F468 1F3FF 200D 2764 200D 1F468 1F3FE ; minimally-qualified # 👨🏿‍❤‍👨🏾 E13.1 couple with heart: man, man, dark skin tone, medium-dark skin tone +1F468 1F3FF 200D 2764 FE0F 200D 1F468 1F3FF ; fully-qualified # 👨🏿‍❤️‍👨🏿 E13.1 couple with heart: man, man, dark skin tone +1F468 1F3FF 200D 2764 200D 1F468 1F3FF ; minimally-qualified # 👨🏿‍❤‍👨🏿 E13.1 couple with heart: man, man, dark skin tone +1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 E2.0 couple with heart: woman, woman +1F469 200D 2764 200D 1F469 ; minimally-qualified # 👩‍❤‍👩 E2.0 couple with heart: woman, woman +1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏻‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, light skin tone +1F469 1F3FB 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏻‍❤‍👩🏻 E13.1 couple with heart: woman, woman, light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏻‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏻‍❤‍👩🏼 E13.1 couple with heart: woman, woman, light skin tone, medium-light skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏻‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏻‍❤‍👩🏽 E13.1 couple with heart: woman, woman, light skin tone, medium skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏻‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏻‍❤‍👩🏾 E13.1 couple with heart: woman, woman, light skin tone, medium-dark skin tone +1F469 1F3FB 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏻‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, light skin tone, dark skin tone +1F469 1F3FB 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏻‍❤‍👩🏿 E13.1 couple with heart: woman, woman, light skin tone, dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏼‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏼‍❤‍👩🏻 E13.1 couple with heart: woman, woman, medium-light skin tone, light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏼‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, medium-light skin tone +1F469 1F3FC 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏼‍❤‍👩🏼 E13.1 couple with heart: woman, woman, medium-light skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏼‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏼‍❤‍👩🏽 E13.1 couple with heart: woman, woman, medium-light skin tone, medium skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏼‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏼‍❤‍👩🏾 E13.1 couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone +1F469 1F3FC 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏼‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, medium-light skin tone, dark skin tone +1F469 1F3FC 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏼‍❤‍👩🏿 E13.1 couple with heart: woman, woman, medium-light skin tone, dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏽‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏽‍❤‍👩🏻 E13.1 couple with heart: woman, woman, medium skin tone, light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏽‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏽‍❤‍👩🏼 E13.1 couple with heart: woman, woman, medium skin tone, medium-light skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏽‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, medium skin tone +1F469 1F3FD 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏽‍❤‍👩🏽 E13.1 couple with heart: woman, woman, medium skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏽‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏽‍❤‍👩🏾 E13.1 couple with heart: woman, woman, medium skin tone, medium-dark skin tone +1F469 1F3FD 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏽‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, medium skin tone, dark skin tone +1F469 1F3FD 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏽‍❤‍👩🏿 E13.1 couple with heart: woman, woman, medium skin tone, dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏾‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏾‍❤‍👩🏻 E13.1 couple with heart: woman, woman, medium-dark skin tone, light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏾‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏾‍❤‍👩🏼 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏾‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏾‍❤‍👩🏽 E13.1 couple with heart: woman, woman, medium-dark skin tone, medium skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏾‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, medium-dark skin tone +1F469 1F3FE 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏾‍❤‍👩🏾 E13.1 couple with heart: woman, woman, medium-dark skin tone +1F469 1F3FE 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏾‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, medium-dark skin tone, dark skin tone +1F469 1F3FE 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏾‍❤‍👩🏿 E13.1 couple with heart: woman, woman, medium-dark skin tone, dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FB ; fully-qualified # 👩🏿‍❤️‍👩🏻 E13.1 couple with heart: woman, woman, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 200D 1F469 1F3FB ; minimally-qualified # 👩🏿‍❤‍👩🏻 E13.1 couple with heart: woman, woman, dark skin tone, light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FC ; fully-qualified # 👩🏿‍❤️‍👩🏼 E13.1 couple with heart: woman, woman, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 200D 1F469 1F3FC ; minimally-qualified # 👩🏿‍❤‍👩🏼 E13.1 couple with heart: woman, woman, dark skin tone, medium-light skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FD ; fully-qualified # 👩🏿‍❤️‍👩🏽 E13.1 couple with heart: woman, woman, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 200D 1F469 1F3FD ; minimally-qualified # 👩🏿‍❤‍👩🏽 E13.1 couple with heart: woman, woman, dark skin tone, medium skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FE ; fully-qualified # 👩🏿‍❤️‍👩🏾 E13.1 couple with heart: woman, woman, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 200D 1F469 1F3FE ; minimally-qualified # 👩🏿‍❤‍👩🏾 E13.1 couple with heart: woman, woman, dark skin tone, medium-dark skin tone +1F469 1F3FF 200D 2764 FE0F 200D 1F469 1F3FF ; fully-qualified # 👩🏿‍❤️‍👩🏿 E13.1 couple with heart: woman, woman, dark skin tone +1F469 1F3FF 200D 2764 200D 1F469 1F3FF ; minimally-qualified # 👩🏿‍❤‍👩🏿 E13.1 couple with heart: woman, woman, dark skin tone +1F46A ; fully-qualified # 👪 E0.6 family +1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 E2.0 family: man, woman, boy +1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 E2.0 family: man, woman, girl +1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 E2.0 family: man, woman, girl, boy +1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 E2.0 family: man, woman, boy, boy +1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 E2.0 family: man, woman, girl, girl +1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 E2.0 family: man, man, boy +1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 E2.0 family: man, man, girl +1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 E2.0 family: man, man, girl, boy +1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 E2.0 family: man, man, boy, boy +1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 E2.0 family: man, man, girl, girl +1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 E2.0 family: woman, woman, boy +1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 E2.0 family: woman, woman, girl +1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 E2.0 family: woman, woman, girl, boy +1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 E2.0 family: woman, woman, boy, boy +1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 E2.0 family: woman, woman, girl, girl +1F468 200D 1F466 ; fully-qualified # 👨‍👦 E4.0 family: man, boy +1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 E4.0 family: man, boy, boy +1F468 200D 1F467 ; fully-qualified # 👨‍👧 E4.0 family: man, girl +1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 E4.0 family: man, girl, boy +1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 E4.0 family: man, girl, girl +1F469 200D 1F466 ; fully-qualified # 👩‍👦 E4.0 family: woman, boy +1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 E4.0 family: woman, boy, boy +1F469 200D 1F467 ; fully-qualified # 👩‍👧 E4.0 family: woman, girl +1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 E4.0 family: woman, girl, boy +1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 E4.0 family: woman, girl, girl + +# subgroup: person-symbol +1F5E3 FE0F ; fully-qualified # 🗣️ E0.7 speaking head +1F5E3 ; unqualified # 🗣 E0.7 speaking head +1F464 ; fully-qualified # 👤 E0.6 bust in silhouette +1F465 ; fully-qualified # 👥 E1.0 busts in silhouette +1FAC2 ; fully-qualified # 🫂 E13.0 people hugging +1F463 ; fully-qualified # 👣 E0.6 footprints + +# People & Body subtotal: 2899 +# People & Body subtotal: 494 w/o modifiers + +# group: Component + +# subgroup: skin-tone +1F3FB ; component # 🏻 E1.0 light skin tone +1F3FC ; component # 🏼 E1.0 medium-light skin tone +1F3FD ; component # 🏽 E1.0 medium skin tone +1F3FE ; component # 🏾 E1.0 medium-dark skin tone +1F3FF ; component # 🏿 E1.0 dark skin tone + +# subgroup: hair-style +1F9B0 ; component # 🦰 E11.0 red hair +1F9B1 ; component # 🦱 E11.0 curly hair +1F9B3 ; component # 🦳 E11.0 white hair +1F9B2 ; component # 🦲 E11.0 bald + +# Component subtotal: 9 +# Component subtotal: 4 w/o modifiers + +# group: Animals & Nature + +# subgroup: animal-mammal +1F435 ; fully-qualified # 🐵 E0.6 monkey face +1F412 ; fully-qualified # 🐒 E0.6 monkey +1F98D ; fully-qualified # 🦍 E3.0 gorilla +1F9A7 ; fully-qualified # 🦧 E12.0 orangutan +1F436 ; fully-qualified # 🐶 E0.6 dog face +1F415 ; fully-qualified # 🐕 E0.7 dog +1F9AE ; fully-qualified # 🦮 E12.0 guide dog +1F415 200D 1F9BA ; fully-qualified # 🐕‍🦺 E12.0 service dog +1F429 ; fully-qualified # 🐩 E0.6 poodle +1F43A ; fully-qualified # 🐺 E0.6 wolf +1F98A ; fully-qualified # 🦊 E3.0 fox +1F99D ; fully-qualified # 🦝 E11.0 raccoon +1F431 ; fully-qualified # 🐱 E0.6 cat face +1F408 ; fully-qualified # 🐈 E0.7 cat +1F408 200D 2B1B ; fully-qualified # 🐈‍⬛ E13.0 black cat +1F981 ; fully-qualified # 🦁 E1.0 lion +1F42F ; fully-qualified # 🐯 E0.6 tiger face +1F405 ; fully-qualified # 🐅 E1.0 tiger +1F406 ; fully-qualified # 🐆 E1.0 leopard +1F434 ; fully-qualified # 🐴 E0.6 horse face +1F40E ; fully-qualified # 🐎 E0.6 horse +1F984 ; fully-qualified # 🦄 E1.0 unicorn +1F993 ; fully-qualified # 🦓 E5.0 zebra +1F98C ; fully-qualified # 🦌 E3.0 deer +1F9AC ; fully-qualified # 🦬 E13.0 bison +1F42E ; fully-qualified # 🐮 E0.6 cow face +1F402 ; fully-qualified # 🐂 E1.0 ox +1F403 ; fully-qualified # 🐃 E1.0 water buffalo +1F404 ; fully-qualified # 🐄 E1.0 cow +1F437 ; fully-qualified # 🐷 E0.6 pig face +1F416 ; fully-qualified # 🐖 E1.0 pig +1F417 ; fully-qualified # 🐗 E0.6 boar +1F43D ; fully-qualified # 🐽 E0.6 pig nose +1F40F ; fully-qualified # 🐏 E1.0 ram +1F411 ; fully-qualified # 🐑 E0.6 ewe +1F410 ; fully-qualified # 🐐 E1.0 goat +1F42A ; fully-qualified # 🐪 E1.0 camel +1F42B ; fully-qualified # 🐫 E0.6 two-hump camel +1F999 ; fully-qualified # 🦙 E11.0 llama +1F992 ; fully-qualified # 🦒 E5.0 giraffe +1F418 ; fully-qualified # 🐘 E0.6 elephant +1F9A3 ; fully-qualified # 🦣 E13.0 mammoth +1F98F ; fully-qualified # 🦏 E3.0 rhinoceros +1F99B ; fully-qualified # 🦛 E11.0 hippopotamus +1F42D ; fully-qualified # 🐭 E0.6 mouse face +1F401 ; fully-qualified # 🐁 E1.0 mouse +1F400 ; fully-qualified # 🐀 E1.0 rat +1F439 ; fully-qualified # 🐹 E0.6 hamster +1F430 ; fully-qualified # 🐰 E0.6 rabbit face +1F407 ; fully-qualified # 🐇 E1.0 rabbit +1F43F FE0F ; fully-qualified # 🐿️ E0.7 chipmunk +1F43F ; unqualified # 🐿 E0.7 chipmunk +1F9AB ; fully-qualified # 🦫 E13.0 beaver +1F994 ; fully-qualified # 🦔 E5.0 hedgehog +1F987 ; fully-qualified # 🦇 E3.0 bat +1F43B ; fully-qualified # 🐻 E0.6 bear +1F43B 200D 2744 FE0F ; fully-qualified # 🐻‍❄️ E13.0 polar bear +1F43B 200D 2744 ; minimally-qualified # 🐻‍❄ E13.0 polar bear +1F428 ; fully-qualified # 🐨 E0.6 koala +1F43C ; fully-qualified # 🐼 E0.6 panda +1F9A5 ; fully-qualified # 🦥 E12.0 sloth +1F9A6 ; fully-qualified # 🦦 E12.0 otter +1F9A8 ; fully-qualified # 🦨 E12.0 skunk +1F998 ; fully-qualified # 🦘 E11.0 kangaroo +1F9A1 ; fully-qualified # 🦡 E11.0 badger +1F43E ; fully-qualified # 🐾 E0.6 paw prints + +# subgroup: animal-bird +1F983 ; fully-qualified # 🦃 E1.0 turkey +1F414 ; fully-qualified # 🐔 E0.6 chicken +1F413 ; fully-qualified # 🐓 E1.0 rooster +1F423 ; fully-qualified # 🐣 E0.6 hatching chick +1F424 ; fully-qualified # 🐤 E0.6 baby chick +1F425 ; fully-qualified # 🐥 E0.6 front-facing baby chick +1F426 ; fully-qualified # 🐦 E0.6 bird +1F427 ; fully-qualified # 🐧 E0.6 penguin +1F54A FE0F ; fully-qualified # 🕊️ E0.7 dove +1F54A ; unqualified # 🕊 E0.7 dove +1F985 ; fully-qualified # 🦅 E3.0 eagle +1F986 ; fully-qualified # 🦆 E3.0 duck +1F9A2 ; fully-qualified # 🦢 E11.0 swan +1F989 ; fully-qualified # 🦉 E3.0 owl +1F9A4 ; fully-qualified # 🦤 E13.0 dodo +1FAB6 ; fully-qualified # 🪶 E13.0 feather +1F9A9 ; fully-qualified # 🦩 E12.0 flamingo +1F99A ; fully-qualified # 🦚 E11.0 peacock +1F99C ; fully-qualified # 🦜 E11.0 parrot + +# subgroup: animal-amphibian +1F438 ; fully-qualified # 🐸 E0.6 frog + +# subgroup: animal-reptile +1F40A ; fully-qualified # 🐊 E1.0 crocodile +1F422 ; fully-qualified # 🐢 E0.6 turtle +1F98E ; fully-qualified # 🦎 E3.0 lizard +1F40D ; fully-qualified # 🐍 E0.6 snake +1F432 ; fully-qualified # 🐲 E0.6 dragon face +1F409 ; fully-qualified # 🐉 E1.0 dragon +1F995 ; fully-qualified # 🦕 E5.0 sauropod +1F996 ; fully-qualified # 🦖 E5.0 T-Rex + +# subgroup: animal-marine +1F433 ; fully-qualified # 🐳 E0.6 spouting whale +1F40B ; fully-qualified # 🐋 E1.0 whale +1F42C ; fully-qualified # 🐬 E0.6 dolphin +1F9AD ; fully-qualified # 🦭 E13.0 seal +1F41F ; fully-qualified # 🐟 E0.6 fish +1F420 ; fully-qualified # 🐠 E0.6 tropical fish +1F421 ; fully-qualified # 🐡 E0.6 blowfish +1F988 ; fully-qualified # 🦈 E3.0 shark +1F419 ; fully-qualified # 🐙 E0.6 octopus +1F41A ; fully-qualified # 🐚 E0.6 spiral shell + +# subgroup: animal-bug +1F40C ; fully-qualified # 🐌 E0.6 snail +1F98B ; fully-qualified # 🦋 E3.0 butterfly +1F41B ; fully-qualified # 🐛 E0.6 bug +1F41C ; fully-qualified # 🐜 E0.6 ant +1F41D ; fully-qualified # 🐝 E0.6 honeybee +1FAB2 ; fully-qualified # 🪲 E13.0 beetle +1F41E ; fully-qualified # 🐞 E0.6 lady beetle +1F997 ; fully-qualified # 🦗 E5.0 cricket +1FAB3 ; fully-qualified # 🪳 E13.0 cockroach +1F577 FE0F ; fully-qualified # 🕷️ E0.7 spider +1F577 ; unqualified # 🕷 E0.7 spider +1F578 FE0F ; fully-qualified # 🕸️ E0.7 spider web +1F578 ; unqualified # 🕸 E0.7 spider web +1F982 ; fully-qualified # 🦂 E1.0 scorpion +1F99F ; fully-qualified # 🦟 E11.0 mosquito +1FAB0 ; fully-qualified # 🪰 E13.0 fly +1FAB1 ; fully-qualified # 🪱 E13.0 worm +1F9A0 ; fully-qualified # 🦠 E11.0 microbe + +# subgroup: plant-flower +1F490 ; fully-qualified # 💐 E0.6 bouquet +1F338 ; fully-qualified # 🌸 E0.6 cherry blossom +1F4AE ; fully-qualified # 💮 E0.6 white flower +1F3F5 FE0F ; fully-qualified # 🏵️ E0.7 rosette +1F3F5 ; unqualified # 🏵 E0.7 rosette +1F339 ; fully-qualified # 🌹 E0.6 rose +1F940 ; fully-qualified # 🥀 E3.0 wilted flower +1F33A ; fully-qualified # 🌺 E0.6 hibiscus +1F33B ; fully-qualified # 🌻 E0.6 sunflower +1F33C ; fully-qualified # 🌼 E0.6 blossom +1F337 ; fully-qualified # 🌷 E0.6 tulip + +# subgroup: plant-other +1F331 ; fully-qualified # 🌱 E0.6 seedling +1FAB4 ; fully-qualified # 🪴 E13.0 potted plant +1F332 ; fully-qualified # 🌲 E1.0 evergreen tree +1F333 ; fully-qualified # 🌳 E1.0 deciduous tree +1F334 ; fully-qualified # 🌴 E0.6 palm tree +1F335 ; fully-qualified # 🌵 E0.6 cactus +1F33E ; fully-qualified # 🌾 E0.6 sheaf of rice +1F33F ; fully-qualified # 🌿 E0.6 herb +2618 FE0F ; fully-qualified # ☘️ E1.0 shamrock +2618 ; unqualified # ☘ E1.0 shamrock +1F340 ; fully-qualified # 🍀 E0.6 four leaf clover +1F341 ; fully-qualified # 🍁 E0.6 maple leaf +1F342 ; fully-qualified # 🍂 E0.6 fallen leaf +1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind + +# Animals & Nature subtotal: 147 +# Animals & Nature subtotal: 147 w/o modifiers + +# group: Food & Drink + +# subgroup: food-fruit +1F347 ; fully-qualified # 🍇 E0.6 grapes +1F348 ; fully-qualified # 🍈 E0.6 melon +1F349 ; fully-qualified # 🍉 E0.6 watermelon +1F34A ; fully-qualified # 🍊 E0.6 tangerine +1F34B ; fully-qualified # 🍋 E1.0 lemon +1F34C ; fully-qualified # 🍌 E0.6 banana +1F34D ; fully-qualified # 🍍 E0.6 pineapple +1F96D ; fully-qualified # 🥭 E11.0 mango +1F34E ; fully-qualified # 🍎 E0.6 red apple +1F34F ; fully-qualified # 🍏 E0.6 green apple +1F350 ; fully-qualified # 🍐 E1.0 pear +1F351 ; fully-qualified # 🍑 E0.6 peach +1F352 ; fully-qualified # 🍒 E0.6 cherries +1F353 ; fully-qualified # 🍓 E0.6 strawberry +1FAD0 ; fully-qualified # 🫐 E13.0 blueberries +1F95D ; fully-qualified # 🥝 E3.0 kiwi fruit +1F345 ; fully-qualified # 🍅 E0.6 tomato +1FAD2 ; fully-qualified # 🫒 E13.0 olive +1F965 ; fully-qualified # 🥥 E5.0 coconut + +# subgroup: food-vegetable +1F951 ; fully-qualified # 🥑 E3.0 avocado +1F346 ; fully-qualified # 🍆 E0.6 eggplant +1F954 ; fully-qualified # 🥔 E3.0 potato +1F955 ; fully-qualified # 🥕 E3.0 carrot +1F33D ; fully-qualified # 🌽 E0.6 ear of corn +1F336 FE0F ; fully-qualified # 🌶️ E0.7 hot pepper +1F336 ; unqualified # 🌶 E0.7 hot pepper +1FAD1 ; fully-qualified # 🫑 E13.0 bell pepper +1F952 ; fully-qualified # 🥒 E3.0 cucumber +1F96C ; fully-qualified # 🥬 E11.0 leafy green +1F966 ; fully-qualified # 🥦 E5.0 broccoli +1F9C4 ; fully-qualified # 🧄 E12.0 garlic +1F9C5 ; fully-qualified # 🧅 E12.0 onion +1F344 ; fully-qualified # 🍄 E0.6 mushroom +1F95C ; fully-qualified # 🥜 E3.0 peanuts +1F330 ; fully-qualified # 🌰 E0.6 chestnut + +# subgroup: food-prepared +1F35E ; fully-qualified # 🍞 E0.6 bread +1F950 ; fully-qualified # 🥐 E3.0 croissant +1F956 ; fully-qualified # 🥖 E3.0 baguette bread +1FAD3 ; fully-qualified # 🫓 E13.0 flatbread +1F968 ; fully-qualified # 🥨 E5.0 pretzel +1F96F ; fully-qualified # 🥯 E11.0 bagel +1F95E ; fully-qualified # 🥞 E3.0 pancakes +1F9C7 ; fully-qualified # 🧇 E12.0 waffle +1F9C0 ; fully-qualified # 🧀 E1.0 cheese wedge +1F356 ; fully-qualified # 🍖 E0.6 meat on bone +1F357 ; fully-qualified # 🍗 E0.6 poultry leg +1F969 ; fully-qualified # 🥩 E5.0 cut of meat +1F953 ; fully-qualified # 🥓 E3.0 bacon +1F354 ; fully-qualified # 🍔 E0.6 hamburger +1F35F ; fully-qualified # 🍟 E0.6 french fries +1F355 ; fully-qualified # 🍕 E0.6 pizza +1F32D ; fully-qualified # 🌭 E1.0 hot dog +1F96A ; fully-qualified # 🥪 E5.0 sandwich +1F32E ; fully-qualified # 🌮 E1.0 taco +1F32F ; fully-qualified # 🌯 E1.0 burrito +1FAD4 ; fully-qualified # 🫔 E13.0 tamale +1F959 ; fully-qualified # 🥙 E3.0 stuffed flatbread +1F9C6 ; fully-qualified # 🧆 E12.0 falafel +1F95A ; fully-qualified # 🥚 E3.0 egg +1F373 ; fully-qualified # 🍳 E0.6 cooking +1F958 ; fully-qualified # 🥘 E3.0 shallow pan of food +1F372 ; fully-qualified # 🍲 E0.6 pot of food +1FAD5 ; fully-qualified # 🫕 E13.0 fondue +1F963 ; fully-qualified # 🥣 E5.0 bowl with spoon +1F957 ; fully-qualified # 🥗 E3.0 green salad +1F37F ; fully-qualified # 🍿 E1.0 popcorn +1F9C8 ; fully-qualified # 🧈 E12.0 butter +1F9C2 ; fully-qualified # 🧂 E11.0 salt +1F96B ; fully-qualified # 🥫 E5.0 canned food + +# subgroup: food-asian +1F371 ; fully-qualified # 🍱 E0.6 bento box +1F358 ; fully-qualified # 🍘 E0.6 rice cracker +1F359 ; fully-qualified # 🍙 E0.6 rice ball +1F35A ; fully-qualified # 🍚 E0.6 cooked rice +1F35B ; fully-qualified # 🍛 E0.6 curry rice +1F35C ; fully-qualified # 🍜 E0.6 steaming bowl +1F35D ; fully-qualified # 🍝 E0.6 spaghetti +1F360 ; fully-qualified # 🍠 E0.6 roasted sweet potato +1F362 ; fully-qualified # 🍢 E0.6 oden +1F363 ; fully-qualified # 🍣 E0.6 sushi +1F364 ; fully-qualified # 🍤 E0.6 fried shrimp +1F365 ; fully-qualified # 🍥 E0.6 fish cake with swirl +1F96E ; fully-qualified # 🥮 E11.0 moon cake +1F361 ; fully-qualified # 🍡 E0.6 dango +1F95F ; fully-qualified # 🥟 E5.0 dumpling +1F960 ; fully-qualified # 🥠 E5.0 fortune cookie +1F961 ; fully-qualified # 🥡 E5.0 takeout box + +# subgroup: food-marine +1F980 ; fully-qualified # 🦀 E1.0 crab +1F99E ; fully-qualified # 🦞 E11.0 lobster +1F990 ; fully-qualified # 🦐 E3.0 shrimp +1F991 ; fully-qualified # 🦑 E3.0 squid +1F9AA ; fully-qualified # 🦪 E12.0 oyster + +# subgroup: food-sweet +1F366 ; fully-qualified # 🍦 E0.6 soft ice cream +1F367 ; fully-qualified # 🍧 E0.6 shaved ice +1F368 ; fully-qualified # 🍨 E0.6 ice cream +1F369 ; fully-qualified # 🍩 E0.6 doughnut +1F36A ; fully-qualified # 🍪 E0.6 cookie +1F382 ; fully-qualified # 🎂 E0.6 birthday cake +1F370 ; fully-qualified # 🍰 E0.6 shortcake +1F9C1 ; fully-qualified # 🧁 E11.0 cupcake +1F967 ; fully-qualified # 🥧 E5.0 pie +1F36B ; fully-qualified # 🍫 E0.6 chocolate bar +1F36C ; fully-qualified # 🍬 E0.6 candy +1F36D ; fully-qualified # 🍭 E0.6 lollipop +1F36E ; fully-qualified # 🍮 E0.6 custard +1F36F ; fully-qualified # 🍯 E0.6 honey pot + +# subgroup: drink +1F37C ; fully-qualified # 🍼 E1.0 baby bottle +1F95B ; fully-qualified # 🥛 E3.0 glass of milk +2615 ; fully-qualified # ☕ E0.6 hot beverage +1FAD6 ; fully-qualified # 🫖 E13.0 teapot +1F375 ; fully-qualified # 🍵 E0.6 teacup without handle +1F376 ; fully-qualified # 🍶 E0.6 sake +1F37E ; fully-qualified # 🍾 E1.0 bottle with popping cork +1F377 ; fully-qualified # 🍷 E0.6 wine glass +1F378 ; fully-qualified # 🍸 E0.6 cocktail glass +1F379 ; fully-qualified # 🍹 E0.6 tropical drink +1F37A ; fully-qualified # 🍺 E0.6 beer mug +1F37B ; fully-qualified # 🍻 E0.6 clinking beer mugs +1F942 ; fully-qualified # 🥂 E3.0 clinking glasses +1F943 ; fully-qualified # 🥃 E3.0 tumbler glass +1F964 ; fully-qualified # 🥤 E5.0 cup with straw +1F9CB ; fully-qualified # 🧋 E13.0 bubble tea +1F9C3 ; fully-qualified # 🧃 E12.0 beverage box +1F9C9 ; fully-qualified # 🧉 E12.0 mate +1F9CA ; fully-qualified # 🧊 E12.0 ice + +# subgroup: dishware +1F962 ; fully-qualified # 🥢 E5.0 chopsticks +1F37D FE0F ; fully-qualified # 🍽️ E0.7 fork and knife with plate +1F37D ; unqualified # 🍽 E0.7 fork and knife with plate +1F374 ; fully-qualified # 🍴 E0.6 fork and knife +1F944 ; fully-qualified # 🥄 E3.0 spoon +1F52A ; fully-qualified # 🔪 E0.6 kitchen knife +1F3FA ; fully-qualified # 🏺 E1.0 amphora + +# Food & Drink subtotal: 131 +# Food & Drink subtotal: 131 w/o modifiers + +# group: Travel & Places + +# subgroup: place-map +1F30D ; fully-qualified # 🌍 E0.7 globe showing Europe-Africa +1F30E ; fully-qualified # 🌎 E0.7 globe showing Americas +1F30F ; fully-qualified # 🌏 E0.6 globe showing Asia-Australia +1F310 ; fully-qualified # 🌐 E1.0 globe with meridians +1F5FA FE0F ; fully-qualified # 🗺️ E0.7 world map +1F5FA ; unqualified # 🗺 E0.7 world map +1F5FE ; fully-qualified # 🗾 E0.6 map of Japan +1F9ED ; fully-qualified # 🧭 E11.0 compass + +# subgroup: place-geographic +1F3D4 FE0F ; fully-qualified # 🏔️ E0.7 snow-capped mountain +1F3D4 ; unqualified # 🏔 E0.7 snow-capped mountain +26F0 FE0F ; fully-qualified # ⛰️ E0.7 mountain +26F0 ; unqualified # ⛰ E0.7 mountain +1F30B ; fully-qualified # 🌋 E0.6 volcano +1F5FB ; fully-qualified # 🗻 E0.6 mount fuji +1F3D5 FE0F ; fully-qualified # 🏕️ E0.7 camping +1F3D5 ; unqualified # 🏕 E0.7 camping +1F3D6 FE0F ; fully-qualified # 🏖️ E0.7 beach with umbrella +1F3D6 ; unqualified # 🏖 E0.7 beach with umbrella +1F3DC FE0F ; fully-qualified # 🏜️ E0.7 desert +1F3DC ; unqualified # 🏜 E0.7 desert +1F3DD FE0F ; fully-qualified # 🏝️ E0.7 desert island +1F3DD ; unqualified # 🏝 E0.7 desert island +1F3DE FE0F ; fully-qualified # 🏞️ E0.7 national park +1F3DE ; unqualified # 🏞 E0.7 national park + +# subgroup: place-building +1F3DF FE0F ; fully-qualified # 🏟️ E0.7 stadium +1F3DF ; unqualified # 🏟 E0.7 stadium +1F3DB FE0F ; fully-qualified # 🏛️ E0.7 classical building +1F3DB ; unqualified # 🏛 E0.7 classical building +1F3D7 FE0F ; fully-qualified # 🏗️ E0.7 building construction +1F3D7 ; unqualified # 🏗 E0.7 building construction +1F9F1 ; fully-qualified # 🧱 E11.0 brick +1FAA8 ; fully-qualified # 🪨 E13.0 rock +1FAB5 ; fully-qualified # 🪵 E13.0 wood +1F6D6 ; fully-qualified # 🛖 E13.0 hut +1F3D8 FE0F ; fully-qualified # 🏘️ E0.7 houses +1F3D8 ; unqualified # 🏘 E0.7 houses +1F3DA FE0F ; fully-qualified # 🏚️ E0.7 derelict house +1F3DA ; unqualified # 🏚 E0.7 derelict house +1F3E0 ; fully-qualified # 🏠 E0.6 house +1F3E1 ; fully-qualified # 🏡 E0.6 house with garden +1F3E2 ; fully-qualified # 🏢 E0.6 office building +1F3E3 ; fully-qualified # 🏣 E0.6 Japanese post office +1F3E4 ; fully-qualified # 🏤 E1.0 post office +1F3E5 ; fully-qualified # 🏥 E0.6 hospital +1F3E6 ; fully-qualified # 🏦 E0.6 bank +1F3E8 ; fully-qualified # 🏨 E0.6 hotel +1F3E9 ; fully-qualified # 🏩 E0.6 love hotel +1F3EA ; fully-qualified # 🏪 E0.6 convenience store +1F3EB ; fully-qualified # 🏫 E0.6 school +1F3EC ; fully-qualified # 🏬 E0.6 department store +1F3ED ; fully-qualified # 🏭 E0.6 factory +1F3EF ; fully-qualified # 🏯 E0.6 Japanese castle +1F3F0 ; fully-qualified # 🏰 E0.6 castle +1F492 ; fully-qualified # 💒 E0.6 wedding +1F5FC ; fully-qualified # 🗼 E0.6 Tokyo tower +1F5FD ; fully-qualified # 🗽 E0.6 Statue of Liberty + +# subgroup: place-religious +26EA ; fully-qualified # ⛪ E0.6 church +1F54C ; fully-qualified # 🕌 E1.0 mosque +1F6D5 ; fully-qualified # 🛕 E12.0 hindu temple +1F54D ; fully-qualified # 🕍 E1.0 synagogue +26E9 FE0F ; fully-qualified # ⛩️ E0.7 shinto shrine +26E9 ; unqualified # ⛩ E0.7 shinto shrine +1F54B ; fully-qualified # 🕋 E1.0 kaaba + +# subgroup: place-other +26F2 ; fully-qualified # ⛲ E0.6 fountain +26FA ; fully-qualified # ⛺ E0.6 tent +1F301 ; fully-qualified # 🌁 E0.6 foggy +1F303 ; fully-qualified # 🌃 E0.6 night with stars +1F3D9 FE0F ; fully-qualified # 🏙️ E0.7 cityscape +1F3D9 ; unqualified # 🏙 E0.7 cityscape +1F304 ; fully-qualified # 🌄 E0.6 sunrise over mountains +1F305 ; fully-qualified # 🌅 E0.6 sunrise +1F306 ; fully-qualified # 🌆 E0.6 cityscape at dusk +1F307 ; fully-qualified # 🌇 E0.6 sunset +1F309 ; fully-qualified # 🌉 E0.6 bridge at night +2668 FE0F ; fully-qualified # ♨️ E0.6 hot springs +2668 ; unqualified # ♨ E0.6 hot springs +1F3A0 ; fully-qualified # 🎠 E0.6 carousel horse +1F3A1 ; fully-qualified # 🎡 E0.6 ferris wheel +1F3A2 ; fully-qualified # 🎢 E0.6 roller coaster +1F488 ; fully-qualified # 💈 E0.6 barber pole +1F3AA ; fully-qualified # 🎪 E0.6 circus tent + +# subgroup: transport-ground +1F682 ; fully-qualified # 🚂 E1.0 locomotive +1F683 ; fully-qualified # 🚃 E0.6 railway car +1F684 ; fully-qualified # 🚄 E0.6 high-speed train +1F685 ; fully-qualified # 🚅 E0.6 bullet train +1F686 ; fully-qualified # 🚆 E1.0 train +1F687 ; fully-qualified # 🚇 E0.6 metro +1F688 ; fully-qualified # 🚈 E1.0 light rail +1F689 ; fully-qualified # 🚉 E0.6 station +1F68A ; fully-qualified # 🚊 E1.0 tram +1F69D ; fully-qualified # 🚝 E1.0 monorail +1F69E ; fully-qualified # 🚞 E1.0 mountain railway +1F68B ; fully-qualified # 🚋 E1.0 tram car +1F68C ; fully-qualified # 🚌 E0.6 bus +1F68D ; fully-qualified # 🚍 E0.7 oncoming bus +1F68E ; fully-qualified # 🚎 E1.0 trolleybus +1F690 ; fully-qualified # 🚐 E1.0 minibus +1F691 ; fully-qualified # 🚑 E0.6 ambulance +1F692 ; fully-qualified # 🚒 E0.6 fire engine +1F693 ; fully-qualified # 🚓 E0.6 police car +1F694 ; fully-qualified # 🚔 E0.7 oncoming police car +1F695 ; fully-qualified # 🚕 E0.6 taxi +1F696 ; fully-qualified # 🚖 E1.0 oncoming taxi +1F697 ; fully-qualified # 🚗 E0.6 automobile +1F698 ; fully-qualified # 🚘 E0.7 oncoming automobile +1F699 ; fully-qualified # 🚙 E0.6 sport utility vehicle +1F6FB ; fully-qualified # 🛻 E13.0 pickup truck +1F69A ; fully-qualified # 🚚 E0.6 delivery truck +1F69B ; fully-qualified # 🚛 E1.0 articulated lorry +1F69C ; fully-qualified # 🚜 E1.0 tractor +1F3CE FE0F ; fully-qualified # 🏎️ E0.7 racing car +1F3CE ; unqualified # 🏎 E0.7 racing car +1F3CD FE0F ; fully-qualified # 🏍️ E0.7 motorcycle +1F3CD ; unqualified # 🏍 E0.7 motorcycle +1F6F5 ; fully-qualified # 🛵 E3.0 motor scooter +1F9BD ; fully-qualified # 🦽 E12.0 manual wheelchair +1F9BC ; fully-qualified # 🦼 E12.0 motorized wheelchair +1F6FA ; fully-qualified # 🛺 E12.0 auto rickshaw +1F6B2 ; fully-qualified # 🚲 E0.6 bicycle +1F6F4 ; fully-qualified # 🛴 E3.0 kick scooter +1F6F9 ; fully-qualified # 🛹 E11.0 skateboard +1F6FC ; fully-qualified # 🛼 E13.0 roller skate +1F68F ; fully-qualified # 🚏 E0.6 bus stop +1F6E3 FE0F ; fully-qualified # 🛣️ E0.7 motorway +1F6E3 ; unqualified # 🛣 E0.7 motorway +1F6E4 FE0F ; fully-qualified # 🛤️ E0.7 railway track +1F6E4 ; unqualified # 🛤 E0.7 railway track +1F6E2 FE0F ; fully-qualified # 🛢️ E0.7 oil drum +1F6E2 ; unqualified # 🛢 E0.7 oil drum +26FD ; fully-qualified # ⛽ E0.6 fuel pump +1F6A8 ; fully-qualified # 🚨 E0.6 police car light +1F6A5 ; fully-qualified # 🚥 E0.6 horizontal traffic light +1F6A6 ; fully-qualified # 🚦 E1.0 vertical traffic light +1F6D1 ; fully-qualified # 🛑 E3.0 stop sign +1F6A7 ; fully-qualified # 🚧 E0.6 construction + +# subgroup: transport-water +2693 ; fully-qualified # ⚓ E0.6 anchor +26F5 ; fully-qualified # ⛵ E0.6 sailboat +1F6F6 ; fully-qualified # 🛶 E3.0 canoe +1F6A4 ; fully-qualified # 🚤 E0.6 speedboat +1F6F3 FE0F ; fully-qualified # 🛳️ E0.7 passenger ship +1F6F3 ; unqualified # 🛳 E0.7 passenger ship +26F4 FE0F ; fully-qualified # ⛴️ E0.7 ferry +26F4 ; unqualified # ⛴ E0.7 ferry +1F6E5 FE0F ; fully-qualified # 🛥️ E0.7 motor boat +1F6E5 ; unqualified # 🛥 E0.7 motor boat +1F6A2 ; fully-qualified # 🚢 E0.6 ship + +# subgroup: transport-air +2708 FE0F ; fully-qualified # ✈️ E0.6 airplane +2708 ; unqualified # ✈ E0.6 airplane +1F6E9 FE0F ; fully-qualified # 🛩️ E0.7 small airplane +1F6E9 ; unqualified # 🛩 E0.7 small airplane +1F6EB ; fully-qualified # 🛫 E1.0 airplane departure +1F6EC ; fully-qualified # 🛬 E1.0 airplane arrival +1FA82 ; fully-qualified # 🪂 E12.0 parachute +1F4BA ; fully-qualified # 💺 E0.6 seat +1F681 ; fully-qualified # 🚁 E1.0 helicopter +1F69F ; fully-qualified # 🚟 E1.0 suspension railway +1F6A0 ; fully-qualified # 🚠 E1.0 mountain cableway +1F6A1 ; fully-qualified # 🚡 E1.0 aerial tramway +1F6F0 FE0F ; fully-qualified # 🛰️ E0.7 satellite +1F6F0 ; unqualified # 🛰 E0.7 satellite +1F680 ; fully-qualified # 🚀 E0.6 rocket +1F6F8 ; fully-qualified # 🛸 E5.0 flying saucer + +# subgroup: hotel +1F6CE FE0F ; fully-qualified # 🛎️ E0.7 bellhop bell +1F6CE ; unqualified # 🛎 E0.7 bellhop bell +1F9F3 ; fully-qualified # 🧳 E11.0 luggage + +# subgroup: time +231B ; fully-qualified # ⌛ E0.6 hourglass done +23F3 ; fully-qualified # ⏳ E0.6 hourglass not done +231A ; fully-qualified # ⌚ E0.6 watch +23F0 ; fully-qualified # ⏰ E0.6 alarm clock +23F1 FE0F ; fully-qualified # ⏱️ E1.0 stopwatch +23F1 ; unqualified # ⏱ E1.0 stopwatch +23F2 FE0F ; fully-qualified # ⏲️ E1.0 timer clock +23F2 ; unqualified # ⏲ E1.0 timer clock +1F570 FE0F ; fully-qualified # 🕰️ E0.7 mantelpiece clock +1F570 ; unqualified # 🕰 E0.7 mantelpiece clock +1F55B ; fully-qualified # 🕛 E0.6 twelve o’clock +1F567 ; fully-qualified # 🕧 E0.7 twelve-thirty +1F550 ; fully-qualified # 🕐 E0.6 one o’clock +1F55C ; fully-qualified # 🕜 E0.7 one-thirty +1F551 ; fully-qualified # 🕑 E0.6 two o’clock +1F55D ; fully-qualified # 🕝 E0.7 two-thirty +1F552 ; fully-qualified # 🕒 E0.6 three o’clock +1F55E ; fully-qualified # 🕞 E0.7 three-thirty +1F553 ; fully-qualified # 🕓 E0.6 four o’clock +1F55F ; fully-qualified # 🕟 E0.7 four-thirty +1F554 ; fully-qualified # 🕔 E0.6 five o’clock +1F560 ; fully-qualified # 🕠 E0.7 five-thirty +1F555 ; fully-qualified # 🕕 E0.6 six o’clock +1F561 ; fully-qualified # 🕡 E0.7 six-thirty +1F556 ; fully-qualified # 🕖 E0.6 seven o’clock +1F562 ; fully-qualified # 🕢 E0.7 seven-thirty +1F557 ; fully-qualified # 🕗 E0.6 eight o’clock +1F563 ; fully-qualified # 🕣 E0.7 eight-thirty +1F558 ; fully-qualified # 🕘 E0.6 nine o’clock +1F564 ; fully-qualified # 🕤 E0.7 nine-thirty +1F559 ; fully-qualified # 🕙 E0.6 ten o’clock +1F565 ; fully-qualified # 🕥 E0.7 ten-thirty +1F55A ; fully-qualified # 🕚 E0.6 eleven o’clock +1F566 ; fully-qualified # 🕦 E0.7 eleven-thirty + +# subgroup: sky & weather +1F311 ; fully-qualified # 🌑 E0.6 new moon +1F312 ; fully-qualified # 🌒 E1.0 waxing crescent moon +1F313 ; fully-qualified # 🌓 E0.6 first quarter moon +1F314 ; fully-qualified # 🌔 E0.6 waxing gibbous moon +1F315 ; fully-qualified # 🌕 E0.6 full moon +1F316 ; fully-qualified # 🌖 E1.0 waning gibbous moon +1F317 ; fully-qualified # 🌗 E1.0 last quarter moon +1F318 ; fully-qualified # 🌘 E1.0 waning crescent moon +1F319 ; fully-qualified # 🌙 E0.6 crescent moon +1F31A ; fully-qualified # 🌚 E1.0 new moon face +1F31B ; fully-qualified # 🌛 E0.6 first quarter moon face +1F31C ; fully-qualified # 🌜 E0.7 last quarter moon face +1F321 FE0F ; fully-qualified # 🌡️ E0.7 thermometer +1F321 ; unqualified # 🌡 E0.7 thermometer +2600 FE0F ; fully-qualified # ☀️ E0.6 sun +2600 ; unqualified # ☀ E0.6 sun +1F31D ; fully-qualified # 🌝 E1.0 full moon face +1F31E ; fully-qualified # 🌞 E1.0 sun with face +1FA90 ; fully-qualified # 🪐 E12.0 ringed planet +2B50 ; fully-qualified # ⭐ E0.6 star +1F31F ; fully-qualified # 🌟 E0.6 glowing star +1F320 ; fully-qualified # 🌠 E0.6 shooting star +1F30C ; fully-qualified # 🌌 E0.6 milky way +2601 FE0F ; fully-qualified # ☁️ E0.6 cloud +2601 ; unqualified # ☁ E0.6 cloud +26C5 ; fully-qualified # ⛅ E0.6 sun behind cloud +26C8 FE0F ; fully-qualified # ⛈️ E0.7 cloud with lightning and rain +26C8 ; unqualified # ⛈ E0.7 cloud with lightning and rain +1F324 FE0F ; fully-qualified # 🌤️ E0.7 sun behind small cloud +1F324 ; unqualified # 🌤 E0.7 sun behind small cloud +1F325 FE0F ; fully-qualified # 🌥️ E0.7 sun behind large cloud +1F325 ; unqualified # 🌥 E0.7 sun behind large cloud +1F326 FE0F ; fully-qualified # 🌦️ E0.7 sun behind rain cloud +1F326 ; unqualified # 🌦 E0.7 sun behind rain cloud +1F327 FE0F ; fully-qualified # 🌧️ E0.7 cloud with rain +1F327 ; unqualified # 🌧 E0.7 cloud with rain +1F328 FE0F ; fully-qualified # 🌨️ E0.7 cloud with snow +1F328 ; unqualified # 🌨 E0.7 cloud with snow +1F329 FE0F ; fully-qualified # 🌩️ E0.7 cloud with lightning +1F329 ; unqualified # 🌩 E0.7 cloud with lightning +1F32A FE0F ; fully-qualified # 🌪️ E0.7 tornado +1F32A ; unqualified # 🌪 E0.7 tornado +1F32B FE0F ; fully-qualified # 🌫️ E0.7 fog +1F32B ; unqualified # 🌫 E0.7 fog +1F32C FE0F ; fully-qualified # 🌬️ E0.7 wind face +1F32C ; unqualified # 🌬 E0.7 wind face +1F300 ; fully-qualified # 🌀 E0.6 cyclone +1F308 ; fully-qualified # 🌈 E0.6 rainbow +1F302 ; fully-qualified # 🌂 E0.6 closed umbrella +2602 FE0F ; fully-qualified # ☂️ E0.7 umbrella +2602 ; unqualified # ☂ E0.7 umbrella +2614 ; fully-qualified # ☔ E0.6 umbrella with rain drops +26F1 FE0F ; fully-qualified # ⛱️ E0.7 umbrella on ground +26F1 ; unqualified # ⛱ E0.7 umbrella on ground +26A1 ; fully-qualified # ⚡ E0.6 high voltage +2744 FE0F ; fully-qualified # ❄️ E0.6 snowflake +2744 ; unqualified # ❄ E0.6 snowflake +2603 FE0F ; fully-qualified # ☃️ E0.7 snowman +2603 ; unqualified # ☃ E0.7 snowman +26C4 ; fully-qualified # ⛄ E0.6 snowman without snow +2604 FE0F ; fully-qualified # ☄️ E1.0 comet +2604 ; unqualified # ☄ E1.0 comet +1F525 ; fully-qualified # 🔥 E0.6 fire +1F4A7 ; fully-qualified # 💧 E0.6 droplet +1F30A ; fully-qualified # 🌊 E0.6 water wave + +# Travel & Places subtotal: 264 +# Travel & Places subtotal: 264 w/o modifiers + +# group: Activities + +# subgroup: event +1F383 ; fully-qualified # 🎃 E0.6 jack-o-lantern +1F384 ; fully-qualified # 🎄 E0.6 Christmas tree +1F386 ; fully-qualified # 🎆 E0.6 fireworks +1F387 ; fully-qualified # 🎇 E0.6 sparkler +1F9E8 ; fully-qualified # 🧨 E11.0 firecracker +2728 ; fully-qualified # ✨ E0.6 sparkles +1F388 ; fully-qualified # 🎈 E0.6 balloon +1F389 ; fully-qualified # 🎉 E0.6 party popper +1F38A ; fully-qualified # 🎊 E0.6 confetti ball +1F38B ; fully-qualified # 🎋 E0.6 tanabata tree +1F38D ; fully-qualified # 🎍 E0.6 pine decoration +1F38E ; fully-qualified # 🎎 E0.6 Japanese dolls +1F38F ; fully-qualified # 🎏 E0.6 carp streamer +1F390 ; fully-qualified # 🎐 E0.6 wind chime +1F391 ; fully-qualified # 🎑 E0.6 moon viewing ceremony +1F9E7 ; fully-qualified # 🧧 E11.0 red envelope +1F380 ; fully-qualified # 🎀 E0.6 ribbon +1F381 ; fully-qualified # 🎁 E0.6 wrapped gift +1F397 FE0F ; fully-qualified # 🎗️ E0.7 reminder ribbon +1F397 ; unqualified # 🎗 E0.7 reminder ribbon +1F39F FE0F ; fully-qualified # 🎟️ E0.7 admission tickets +1F39F ; unqualified # 🎟 E0.7 admission tickets +1F3AB ; fully-qualified # 🎫 E0.6 ticket + +# subgroup: award-medal +1F396 FE0F ; fully-qualified # 🎖️ E0.7 military medal +1F396 ; unqualified # 🎖 E0.7 military medal +1F3C6 ; fully-qualified # 🏆 E0.6 trophy +1F3C5 ; fully-qualified # 🏅 E1.0 sports medal +1F947 ; fully-qualified # 🥇 E3.0 1st place medal +1F948 ; fully-qualified # 🥈 E3.0 2nd place medal +1F949 ; fully-qualified # 🥉 E3.0 3rd place medal + +# subgroup: sport +26BD ; fully-qualified # ⚽ E0.6 soccer ball +26BE ; fully-qualified # ⚾ E0.6 baseball +1F94E ; fully-qualified # 🥎 E11.0 softball +1F3C0 ; fully-qualified # 🏀 E0.6 basketball +1F3D0 ; fully-qualified # 🏐 E1.0 volleyball +1F3C8 ; fully-qualified # 🏈 E0.6 american football +1F3C9 ; fully-qualified # 🏉 E1.0 rugby football +1F3BE ; fully-qualified # 🎾 E0.6 tennis +1F94F ; fully-qualified # 🥏 E11.0 flying disc +1F3B3 ; fully-qualified # 🎳 E0.6 bowling +1F3CF ; fully-qualified # 🏏 E1.0 cricket game +1F3D1 ; fully-qualified # 🏑 E1.0 field hockey +1F3D2 ; fully-qualified # 🏒 E1.0 ice hockey +1F94D ; fully-qualified # 🥍 E11.0 lacrosse +1F3D3 ; fully-qualified # 🏓 E1.0 ping pong +1F3F8 ; fully-qualified # 🏸 E1.0 badminton +1F94A ; fully-qualified # 🥊 E3.0 boxing glove +1F94B ; fully-qualified # 🥋 E3.0 martial arts uniform +1F945 ; fully-qualified # 🥅 E3.0 goal net +26F3 ; fully-qualified # ⛳ E0.6 flag in hole +26F8 FE0F ; fully-qualified # ⛸️ E0.7 ice skate +26F8 ; unqualified # ⛸ E0.7 ice skate +1F3A3 ; fully-qualified # 🎣 E0.6 fishing pole +1F93F ; fully-qualified # 🤿 E12.0 diving mask +1F3BD ; fully-qualified # 🎽 E0.6 running shirt +1F3BF ; fully-qualified # 🎿 E0.6 skis +1F6F7 ; fully-qualified # 🛷 E5.0 sled +1F94C ; fully-qualified # 🥌 E5.0 curling stone + +# subgroup: game +1F3AF ; fully-qualified # 🎯 E0.6 bullseye +1FA80 ; fully-qualified # 🪀 E12.0 yo-yo +1FA81 ; fully-qualified # 🪁 E12.0 kite +1F3B1 ; fully-qualified # 🎱 E0.6 pool 8 ball +1F52E ; fully-qualified # 🔮 E0.6 crystal ball +1FA84 ; fully-qualified # 🪄 E13.0 magic wand +1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet +1F3AE ; fully-qualified # 🎮 E0.6 video game +1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick +1F579 ; unqualified # 🕹 E0.7 joystick +1F3B0 ; fully-qualified # 🎰 E0.6 slot machine +1F3B2 ; fully-qualified # 🎲 E0.6 game die +1F9E9 ; fully-qualified # 🧩 E11.0 puzzle piece +1F9F8 ; fully-qualified # 🧸 E11.0 teddy bear +1FA85 ; fully-qualified # 🪅 E13.0 piñata +1FA86 ; fully-qualified # 🪆 E13.0 nesting dolls +2660 FE0F ; fully-qualified # ♠️ E0.6 spade suit +2660 ; unqualified # ♠ E0.6 spade suit +2665 FE0F ; fully-qualified # ♥️ E0.6 heart suit +2665 ; unqualified # ♥ E0.6 heart suit +2666 FE0F ; fully-qualified # ♦️ E0.6 diamond suit +2666 ; unqualified # ♦ E0.6 diamond suit +2663 FE0F ; fully-qualified # ♣️ E0.6 club suit +2663 ; unqualified # ♣ E0.6 club suit +265F FE0F ; fully-qualified # ♟️ E11.0 chess pawn +265F ; unqualified # ♟ E11.0 chess pawn +1F0CF ; fully-qualified # 🃏 E0.6 joker +1F004 ; fully-qualified # 🀄 E0.6 mahjong red dragon +1F3B4 ; fully-qualified # 🎴 E0.6 flower playing cards + +# subgroup: arts & crafts +1F3AD ; fully-qualified # 🎭 E0.6 performing arts +1F5BC FE0F ; fully-qualified # 🖼️ E0.7 framed picture +1F5BC ; unqualified # 🖼 E0.7 framed picture +1F3A8 ; fully-qualified # 🎨 E0.6 artist palette +1F9F5 ; fully-qualified # 🧵 E11.0 thread +1FAA1 ; fully-qualified # 🪡 E13.0 sewing needle +1F9F6 ; fully-qualified # 🧶 E11.0 yarn +1FAA2 ; fully-qualified # 🪢 E13.0 knot + +# Activities subtotal: 95 +# Activities subtotal: 95 w/o modifiers + +# group: Objects + +# subgroup: clothing +1F453 ; fully-qualified # 👓 E0.6 glasses +1F576 FE0F ; fully-qualified # 🕶️ E0.7 sunglasses +1F576 ; unqualified # 🕶 E0.7 sunglasses +1F97D ; fully-qualified # 🥽 E11.0 goggles +1F97C ; fully-qualified # 🥼 E11.0 lab coat +1F9BA ; fully-qualified # 🦺 E12.0 safety vest +1F454 ; fully-qualified # 👔 E0.6 necktie +1F455 ; fully-qualified # 👕 E0.6 t-shirt +1F456 ; fully-qualified # 👖 E0.6 jeans +1F9E3 ; fully-qualified # 🧣 E5.0 scarf +1F9E4 ; fully-qualified # 🧤 E5.0 gloves +1F9E5 ; fully-qualified # 🧥 E5.0 coat +1F9E6 ; fully-qualified # 🧦 E5.0 socks +1F457 ; fully-qualified # 👗 E0.6 dress +1F458 ; fully-qualified # 👘 E0.6 kimono +1F97B ; fully-qualified # 🥻 E12.0 sari +1FA71 ; fully-qualified # 🩱 E12.0 one-piece swimsuit +1FA72 ; fully-qualified # 🩲 E12.0 briefs +1FA73 ; fully-qualified # 🩳 E12.0 shorts +1F459 ; fully-qualified # 👙 E0.6 bikini +1F45A ; fully-qualified # 👚 E0.6 woman’s clothes +1F45B ; fully-qualified # 👛 E0.6 purse +1F45C ; fully-qualified # 👜 E0.6 handbag +1F45D ; fully-qualified # 👝 E0.6 clutch bag +1F6CD FE0F ; fully-qualified # 🛍️ E0.7 shopping bags +1F6CD ; unqualified # 🛍 E0.7 shopping bags +1F392 ; fully-qualified # 🎒 E0.6 backpack +1FA74 ; fully-qualified # 🩴 E13.0 thong sandal +1F45E ; fully-qualified # 👞 E0.6 man’s shoe +1F45F ; fully-qualified # 👟 E0.6 running shoe +1F97E ; fully-qualified # 🥾 E11.0 hiking boot +1F97F ; fully-qualified # 🥿 E11.0 flat shoe +1F460 ; fully-qualified # 👠 E0.6 high-heeled shoe +1F461 ; fully-qualified # 👡 E0.6 woman’s sandal +1FA70 ; fully-qualified # 🩰 E12.0 ballet shoes +1F462 ; fully-qualified # 👢 E0.6 woman’s boot +1F451 ; fully-qualified # 👑 E0.6 crown +1F452 ; fully-qualified # 👒 E0.6 woman’s hat +1F3A9 ; fully-qualified # 🎩 E0.6 top hat +1F393 ; fully-qualified # 🎓 E0.6 graduation cap +1F9E2 ; fully-qualified # 🧢 E5.0 billed cap +1FA96 ; fully-qualified # 🪖 E13.0 military helmet +26D1 FE0F ; fully-qualified # ⛑️ E0.7 rescue worker’s helmet +26D1 ; unqualified # ⛑ E0.7 rescue worker’s helmet +1F4FF ; fully-qualified # 📿 E1.0 prayer beads +1F484 ; fully-qualified # 💄 E0.6 lipstick +1F48D ; fully-qualified # 💍 E0.6 ring +1F48E ; fully-qualified # 💎 E0.6 gem stone + +# subgroup: sound +1F507 ; fully-qualified # 🔇 E1.0 muted speaker +1F508 ; fully-qualified # 🔈 E0.7 speaker low volume +1F509 ; fully-qualified # 🔉 E1.0 speaker medium volume +1F50A ; fully-qualified # 🔊 E0.6 speaker high volume +1F4E2 ; fully-qualified # 📢 E0.6 loudspeaker +1F4E3 ; fully-qualified # 📣 E0.6 megaphone +1F4EF ; fully-qualified # 📯 E1.0 postal horn +1F514 ; fully-qualified # 🔔 E0.6 bell +1F515 ; fully-qualified # 🔕 E1.0 bell with slash + +# subgroup: music +1F3BC ; fully-qualified # 🎼 E0.6 musical score +1F3B5 ; fully-qualified # 🎵 E0.6 musical note +1F3B6 ; fully-qualified # 🎶 E0.6 musical notes +1F399 FE0F ; fully-qualified # 🎙️ E0.7 studio microphone +1F399 ; unqualified # 🎙 E0.7 studio microphone +1F39A FE0F ; fully-qualified # 🎚️ E0.7 level slider +1F39A ; unqualified # 🎚 E0.7 level slider +1F39B FE0F ; fully-qualified # 🎛️ E0.7 control knobs +1F39B ; unqualified # 🎛 E0.7 control knobs +1F3A4 ; fully-qualified # 🎤 E0.6 microphone +1F3A7 ; fully-qualified # 🎧 E0.6 headphone +1F4FB ; fully-qualified # 📻 E0.6 radio + +# subgroup: musical-instrument +1F3B7 ; fully-qualified # 🎷 E0.6 saxophone +1FA97 ; fully-qualified # 🪗 E13.0 accordion +1F3B8 ; fully-qualified # 🎸 E0.6 guitar +1F3B9 ; fully-qualified # 🎹 E0.6 musical keyboard +1F3BA ; fully-qualified # 🎺 E0.6 trumpet +1F3BB ; fully-qualified # 🎻 E0.6 violin +1FA95 ; fully-qualified # 🪕 E12.0 banjo +1F941 ; fully-qualified # 🥁 E3.0 drum +1FA98 ; fully-qualified # 🪘 E13.0 long drum + +# subgroup: phone +1F4F1 ; fully-qualified # 📱 E0.6 mobile phone +1F4F2 ; fully-qualified # 📲 E0.6 mobile phone with arrow +260E FE0F ; fully-qualified # ☎️ E0.6 telephone +260E ; unqualified # ☎ E0.6 telephone +1F4DE ; fully-qualified # 📞 E0.6 telephone receiver +1F4DF ; fully-qualified # 📟 E0.6 pager +1F4E0 ; fully-qualified # 📠 E0.6 fax machine + +# subgroup: computer +1F50B ; fully-qualified # 🔋 E0.6 battery +1F50C ; fully-qualified # 🔌 E0.6 electric plug +1F4BB ; fully-qualified # 💻 E0.6 laptop +1F5A5 FE0F ; fully-qualified # 🖥️ E0.7 desktop computer +1F5A5 ; unqualified # 🖥 E0.7 desktop computer +1F5A8 FE0F ; fully-qualified # 🖨️ E0.7 printer +1F5A8 ; unqualified # 🖨 E0.7 printer +2328 FE0F ; fully-qualified # ⌨️ E1.0 keyboard +2328 ; unqualified # ⌨ E1.0 keyboard +1F5B1 FE0F ; fully-qualified # 🖱️ E0.7 computer mouse +1F5B1 ; unqualified # 🖱 E0.7 computer mouse +1F5B2 FE0F ; fully-qualified # 🖲️ E0.7 trackball +1F5B2 ; unqualified # 🖲 E0.7 trackball +1F4BD ; fully-qualified # 💽 E0.6 computer disk +1F4BE ; fully-qualified # 💾 E0.6 floppy disk +1F4BF ; fully-qualified # 💿 E0.6 optical disk +1F4C0 ; fully-qualified # 📀 E0.6 dvd +1F9EE ; fully-qualified # 🧮 E11.0 abacus + +# subgroup: light & video +1F3A5 ; fully-qualified # 🎥 E0.6 movie camera +1F39E FE0F ; fully-qualified # 🎞️ E0.7 film frames +1F39E ; unqualified # 🎞 E0.7 film frames +1F4FD FE0F ; fully-qualified # 📽️ E0.7 film projector +1F4FD ; unqualified # 📽 E0.7 film projector +1F3AC ; fully-qualified # 🎬 E0.6 clapper board +1F4FA ; fully-qualified # 📺 E0.6 television +1F4F7 ; fully-qualified # 📷 E0.6 camera +1F4F8 ; fully-qualified # 📸 E1.0 camera with flash +1F4F9 ; fully-qualified # 📹 E0.6 video camera +1F4FC ; fully-qualified # 📼 E0.6 videocassette +1F50D ; fully-qualified # 🔍 E0.6 magnifying glass tilted left +1F50E ; fully-qualified # 🔎 E0.6 magnifying glass tilted right +1F56F FE0F ; fully-qualified # 🕯️ E0.7 candle +1F56F ; unqualified # 🕯 E0.7 candle +1F4A1 ; fully-qualified # 💡 E0.6 light bulb +1F526 ; fully-qualified # 🔦 E0.6 flashlight +1F3EE ; fully-qualified # 🏮 E0.6 red paper lantern +1FA94 ; fully-qualified # 🪔 E12.0 diya lamp + +# subgroup: book-paper +1F4D4 ; fully-qualified # 📔 E0.6 notebook with decorative cover +1F4D5 ; fully-qualified # 📕 E0.6 closed book +1F4D6 ; fully-qualified # 📖 E0.6 open book +1F4D7 ; fully-qualified # 📗 E0.6 green book +1F4D8 ; fully-qualified # 📘 E0.6 blue book +1F4D9 ; fully-qualified # 📙 E0.6 orange book +1F4DA ; fully-qualified # 📚 E0.6 books +1F4D3 ; fully-qualified # 📓 E0.6 notebook +1F4D2 ; fully-qualified # 📒 E0.6 ledger +1F4C3 ; fully-qualified # 📃 E0.6 page with curl +1F4DC ; fully-qualified # 📜 E0.6 scroll +1F4C4 ; fully-qualified # 📄 E0.6 page facing up +1F4F0 ; fully-qualified # 📰 E0.6 newspaper +1F5DE FE0F ; fully-qualified # 🗞️ E0.7 rolled-up newspaper +1F5DE ; unqualified # 🗞 E0.7 rolled-up newspaper +1F4D1 ; fully-qualified # 📑 E0.6 bookmark tabs +1F516 ; fully-qualified # 🔖 E0.6 bookmark +1F3F7 FE0F ; fully-qualified # 🏷️ E0.7 label +1F3F7 ; unqualified # 🏷 E0.7 label + +# subgroup: money +1F4B0 ; fully-qualified # 💰 E0.6 money bag +1FA99 ; fully-qualified # 🪙 E13.0 coin +1F4B4 ; fully-qualified # 💴 E0.6 yen banknote +1F4B5 ; fully-qualified # 💵 E0.6 dollar banknote +1F4B6 ; fully-qualified # 💶 E1.0 euro banknote +1F4B7 ; fully-qualified # 💷 E1.0 pound banknote +1F4B8 ; fully-qualified # 💸 E0.6 money with wings +1F4B3 ; fully-qualified # 💳 E0.6 credit card +1F9FE ; fully-qualified # 🧾 E11.0 receipt +1F4B9 ; fully-qualified # 💹 E0.6 chart increasing with yen + +# subgroup: mail +2709 FE0F ; fully-qualified # ✉️ E0.6 envelope +2709 ; unqualified # ✉ E0.6 envelope +1F4E7 ; fully-qualified # 📧 E0.6 e-mail +1F4E8 ; fully-qualified # 📨 E0.6 incoming envelope +1F4E9 ; fully-qualified # 📩 E0.6 envelope with arrow +1F4E4 ; fully-qualified # 📤 E0.6 outbox tray +1F4E5 ; fully-qualified # 📥 E0.6 inbox tray +1F4E6 ; fully-qualified # 📦 E0.6 package +1F4EB ; fully-qualified # 📫 E0.6 closed mailbox with raised flag +1F4EA ; fully-qualified # 📪 E0.6 closed mailbox with lowered flag +1F4EC ; fully-qualified # 📬 E0.7 open mailbox with raised flag +1F4ED ; fully-qualified # 📭 E0.7 open mailbox with lowered flag +1F4EE ; fully-qualified # 📮 E0.6 postbox +1F5F3 FE0F ; fully-qualified # 🗳️ E0.7 ballot box with ballot +1F5F3 ; unqualified # 🗳 E0.7 ballot box with ballot + +# subgroup: writing +270F FE0F ; fully-qualified # ✏️ E0.6 pencil +270F ; unqualified # ✏ E0.6 pencil +2712 FE0F ; fully-qualified # ✒️ E0.6 black nib +2712 ; unqualified # ✒ E0.6 black nib +1F58B FE0F ; fully-qualified # 🖋️ E0.7 fountain pen +1F58B ; unqualified # 🖋 E0.7 fountain pen +1F58A FE0F ; fully-qualified # 🖊️ E0.7 pen +1F58A ; unqualified # 🖊 E0.7 pen +1F58C FE0F ; fully-qualified # 🖌️ E0.7 paintbrush +1F58C ; unqualified # 🖌 E0.7 paintbrush +1F58D FE0F ; fully-qualified # 🖍️ E0.7 crayon +1F58D ; unqualified # 🖍 E0.7 crayon +1F4DD ; fully-qualified # 📝 E0.6 memo + +# subgroup: office +1F4BC ; fully-qualified # 💼 E0.6 briefcase +1F4C1 ; fully-qualified # 📁 E0.6 file folder +1F4C2 ; fully-qualified # 📂 E0.6 open file folder +1F5C2 FE0F ; fully-qualified # 🗂️ E0.7 card index dividers +1F5C2 ; unqualified # 🗂 E0.7 card index dividers +1F4C5 ; fully-qualified # 📅 E0.6 calendar +1F4C6 ; fully-qualified # 📆 E0.6 tear-off calendar +1F5D2 FE0F ; fully-qualified # 🗒️ E0.7 spiral notepad +1F5D2 ; unqualified # 🗒 E0.7 spiral notepad +1F5D3 FE0F ; fully-qualified # 🗓️ E0.7 spiral calendar +1F5D3 ; unqualified # 🗓 E0.7 spiral calendar +1F4C7 ; fully-qualified # 📇 E0.6 card index +1F4C8 ; fully-qualified # 📈 E0.6 chart increasing +1F4C9 ; fully-qualified # 📉 E0.6 chart decreasing +1F4CA ; fully-qualified # 📊 E0.6 bar chart +1F4CB ; fully-qualified # 📋 E0.6 clipboard +1F4CC ; fully-qualified # 📌 E0.6 pushpin +1F4CD ; fully-qualified # 📍 E0.6 round pushpin +1F4CE ; fully-qualified # 📎 E0.6 paperclip +1F587 FE0F ; fully-qualified # 🖇️ E0.7 linked paperclips +1F587 ; unqualified # 🖇 E0.7 linked paperclips +1F4CF ; fully-qualified # 📏 E0.6 straight ruler +1F4D0 ; fully-qualified # 📐 E0.6 triangular ruler +2702 FE0F ; fully-qualified # ✂️ E0.6 scissors +2702 ; unqualified # ✂ E0.6 scissors +1F5C3 FE0F ; fully-qualified # 🗃️ E0.7 card file box +1F5C3 ; unqualified # 🗃 E0.7 card file box +1F5C4 FE0F ; fully-qualified # 🗄️ E0.7 file cabinet +1F5C4 ; unqualified # 🗄 E0.7 file cabinet +1F5D1 FE0F ; fully-qualified # 🗑️ E0.7 wastebasket +1F5D1 ; unqualified # 🗑 E0.7 wastebasket + +# subgroup: lock +1F512 ; fully-qualified # 🔒 E0.6 locked +1F513 ; fully-qualified # 🔓 E0.6 unlocked +1F50F ; fully-qualified # 🔏 E0.6 locked with pen +1F510 ; fully-qualified # 🔐 E0.6 locked with key +1F511 ; fully-qualified # 🔑 E0.6 key +1F5DD FE0F ; fully-qualified # 🗝️ E0.7 old key +1F5DD ; unqualified # 🗝 E0.7 old key + +# subgroup: tool +1F528 ; fully-qualified # 🔨 E0.6 hammer +1FA93 ; fully-qualified # 🪓 E12.0 axe +26CF FE0F ; fully-qualified # ⛏️ E0.7 pick +26CF ; unqualified # ⛏ E0.7 pick +2692 FE0F ; fully-qualified # ⚒️ E1.0 hammer and pick +2692 ; unqualified # ⚒ E1.0 hammer and pick +1F6E0 FE0F ; fully-qualified # 🛠️ E0.7 hammer and wrench +1F6E0 ; unqualified # 🛠 E0.7 hammer and wrench +1F5E1 FE0F ; fully-qualified # 🗡️ E0.7 dagger +1F5E1 ; unqualified # 🗡 E0.7 dagger +2694 FE0F ; fully-qualified # ⚔️ E1.0 crossed swords +2694 ; unqualified # ⚔ E1.0 crossed swords +1F52B ; fully-qualified # 🔫 E0.6 water pistol +1FA83 ; fully-qualified # 🪃 E13.0 boomerang +1F3F9 ; fully-qualified # 🏹 E1.0 bow and arrow +1F6E1 FE0F ; fully-qualified # 🛡️ E0.7 shield +1F6E1 ; unqualified # 🛡 E0.7 shield +1FA9A ; fully-qualified # 🪚 E13.0 carpentry saw +1F527 ; fully-qualified # 🔧 E0.6 wrench +1FA9B ; fully-qualified # 🪛 E13.0 screwdriver +1F529 ; fully-qualified # 🔩 E0.6 nut and bolt +2699 FE0F ; fully-qualified # ⚙️ E1.0 gear +2699 ; unqualified # ⚙ E1.0 gear +1F5DC FE0F ; fully-qualified # 🗜️ E0.7 clamp +1F5DC ; unqualified # 🗜 E0.7 clamp +2696 FE0F ; fully-qualified # ⚖️ E1.0 balance scale +2696 ; unqualified # ⚖ E1.0 balance scale +1F9AF ; fully-qualified # 🦯 E12.0 white cane +1F517 ; fully-qualified # 🔗 E0.6 link +26D3 FE0F ; fully-qualified # ⛓️ E0.7 chains +26D3 ; unqualified # ⛓ E0.7 chains +1FA9D ; fully-qualified # 🪝 E13.0 hook +1F9F0 ; fully-qualified # 🧰 E11.0 toolbox +1F9F2 ; fully-qualified # 🧲 E11.0 magnet +1FA9C ; fully-qualified # 🪜 E13.0 ladder + +# subgroup: science +2697 FE0F ; fully-qualified # ⚗️ E1.0 alembic +2697 ; unqualified # ⚗ E1.0 alembic +1F9EA ; fully-qualified # 🧪 E11.0 test tube +1F9EB ; fully-qualified # 🧫 E11.0 petri dish +1F9EC ; fully-qualified # 🧬 E11.0 dna +1F52C ; fully-qualified # 🔬 E1.0 microscope +1F52D ; fully-qualified # 🔭 E1.0 telescope +1F4E1 ; fully-qualified # 📡 E0.6 satellite antenna + +# subgroup: medical +1F489 ; fully-qualified # 💉 E0.6 syringe +1FA78 ; fully-qualified # 🩸 E12.0 drop of blood +1F48A ; fully-qualified # 💊 E0.6 pill +1FA79 ; fully-qualified # 🩹 E12.0 adhesive bandage +1FA7A ; fully-qualified # 🩺 E12.0 stethoscope + +# subgroup: household +1F6AA ; fully-qualified # 🚪 E0.6 door +1F6D7 ; fully-qualified # 🛗 E13.0 elevator +1FA9E ; fully-qualified # 🪞 E13.0 mirror +1FA9F ; fully-qualified # 🪟 E13.0 window +1F6CF FE0F ; fully-qualified # 🛏️ E0.7 bed +1F6CF ; unqualified # 🛏 E0.7 bed +1F6CB FE0F ; fully-qualified # 🛋️ E0.7 couch and lamp +1F6CB ; unqualified # 🛋 E0.7 couch and lamp +1FA91 ; fully-qualified # 🪑 E12.0 chair +1F6BD ; fully-qualified # 🚽 E0.6 toilet +1FAA0 ; fully-qualified # 🪠 E13.0 plunger +1F6BF ; fully-qualified # 🚿 E1.0 shower +1F6C1 ; fully-qualified # 🛁 E1.0 bathtub +1FAA4 ; fully-qualified # 🪤 E13.0 mouse trap +1FA92 ; fully-qualified # 🪒 E12.0 razor +1F9F4 ; fully-qualified # 🧴 E11.0 lotion bottle +1F9F7 ; fully-qualified # 🧷 E11.0 safety pin +1F9F9 ; fully-qualified # 🧹 E11.0 broom +1F9FA ; fully-qualified # 🧺 E11.0 basket +1F9FB ; fully-qualified # 🧻 E11.0 roll of paper +1FAA3 ; fully-qualified # 🪣 E13.0 bucket +1F9FC ; fully-qualified # 🧼 E11.0 soap +1FAA5 ; fully-qualified # 🪥 E13.0 toothbrush +1F9FD ; fully-qualified # 🧽 E11.0 sponge +1F9EF ; fully-qualified # 🧯 E11.0 fire extinguisher +1F6D2 ; fully-qualified # 🛒 E3.0 shopping cart + +# subgroup: other-object +1F6AC ; fully-qualified # 🚬 E0.6 cigarette +26B0 FE0F ; fully-qualified # ⚰️ E1.0 coffin +26B0 ; unqualified # ⚰ E1.0 coffin +1FAA6 ; fully-qualified # 🪦 E13.0 headstone +26B1 FE0F ; fully-qualified # ⚱️ E1.0 funeral urn +26B1 ; unqualified # ⚱ E1.0 funeral urn +1F5FF ; fully-qualified # 🗿 E0.6 moai +1FAA7 ; fully-qualified # 🪧 E13.0 placard + +# Objects subtotal: 299 +# Objects subtotal: 299 w/o modifiers + +# group: Symbols + +# subgroup: transport-sign +1F3E7 ; fully-qualified # 🏧 E0.6 ATM sign +1F6AE ; fully-qualified # 🚮 E1.0 litter in bin sign +1F6B0 ; fully-qualified # 🚰 E1.0 potable water +267F ; fully-qualified # ♿ E0.6 wheelchair symbol +1F6B9 ; fully-qualified # 🚹 E0.6 men’s room +1F6BA ; fully-qualified # 🚺 E0.6 women’s room +1F6BB ; fully-qualified # 🚻 E0.6 restroom +1F6BC ; fully-qualified # 🚼 E0.6 baby symbol +1F6BE ; fully-qualified # 🚾 E0.6 water closet +1F6C2 ; fully-qualified # 🛂 E1.0 passport control +1F6C3 ; fully-qualified # 🛃 E1.0 customs +1F6C4 ; fully-qualified # 🛄 E1.0 baggage claim +1F6C5 ; fully-qualified # 🛅 E1.0 left luggage + +# subgroup: warning +26A0 FE0F ; fully-qualified # ⚠️ E0.6 warning +26A0 ; unqualified # ⚠ E0.6 warning +1F6B8 ; fully-qualified # 🚸 E1.0 children crossing +26D4 ; fully-qualified # ⛔ E0.6 no entry +1F6AB ; fully-qualified # 🚫 E0.6 prohibited +1F6B3 ; fully-qualified # 🚳 E1.0 no bicycles +1F6AD ; fully-qualified # 🚭 E0.6 no smoking +1F6AF ; fully-qualified # 🚯 E1.0 no littering +1F6B1 ; fully-qualified # 🚱 E1.0 non-potable water +1F6B7 ; fully-qualified # 🚷 E1.0 no pedestrians +1F4F5 ; fully-qualified # 📵 E1.0 no mobile phones +1F51E ; fully-qualified # 🔞 E0.6 no one under eighteen +2622 FE0F ; fully-qualified # ☢️ E1.0 radioactive +2622 ; unqualified # ☢ E1.0 radioactive +2623 FE0F ; fully-qualified # ☣️ E1.0 biohazard +2623 ; unqualified # ☣ E1.0 biohazard + +# subgroup: arrow +2B06 FE0F ; fully-qualified # ⬆️ E0.6 up arrow +2B06 ; unqualified # ⬆ E0.6 up arrow +2197 FE0F ; fully-qualified # ↗️ E0.6 up-right arrow +2197 ; unqualified # ↗ E0.6 up-right arrow +27A1 FE0F ; fully-qualified # ➡️ E0.6 right arrow +27A1 ; unqualified # ➡ E0.6 right arrow +2198 FE0F ; fully-qualified # ↘️ E0.6 down-right arrow +2198 ; unqualified # ↘ E0.6 down-right arrow +2B07 FE0F ; fully-qualified # ⬇️ E0.6 down arrow +2B07 ; unqualified # ⬇ E0.6 down arrow +2199 FE0F ; fully-qualified # ↙️ E0.6 down-left arrow +2199 ; unqualified # ↙ E0.6 down-left arrow +2B05 FE0F ; fully-qualified # ⬅️ E0.6 left arrow +2B05 ; unqualified # ⬅ E0.6 left arrow +2196 FE0F ; fully-qualified # ↖️ E0.6 up-left arrow +2196 ; unqualified # ↖ E0.6 up-left arrow +2195 FE0F ; fully-qualified # ↕️ E0.6 up-down arrow +2195 ; unqualified # ↕ E0.6 up-down arrow +2194 FE0F ; fully-qualified # ↔️ E0.6 left-right arrow +2194 ; unqualified # ↔ E0.6 left-right arrow +21A9 FE0F ; fully-qualified # ↩️ E0.6 right arrow curving left +21A9 ; unqualified # ↩ E0.6 right arrow curving left +21AA FE0F ; fully-qualified # ↪️ E0.6 left arrow curving right +21AA ; unqualified # ↪ E0.6 left arrow curving right +2934 FE0F ; fully-qualified # ⤴️ E0.6 right arrow curving up +2934 ; unqualified # ⤴ E0.6 right arrow curving up +2935 FE0F ; fully-qualified # ⤵️ E0.6 right arrow curving down +2935 ; unqualified # ⤵ E0.6 right arrow curving down +1F503 ; fully-qualified # 🔃 E0.6 clockwise vertical arrows +1F504 ; fully-qualified # 🔄 E1.0 counterclockwise arrows button +1F519 ; fully-qualified # 🔙 E0.6 BACK arrow +1F51A ; fully-qualified # 🔚 E0.6 END arrow +1F51B ; fully-qualified # 🔛 E0.6 ON! arrow +1F51C ; fully-qualified # 🔜 E0.6 SOON arrow +1F51D ; fully-qualified # 🔝 E0.6 TOP arrow + +# subgroup: religion +1F6D0 ; fully-qualified # 🛐 E1.0 place of worship +269B FE0F ; fully-qualified # ⚛️ E1.0 atom symbol +269B ; unqualified # ⚛ E1.0 atom symbol +1F549 FE0F ; fully-qualified # 🕉️ E0.7 om +1F549 ; unqualified # 🕉 E0.7 om +2721 FE0F ; fully-qualified # ✡️ E0.7 star of David +2721 ; unqualified # ✡ E0.7 star of David +2638 FE0F ; fully-qualified # ☸️ E0.7 wheel of dharma +2638 ; unqualified # ☸ E0.7 wheel of dharma +262F FE0F ; fully-qualified # ☯️ E0.7 yin yang +262F ; unqualified # ☯ E0.7 yin yang +271D FE0F ; fully-qualified # ✝️ E0.7 latin cross +271D ; unqualified # ✝ E0.7 latin cross +2626 FE0F ; fully-qualified # ☦️ E1.0 orthodox cross +2626 ; unqualified # ☦ E1.0 orthodox cross +262A FE0F ; fully-qualified # ☪️ E0.7 star and crescent +262A ; unqualified # ☪ E0.7 star and crescent +262E FE0F ; fully-qualified # ☮️ E1.0 peace symbol +262E ; unqualified # ☮ E1.0 peace symbol +1F54E ; fully-qualified # 🕎 E1.0 menorah +1F52F ; fully-qualified # 🔯 E0.6 dotted six-pointed star + +# subgroup: zodiac +2648 ; fully-qualified # ♈ E0.6 Aries +2649 ; fully-qualified # ♉ E0.6 Taurus +264A ; fully-qualified # ♊ E0.6 Gemini +264B ; fully-qualified # ♋ E0.6 Cancer +264C ; fully-qualified # ♌ E0.6 Leo +264D ; fully-qualified # ♍ E0.6 Virgo +264E ; fully-qualified # ♎ E0.6 Libra +264F ; fully-qualified # ♏ E0.6 Scorpio +2650 ; fully-qualified # ♐ E0.6 Sagittarius +2651 ; fully-qualified # ♑ E0.6 Capricorn +2652 ; fully-qualified # ♒ E0.6 Aquarius +2653 ; fully-qualified # ♓ E0.6 Pisces +26CE ; fully-qualified # ⛎ E0.6 Ophiuchus + +# subgroup: av-symbol +1F500 ; fully-qualified # 🔀 E1.0 shuffle tracks button +1F501 ; fully-qualified # 🔁 E1.0 repeat button +1F502 ; fully-qualified # 🔂 E1.0 repeat single button +25B6 FE0F ; fully-qualified # ▶️ E0.6 play button +25B6 ; unqualified # ▶ E0.6 play button +23E9 ; fully-qualified # ⏩ E0.6 fast-forward button +23ED FE0F ; fully-qualified # ⏭️ E0.7 next track button +23ED ; unqualified # ⏭ E0.7 next track button +23EF FE0F ; fully-qualified # ⏯️ E1.0 play or pause button +23EF ; unqualified # ⏯ E1.0 play or pause button +25C0 FE0F ; fully-qualified # ◀️ E0.6 reverse button +25C0 ; unqualified # ◀ E0.6 reverse button +23EA ; fully-qualified # ⏪ E0.6 fast reverse button +23EE FE0F ; fully-qualified # ⏮️ E0.7 last track button +23EE ; unqualified # ⏮ E0.7 last track button +1F53C ; fully-qualified # 🔼 E0.6 upwards button +23EB ; fully-qualified # ⏫ E0.6 fast up button +1F53D ; fully-qualified # 🔽 E0.6 downwards button +23EC ; fully-qualified # ⏬ E0.6 fast down button +23F8 FE0F ; fully-qualified # ⏸️ E0.7 pause button +23F8 ; unqualified # ⏸ E0.7 pause button +23F9 FE0F ; fully-qualified # ⏹️ E0.7 stop button +23F9 ; unqualified # ⏹ E0.7 stop button +23FA FE0F ; fully-qualified # ⏺️ E0.7 record button +23FA ; unqualified # ⏺ E0.7 record button +23CF FE0F ; fully-qualified # ⏏️ E1.0 eject button +23CF ; unqualified # ⏏ E1.0 eject button +1F3A6 ; fully-qualified # 🎦 E0.6 cinema +1F505 ; fully-qualified # 🔅 E1.0 dim button +1F506 ; fully-qualified # 🔆 E1.0 bright button +1F4F6 ; fully-qualified # 📶 E0.6 antenna bars +1F4F3 ; fully-qualified # 📳 E0.6 vibration mode +1F4F4 ; fully-qualified # 📴 E0.6 mobile phone off + +# subgroup: gender +2640 FE0F ; fully-qualified # ♀️ E4.0 female sign +2640 ; unqualified # ♀ E4.0 female sign +2642 FE0F ; fully-qualified # ♂️ E4.0 male sign +2642 ; unqualified # ♂ E4.0 male sign +26A7 FE0F ; fully-qualified # ⚧️ E13.0 transgender symbol +26A7 ; unqualified # ⚧ E13.0 transgender symbol + +# subgroup: math +2716 FE0F ; fully-qualified # ✖️ E0.6 multiply +2716 ; unqualified # ✖ E0.6 multiply +2795 ; fully-qualified # ➕ E0.6 plus +2796 ; fully-qualified # ➖ E0.6 minus +2797 ; fully-qualified # ➗ E0.6 divide +267E FE0F ; fully-qualified # ♾️ E11.0 infinity +267E ; unqualified # ♾ E11.0 infinity + +# subgroup: punctuation +203C FE0F ; fully-qualified # ‼️ E0.6 double exclamation mark +203C ; unqualified # ‼ E0.6 double exclamation mark +2049 FE0F ; fully-qualified # ⁉️ E0.6 exclamation question mark +2049 ; unqualified # ⁉ E0.6 exclamation question mark +2753 ; fully-qualified # ❓ E0.6 red question mark +2754 ; fully-qualified # ❔ E0.6 white question mark +2755 ; fully-qualified # ❕ E0.6 white exclamation mark +2757 ; fully-qualified # ❗ E0.6 red exclamation mark +3030 FE0F ; fully-qualified # 〰️ E0.6 wavy dash +3030 ; unqualified # 〰 E0.6 wavy dash + +# subgroup: currency +1F4B1 ; fully-qualified # 💱 E0.6 currency exchange +1F4B2 ; fully-qualified # 💲 E0.6 heavy dollar sign + +# subgroup: other-symbol +2695 FE0F ; fully-qualified # ⚕️ E4.0 medical symbol +2695 ; unqualified # ⚕ E4.0 medical symbol +267B FE0F ; fully-qualified # ♻️ E0.6 recycling symbol +267B ; unqualified # ♻ E0.6 recycling symbol +269C FE0F ; fully-qualified # ⚜️ E1.0 fleur-de-lis +269C ; unqualified # ⚜ E1.0 fleur-de-lis +1F531 ; fully-qualified # 🔱 E0.6 trident emblem +1F4DB ; fully-qualified # 📛 E0.6 name badge +1F530 ; fully-qualified # 🔰 E0.6 Japanese symbol for beginner +2B55 ; fully-qualified # ⭕ E0.6 hollow red circle +2705 ; fully-qualified # ✅ E0.6 check mark button +2611 FE0F ; fully-qualified # ☑️ E0.6 check box with check +2611 ; unqualified # ☑ E0.6 check box with check +2714 FE0F ; fully-qualified # ✔️ E0.6 check mark +2714 ; unqualified # ✔ E0.6 check mark +274C ; fully-qualified # ❌ E0.6 cross mark +274E ; fully-qualified # ❎ E0.6 cross mark button +27B0 ; fully-qualified # ➰ E0.6 curly loop +27BF ; fully-qualified # ➿ E1.0 double curly loop +303D FE0F ; fully-qualified # 〽️ E0.6 part alternation mark +303D ; unqualified # 〽 E0.6 part alternation mark +2733 FE0F ; fully-qualified # ✳️ E0.6 eight-spoked asterisk +2733 ; unqualified # ✳ E0.6 eight-spoked asterisk +2734 FE0F ; fully-qualified # ✴️ E0.6 eight-pointed star +2734 ; unqualified # ✴ E0.6 eight-pointed star +2747 FE0F ; fully-qualified # ❇️ E0.6 sparkle +2747 ; unqualified # ❇ E0.6 sparkle +00A9 FE0F ; fully-qualified # ©️ E0.6 copyright +00A9 ; unqualified # © E0.6 copyright +00AE FE0F ; fully-qualified # ®️ E0.6 registered +00AE ; unqualified # ® E0.6 registered +2122 FE0F ; fully-qualified # ™️ E0.6 trade mark +2122 ; unqualified # ™ E0.6 trade mark + +# subgroup: keycap +0023 FE0F 20E3 ; fully-qualified # #️⃣ E0.6 keycap: # +0023 20E3 ; unqualified # #⃣ E0.6 keycap: # +002A FE0F 20E3 ; fully-qualified # *️⃣ E2.0 keycap: * +002A 20E3 ; unqualified # *⃣ E2.0 keycap: * +0030 FE0F 20E3 ; fully-qualified # 0️⃣ E0.6 keycap: 0 +0030 20E3 ; unqualified # 0⃣ E0.6 keycap: 0 +0031 FE0F 20E3 ; fully-qualified # 1️⃣ E0.6 keycap: 1 +0031 20E3 ; unqualified # 1⃣ E0.6 keycap: 1 +0032 FE0F 20E3 ; fully-qualified # 2️⃣ E0.6 keycap: 2 +0032 20E3 ; unqualified # 2⃣ E0.6 keycap: 2 +0033 FE0F 20E3 ; fully-qualified # 3️⃣ E0.6 keycap: 3 +0033 20E3 ; unqualified # 3⃣ E0.6 keycap: 3 +0034 FE0F 20E3 ; fully-qualified # 4️⃣ E0.6 keycap: 4 +0034 20E3 ; unqualified # 4⃣ E0.6 keycap: 4 +0035 FE0F 20E3 ; fully-qualified # 5️⃣ E0.6 keycap: 5 +0035 20E3 ; unqualified # 5⃣ E0.6 keycap: 5 +0036 FE0F 20E3 ; fully-qualified # 6️⃣ E0.6 keycap: 6 +0036 20E3 ; unqualified # 6⃣ E0.6 keycap: 6 +0037 FE0F 20E3 ; fully-qualified # 7️⃣ E0.6 keycap: 7 +0037 20E3 ; unqualified # 7⃣ E0.6 keycap: 7 +0038 FE0F 20E3 ; fully-qualified # 8️⃣ E0.6 keycap: 8 +0038 20E3 ; unqualified # 8⃣ E0.6 keycap: 8 +0039 FE0F 20E3 ; fully-qualified # 9️⃣ E0.6 keycap: 9 +0039 20E3 ; unqualified # 9⃣ E0.6 keycap: 9 +1F51F ; fully-qualified # 🔟 E0.6 keycap: 10 + +# subgroup: alphanum +1F520 ; fully-qualified # 🔠 E0.6 input latin uppercase +1F521 ; fully-qualified # 🔡 E0.6 input latin lowercase +1F522 ; fully-qualified # 🔢 E0.6 input numbers +1F523 ; fully-qualified # 🔣 E0.6 input symbols +1F524 ; fully-qualified # 🔤 E0.6 input latin letters +1F170 FE0F ; fully-qualified # 🅰️ E0.6 A button (blood type) +1F170 ; unqualified # 🅰 E0.6 A button (blood type) +1F18E ; fully-qualified # 🆎 E0.6 AB button (blood type) +1F171 FE0F ; fully-qualified # 🅱️ E0.6 B button (blood type) +1F171 ; unqualified # 🅱 E0.6 B button (blood type) +1F191 ; fully-qualified # 🆑 E0.6 CL button +1F192 ; fully-qualified # 🆒 E0.6 COOL button +1F193 ; fully-qualified # 🆓 E0.6 FREE button +2139 FE0F ; fully-qualified # ℹ️ E0.6 information +2139 ; unqualified # ℹ E0.6 information +1F194 ; fully-qualified # 🆔 E0.6 ID button +24C2 FE0F ; fully-qualified # Ⓜ️ E0.6 circled M +24C2 ; unqualified # Ⓜ E0.6 circled M +1F195 ; fully-qualified # 🆕 E0.6 NEW button +1F196 ; fully-qualified # 🆖 E0.6 NG button +1F17E FE0F ; fully-qualified # 🅾️ E0.6 O button (blood type) +1F17E ; unqualified # 🅾 E0.6 O button (blood type) +1F197 ; fully-qualified # 🆗 E0.6 OK button +1F17F FE0F ; fully-qualified # 🅿️ E0.6 P button +1F17F ; unqualified # 🅿 E0.6 P button +1F198 ; fully-qualified # 🆘 E0.6 SOS button +1F199 ; fully-qualified # 🆙 E0.6 UP! button +1F19A ; fully-qualified # 🆚 E0.6 VS button +1F201 ; fully-qualified # 🈁 E0.6 Japanese “here” button +1F202 FE0F ; fully-qualified # 🈂️ E0.6 Japanese “service charge” button +1F202 ; unqualified # 🈂 E0.6 Japanese “service charge” button +1F237 FE0F ; fully-qualified # 🈷️ E0.6 Japanese “monthly amount” button +1F237 ; unqualified # 🈷 E0.6 Japanese “monthly amount” button +1F236 ; fully-qualified # 🈶 E0.6 Japanese “not free of charge” button +1F22F ; fully-qualified # 🈯 E0.6 Japanese “reserved” button +1F250 ; fully-qualified # 🉐 E0.6 Japanese “bargain” button +1F239 ; fully-qualified # 🈹 E0.6 Japanese “discount” button +1F21A ; fully-qualified # 🈚 E0.6 Japanese “free of charge” button +1F232 ; fully-qualified # 🈲 E0.6 Japanese “prohibited” button +1F251 ; fully-qualified # 🉑 E0.6 Japanese “acceptable” button +1F238 ; fully-qualified # 🈸 E0.6 Japanese “application” button +1F234 ; fully-qualified # 🈴 E0.6 Japanese “passing grade” button +1F233 ; fully-qualified # 🈳 E0.6 Japanese “vacancy” button +3297 FE0F ; fully-qualified # ㊗️ E0.6 Japanese “congratulations” button +3297 ; unqualified # ㊗ E0.6 Japanese “congratulations” button +3299 FE0F ; fully-qualified # ㊙️ E0.6 Japanese “secret” button +3299 ; unqualified # ㊙ E0.6 Japanese “secret” button +1F23A ; fully-qualified # 🈺 E0.6 Japanese “open for business” button +1F235 ; fully-qualified # 🈵 E0.6 Japanese “no vacancy” button + +# subgroup: geometric +1F534 ; fully-qualified # 🔴 E0.6 red circle +1F7E0 ; fully-qualified # 🟠 E12.0 orange circle +1F7E1 ; fully-qualified # 🟡 E12.0 yellow circle +1F7E2 ; fully-qualified # 🟢 E12.0 green circle +1F535 ; fully-qualified # 🔵 E0.6 blue circle +1F7E3 ; fully-qualified # 🟣 E12.0 purple circle +1F7E4 ; fully-qualified # 🟤 E12.0 brown circle +26AB ; fully-qualified # ⚫ E0.6 black circle +26AA ; fully-qualified # ⚪ E0.6 white circle +1F7E5 ; fully-qualified # 🟥 E12.0 red square +1F7E7 ; fully-qualified # 🟧 E12.0 orange square +1F7E8 ; fully-qualified # 🟨 E12.0 yellow square +1F7E9 ; fully-qualified # 🟩 E12.0 green square +1F7E6 ; fully-qualified # 🟦 E12.0 blue square +1F7EA ; fully-qualified # 🟪 E12.0 purple square +1F7EB ; fully-qualified # 🟫 E12.0 brown square +2B1B ; fully-qualified # ⬛ E0.6 black large square +2B1C ; fully-qualified # ⬜ E0.6 white large square +25FC FE0F ; fully-qualified # ◼️ E0.6 black medium square +25FC ; unqualified # ◼ E0.6 black medium square +25FB FE0F ; fully-qualified # ◻️ E0.6 white medium square +25FB ; unqualified # ◻ E0.6 white medium square +25FE ; fully-qualified # ◾ E0.6 black medium-small square +25FD ; fully-qualified # ◽ E0.6 white medium-small square +25AA FE0F ; fully-qualified # ▪️ E0.6 black small square +25AA ; unqualified # ▪ E0.6 black small square +25AB FE0F ; fully-qualified # ▫️ E0.6 white small square +25AB ; unqualified # ▫ E0.6 white small square +1F536 ; fully-qualified # 🔶 E0.6 large orange diamond +1F537 ; fully-qualified # 🔷 E0.6 large blue diamond +1F538 ; fully-qualified # 🔸 E0.6 small orange diamond +1F539 ; fully-qualified # 🔹 E0.6 small blue diamond +1F53A ; fully-qualified # 🔺 E0.6 red triangle pointed up +1F53B ; fully-qualified # 🔻 E0.6 red triangle pointed down +1F4A0 ; fully-qualified # 💠 E0.6 diamond with a dot +1F518 ; fully-qualified # 🔘 E0.6 radio button +1F533 ; fully-qualified # 🔳 E0.6 white square button +1F532 ; fully-qualified # 🔲 E0.6 black square button + +# Symbols subtotal: 301 +# Symbols subtotal: 301 w/o modifiers + +# group: Flags + +# subgroup: flag +1F3C1 ; fully-qualified # 🏁 E0.6 chequered flag +1F6A9 ; fully-qualified # 🚩 E0.6 triangular flag +1F38C ; fully-qualified # 🎌 E0.6 crossed flags +1F3F4 ; fully-qualified # 🏴 E1.0 black flag +1F3F3 FE0F ; fully-qualified # 🏳️ E0.7 white flag +1F3F3 ; unqualified # 🏳 E0.7 white flag +1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 E4.0 rainbow flag +1F3F3 200D 1F308 ; unqualified # 🏳‍🌈 E4.0 rainbow flag +1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # 🏳️‍⚧️ E13.0 transgender flag +1F3F3 200D 26A7 FE0F ; unqualified # 🏳‍⚧️ E13.0 transgender flag +1F3F3 FE0F 200D 26A7 ; unqualified # 🏳️‍⚧ E13.0 transgender flag +1F3F3 200D 26A7 ; unqualified # 🏳‍⚧ E13.0 transgender flag +1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ E11.0 pirate flag +1F3F4 200D 2620 ; minimally-qualified # 🏴‍☠ E11.0 pirate flag + +# subgroup: country-flag +1F1E6 1F1E8 ; fully-qualified # 🇦🇨 E2.0 flag: Ascension Island +1F1E6 1F1E9 ; fully-qualified # 🇦🇩 E2.0 flag: Andorra +1F1E6 1F1EA ; fully-qualified # 🇦🇪 E2.0 flag: United Arab Emirates +1F1E6 1F1EB ; fully-qualified # 🇦🇫 E2.0 flag: Afghanistan +1F1E6 1F1EC ; fully-qualified # 🇦🇬 E2.0 flag: Antigua & Barbuda +1F1E6 1F1EE ; fully-qualified # 🇦🇮 E2.0 flag: Anguilla +1F1E6 1F1F1 ; fully-qualified # 🇦🇱 E2.0 flag: Albania +1F1E6 1F1F2 ; fully-qualified # 🇦🇲 E2.0 flag: Armenia +1F1E6 1F1F4 ; fully-qualified # 🇦🇴 E2.0 flag: Angola +1F1E6 1F1F6 ; fully-qualified # 🇦🇶 E2.0 flag: Antarctica +1F1E6 1F1F7 ; fully-qualified # 🇦🇷 E2.0 flag: Argentina +1F1E6 1F1F8 ; fully-qualified # 🇦🇸 E2.0 flag: American Samoa +1F1E6 1F1F9 ; fully-qualified # 🇦🇹 E2.0 flag: Austria +1F1E6 1F1FA ; fully-qualified # 🇦🇺 E2.0 flag: Australia +1F1E6 1F1FC ; fully-qualified # 🇦🇼 E2.0 flag: Aruba +1F1E6 1F1FD ; fully-qualified # 🇦🇽 E2.0 flag: Åland Islands +1F1E6 1F1FF ; fully-qualified # 🇦🇿 E2.0 flag: Azerbaijan +1F1E7 1F1E6 ; fully-qualified # 🇧🇦 E2.0 flag: Bosnia & Herzegovina +1F1E7 1F1E7 ; fully-qualified # 🇧🇧 E2.0 flag: Barbados +1F1E7 1F1E9 ; fully-qualified # 🇧🇩 E2.0 flag: Bangladesh +1F1E7 1F1EA ; fully-qualified # 🇧🇪 E2.0 flag: Belgium +1F1E7 1F1EB ; fully-qualified # 🇧🇫 E2.0 flag: Burkina Faso +1F1E7 1F1EC ; fully-qualified # 🇧🇬 E2.0 flag: Bulgaria +1F1E7 1F1ED ; fully-qualified # 🇧🇭 E2.0 flag: Bahrain +1F1E7 1F1EE ; fully-qualified # 🇧🇮 E2.0 flag: Burundi +1F1E7 1F1EF ; fully-qualified # 🇧🇯 E2.0 flag: Benin +1F1E7 1F1F1 ; fully-qualified # 🇧🇱 E2.0 flag: St. Barthélemy +1F1E7 1F1F2 ; fully-qualified # 🇧🇲 E2.0 flag: Bermuda +1F1E7 1F1F3 ; fully-qualified # 🇧🇳 E2.0 flag: Brunei +1F1E7 1F1F4 ; fully-qualified # 🇧🇴 E2.0 flag: Bolivia +1F1E7 1F1F6 ; fully-qualified # 🇧🇶 E2.0 flag: Caribbean Netherlands +1F1E7 1F1F7 ; fully-qualified # 🇧🇷 E2.0 flag: Brazil +1F1E7 1F1F8 ; fully-qualified # 🇧🇸 E2.0 flag: Bahamas +1F1E7 1F1F9 ; fully-qualified # 🇧🇹 E2.0 flag: Bhutan +1F1E7 1F1FB ; fully-qualified # 🇧🇻 E2.0 flag: Bouvet Island +1F1E7 1F1FC ; fully-qualified # 🇧🇼 E2.0 flag: Botswana +1F1E7 1F1FE ; fully-qualified # 🇧🇾 E2.0 flag: Belarus +1F1E7 1F1FF ; fully-qualified # 🇧🇿 E2.0 flag: Belize +1F1E8 1F1E6 ; fully-qualified # 🇨🇦 E2.0 flag: Canada +1F1E8 1F1E8 ; fully-qualified # 🇨🇨 E2.0 flag: Cocos (Keeling) Islands +1F1E8 1F1E9 ; fully-qualified # 🇨🇩 E2.0 flag: Congo - Kinshasa +1F1E8 1F1EB ; fully-qualified # 🇨🇫 E2.0 flag: Central African Republic +1F1E8 1F1EC ; fully-qualified # 🇨🇬 E2.0 flag: Congo - Brazzaville +1F1E8 1F1ED ; fully-qualified # 🇨🇭 E2.0 flag: Switzerland +1F1E8 1F1EE ; fully-qualified # 🇨🇮 E2.0 flag: Côte d’Ivoire +1F1E8 1F1F0 ; fully-qualified # 🇨🇰 E2.0 flag: Cook Islands +1F1E8 1F1F1 ; fully-qualified # 🇨🇱 E2.0 flag: Chile +1F1E8 1F1F2 ; fully-qualified # 🇨🇲 E2.0 flag: Cameroon +1F1E8 1F1F3 ; fully-qualified # 🇨🇳 E0.6 flag: China +1F1E8 1F1F4 ; fully-qualified # 🇨🇴 E2.0 flag: Colombia +1F1E8 1F1F5 ; fully-qualified # 🇨🇵 E2.0 flag: Clipperton Island +1F1E8 1F1F7 ; fully-qualified # 🇨🇷 E2.0 flag: Costa Rica +1F1E8 1F1FA ; fully-qualified # 🇨🇺 E2.0 flag: Cuba +1F1E8 1F1FB ; fully-qualified # 🇨🇻 E2.0 flag: Cape Verde +1F1E8 1F1FC ; fully-qualified # 🇨🇼 E2.0 flag: Curaçao +1F1E8 1F1FD ; fully-qualified # 🇨🇽 E2.0 flag: Christmas Island +1F1E8 1F1FE ; fully-qualified # 🇨🇾 E2.0 flag: Cyprus +1F1E8 1F1FF ; fully-qualified # 🇨🇿 E2.0 flag: Czechia +1F1E9 1F1EA ; fully-qualified # 🇩🇪 E0.6 flag: Germany +1F1E9 1F1EC ; fully-qualified # 🇩🇬 E2.0 flag: Diego Garcia +1F1E9 1F1EF ; fully-qualified # 🇩🇯 E2.0 flag: Djibouti +1F1E9 1F1F0 ; fully-qualified # 🇩🇰 E2.0 flag: Denmark +1F1E9 1F1F2 ; fully-qualified # 🇩🇲 E2.0 flag: Dominica +1F1E9 1F1F4 ; fully-qualified # 🇩🇴 E2.0 flag: Dominican Republic +1F1E9 1F1FF ; fully-qualified # 🇩🇿 E2.0 flag: Algeria +1F1EA 1F1E6 ; fully-qualified # 🇪🇦 E2.0 flag: Ceuta & Melilla +1F1EA 1F1E8 ; fully-qualified # 🇪🇨 E2.0 flag: Ecuador +1F1EA 1F1EA ; fully-qualified # 🇪🇪 E2.0 flag: Estonia +1F1EA 1F1EC ; fully-qualified # 🇪🇬 E2.0 flag: Egypt +1F1EA 1F1ED ; fully-qualified # 🇪🇭 E2.0 flag: Western Sahara +1F1EA 1F1F7 ; fully-qualified # 🇪🇷 E2.0 flag: Eritrea +1F1EA 1F1F8 ; fully-qualified # 🇪🇸 E0.6 flag: Spain +1F1EA 1F1F9 ; fully-qualified # 🇪🇹 E2.0 flag: Ethiopia +1F1EA 1F1FA ; fully-qualified # 🇪🇺 E2.0 flag: European Union +1F1EB 1F1EE ; fully-qualified # 🇫🇮 E2.0 flag: Finland +1F1EB 1F1EF ; fully-qualified # 🇫🇯 E2.0 flag: Fiji +1F1EB 1F1F0 ; fully-qualified # 🇫🇰 E2.0 flag: Falkland Islands +1F1EB 1F1F2 ; fully-qualified # 🇫🇲 E2.0 flag: Micronesia +1F1EB 1F1F4 ; fully-qualified # 🇫🇴 E2.0 flag: Faroe Islands +1F1EB 1F1F7 ; fully-qualified # 🇫🇷 E0.6 flag: France +1F1EC 1F1E6 ; fully-qualified # 🇬🇦 E2.0 flag: Gabon +1F1EC 1F1E7 ; fully-qualified # 🇬🇧 E0.6 flag: United Kingdom +1F1EC 1F1E9 ; fully-qualified # 🇬🇩 E2.0 flag: Grenada +1F1EC 1F1EA ; fully-qualified # 🇬🇪 E2.0 flag: Georgia +1F1EC 1F1EB ; fully-qualified # 🇬🇫 E2.0 flag: French Guiana +1F1EC 1F1EC ; fully-qualified # 🇬🇬 E2.0 flag: Guernsey +1F1EC 1F1ED ; fully-qualified # 🇬🇭 E2.0 flag: Ghana +1F1EC 1F1EE ; fully-qualified # 🇬🇮 E2.0 flag: Gibraltar +1F1EC 1F1F1 ; fully-qualified # 🇬🇱 E2.0 flag: Greenland +1F1EC 1F1F2 ; fully-qualified # 🇬🇲 E2.0 flag: Gambia +1F1EC 1F1F3 ; fully-qualified # 🇬🇳 E2.0 flag: Guinea +1F1EC 1F1F5 ; fully-qualified # 🇬🇵 E2.0 flag: Guadeloupe +1F1EC 1F1F6 ; fully-qualified # 🇬🇶 E2.0 flag: Equatorial Guinea +1F1EC 1F1F7 ; fully-qualified # 🇬🇷 E2.0 flag: Greece +1F1EC 1F1F8 ; fully-qualified # 🇬🇸 E2.0 flag: South Georgia & South Sandwich Islands +1F1EC 1F1F9 ; fully-qualified # 🇬🇹 E2.0 flag: Guatemala +1F1EC 1F1FA ; fully-qualified # 🇬🇺 E2.0 flag: Guam +1F1EC 1F1FC ; fully-qualified # 🇬🇼 E2.0 flag: Guinea-Bissau +1F1EC 1F1FE ; fully-qualified # 🇬🇾 E2.0 flag: Guyana +1F1ED 1F1F0 ; fully-qualified # 🇭🇰 E2.0 flag: Hong Kong SAR China +1F1ED 1F1F2 ; fully-qualified # 🇭🇲 E2.0 flag: Heard & McDonald Islands +1F1ED 1F1F3 ; fully-qualified # 🇭🇳 E2.0 flag: Honduras +1F1ED 1F1F7 ; fully-qualified # 🇭🇷 E2.0 flag: Croatia +1F1ED 1F1F9 ; fully-qualified # 🇭🇹 E2.0 flag: Haiti +1F1ED 1F1FA ; fully-qualified # 🇭🇺 E2.0 flag: Hungary +1F1EE 1F1E8 ; fully-qualified # 🇮🇨 E2.0 flag: Canary Islands +1F1EE 1F1E9 ; fully-qualified # 🇮🇩 E2.0 flag: Indonesia +1F1EE 1F1EA ; fully-qualified # 🇮🇪 E2.0 flag: Ireland +1F1EE 1F1F1 ; fully-qualified # 🇮🇱 E2.0 flag: Israel +1F1EE 1F1F2 ; fully-qualified # 🇮🇲 E2.0 flag: Isle of Man +1F1EE 1F1F3 ; fully-qualified # 🇮🇳 E2.0 flag: India +1F1EE 1F1F4 ; fully-qualified # 🇮🇴 E2.0 flag: British Indian Ocean Territory +1F1EE 1F1F6 ; fully-qualified # 🇮🇶 E2.0 flag: Iraq +1F1EE 1F1F7 ; fully-qualified # 🇮🇷 E2.0 flag: Iran +1F1EE 1F1F8 ; fully-qualified # 🇮🇸 E2.0 flag: Iceland +1F1EE 1F1F9 ; fully-qualified # 🇮🇹 E0.6 flag: Italy +1F1EF 1F1EA ; fully-qualified # 🇯🇪 E2.0 flag: Jersey +1F1EF 1F1F2 ; fully-qualified # 🇯🇲 E2.0 flag: Jamaica +1F1EF 1F1F4 ; fully-qualified # 🇯🇴 E2.0 flag: Jordan +1F1EF 1F1F5 ; fully-qualified # 🇯🇵 E0.6 flag: Japan +1F1F0 1F1EA ; fully-qualified # 🇰🇪 E2.0 flag: Kenya +1F1F0 1F1EC ; fully-qualified # 🇰🇬 E2.0 flag: Kyrgyzstan +1F1F0 1F1ED ; fully-qualified # 🇰🇭 E2.0 flag: Cambodia +1F1F0 1F1EE ; fully-qualified # 🇰🇮 E2.0 flag: Kiribati +1F1F0 1F1F2 ; fully-qualified # 🇰🇲 E2.0 flag: Comoros +1F1F0 1F1F3 ; fully-qualified # 🇰🇳 E2.0 flag: St. Kitts & Nevis +1F1F0 1F1F5 ; fully-qualified # 🇰🇵 E2.0 flag: North Korea +1F1F0 1F1F7 ; fully-qualified # 🇰🇷 E0.6 flag: South Korea +1F1F0 1F1FC ; fully-qualified # 🇰🇼 E2.0 flag: Kuwait +1F1F0 1F1FE ; fully-qualified # 🇰🇾 E2.0 flag: Cayman Islands +1F1F0 1F1FF ; fully-qualified # 🇰🇿 E2.0 flag: Kazakhstan +1F1F1 1F1E6 ; fully-qualified # 🇱🇦 E2.0 flag: Laos +1F1F1 1F1E7 ; fully-qualified # 🇱🇧 E2.0 flag: Lebanon +1F1F1 1F1E8 ; fully-qualified # 🇱🇨 E2.0 flag: St. Lucia +1F1F1 1F1EE ; fully-qualified # 🇱🇮 E2.0 flag: Liechtenstein +1F1F1 1F1F0 ; fully-qualified # 🇱🇰 E2.0 flag: Sri Lanka +1F1F1 1F1F7 ; fully-qualified # 🇱🇷 E2.0 flag: Liberia +1F1F1 1F1F8 ; fully-qualified # 🇱🇸 E2.0 flag: Lesotho +1F1F1 1F1F9 ; fully-qualified # 🇱🇹 E2.0 flag: Lithuania +1F1F1 1F1FA ; fully-qualified # 🇱🇺 E2.0 flag: Luxembourg +1F1F1 1F1FB ; fully-qualified # 🇱🇻 E2.0 flag: Latvia +1F1F1 1F1FE ; fully-qualified # 🇱🇾 E2.0 flag: Libya +1F1F2 1F1E6 ; fully-qualified # 🇲🇦 E2.0 flag: Morocco +1F1F2 1F1E8 ; fully-qualified # 🇲🇨 E2.0 flag: Monaco +1F1F2 1F1E9 ; fully-qualified # 🇲🇩 E2.0 flag: Moldova +1F1F2 1F1EA ; fully-qualified # 🇲🇪 E2.0 flag: Montenegro +1F1F2 1F1EB ; fully-qualified # 🇲🇫 E2.0 flag: St. Martin +1F1F2 1F1EC ; fully-qualified # 🇲🇬 E2.0 flag: Madagascar +1F1F2 1F1ED ; fully-qualified # 🇲🇭 E2.0 flag: Marshall Islands +1F1F2 1F1F0 ; fully-qualified # 🇲🇰 E2.0 flag: North Macedonia +1F1F2 1F1F1 ; fully-qualified # 🇲🇱 E2.0 flag: Mali +1F1F2 1F1F2 ; fully-qualified # 🇲🇲 E2.0 flag: Myanmar (Burma) +1F1F2 1F1F3 ; fully-qualified # 🇲🇳 E2.0 flag: Mongolia +1F1F2 1F1F4 ; fully-qualified # 🇲🇴 E2.0 flag: Macao SAR China +1F1F2 1F1F5 ; fully-qualified # 🇲🇵 E2.0 flag: Northern Mariana Islands +1F1F2 1F1F6 ; fully-qualified # 🇲🇶 E2.0 flag: Martinique +1F1F2 1F1F7 ; fully-qualified # 🇲🇷 E2.0 flag: Mauritania +1F1F2 1F1F8 ; fully-qualified # 🇲🇸 E2.0 flag: Montserrat +1F1F2 1F1F9 ; fully-qualified # 🇲🇹 E2.0 flag: Malta +1F1F2 1F1FA ; fully-qualified # 🇲🇺 E2.0 flag: Mauritius +1F1F2 1F1FB ; fully-qualified # 🇲🇻 E2.0 flag: Maldives +1F1F2 1F1FC ; fully-qualified # 🇲🇼 E2.0 flag: Malawi +1F1F2 1F1FD ; fully-qualified # 🇲🇽 E2.0 flag: Mexico +1F1F2 1F1FE ; fully-qualified # 🇲🇾 E2.0 flag: Malaysia +1F1F2 1F1FF ; fully-qualified # 🇲🇿 E2.0 flag: Mozambique +1F1F3 1F1E6 ; fully-qualified # 🇳🇦 E2.0 flag: Namibia +1F1F3 1F1E8 ; fully-qualified # 🇳🇨 E2.0 flag: New Caledonia +1F1F3 1F1EA ; fully-qualified # 🇳🇪 E2.0 flag: Niger +1F1F3 1F1EB ; fully-qualified # 🇳🇫 E2.0 flag: Norfolk Island +1F1F3 1F1EC ; fully-qualified # 🇳🇬 E2.0 flag: Nigeria +1F1F3 1F1EE ; fully-qualified # 🇳🇮 E2.0 flag: Nicaragua +1F1F3 1F1F1 ; fully-qualified # 🇳🇱 E2.0 flag: Netherlands +1F1F3 1F1F4 ; fully-qualified # 🇳🇴 E2.0 flag: Norway +1F1F3 1F1F5 ; fully-qualified # 🇳🇵 E2.0 flag: Nepal +1F1F3 1F1F7 ; fully-qualified # 🇳🇷 E2.0 flag: Nauru +1F1F3 1F1FA ; fully-qualified # 🇳🇺 E2.0 flag: Niue +1F1F3 1F1FF ; fully-qualified # 🇳🇿 E2.0 flag: New Zealand +1F1F4 1F1F2 ; fully-qualified # 🇴🇲 E2.0 flag: Oman +1F1F5 1F1E6 ; fully-qualified # 🇵🇦 E2.0 flag: Panama +1F1F5 1F1EA ; fully-qualified # 🇵🇪 E2.0 flag: Peru +1F1F5 1F1EB ; fully-qualified # 🇵🇫 E2.0 flag: French Polynesia +1F1F5 1F1EC ; fully-qualified # 🇵🇬 E2.0 flag: Papua New Guinea +1F1F5 1F1ED ; fully-qualified # 🇵🇭 E2.0 flag: Philippines +1F1F5 1F1F0 ; fully-qualified # 🇵🇰 E2.0 flag: Pakistan +1F1F5 1F1F1 ; fully-qualified # 🇵🇱 E2.0 flag: Poland +1F1F5 1F1F2 ; fully-qualified # 🇵🇲 E2.0 flag: St. Pierre & Miquelon +1F1F5 1F1F3 ; fully-qualified # 🇵🇳 E2.0 flag: Pitcairn Islands +1F1F5 1F1F7 ; fully-qualified # 🇵🇷 E2.0 flag: Puerto Rico +1F1F5 1F1F8 ; fully-qualified # 🇵🇸 E2.0 flag: Palestinian Territories +1F1F5 1F1F9 ; fully-qualified # 🇵🇹 E2.0 flag: Portugal +1F1F5 1F1FC ; fully-qualified # 🇵🇼 E2.0 flag: Palau +1F1F5 1F1FE ; fully-qualified # 🇵🇾 E2.0 flag: Paraguay +1F1F6 1F1E6 ; fully-qualified # 🇶🇦 E2.0 flag: Qatar +1F1F7 1F1EA ; fully-qualified # 🇷🇪 E2.0 flag: Réunion +1F1F7 1F1F4 ; fully-qualified # 🇷🇴 E2.0 flag: Romania +1F1F7 1F1F8 ; fully-qualified # 🇷🇸 E2.0 flag: Serbia +1F1F7 1F1FA ; fully-qualified # 🇷🇺 E0.6 flag: Russia +1F1F7 1F1FC ; fully-qualified # 🇷🇼 E2.0 flag: Rwanda +1F1F8 1F1E6 ; fully-qualified # 🇸🇦 E2.0 flag: Saudi Arabia +1F1F8 1F1E7 ; fully-qualified # 🇸🇧 E2.0 flag: Solomon Islands +1F1F8 1F1E8 ; fully-qualified # 🇸🇨 E2.0 flag: Seychelles +1F1F8 1F1E9 ; fully-qualified # 🇸🇩 E2.0 flag: Sudan +1F1F8 1F1EA ; fully-qualified # 🇸🇪 E2.0 flag: Sweden +1F1F8 1F1EC ; fully-qualified # 🇸🇬 E2.0 flag: Singapore +1F1F8 1F1ED ; fully-qualified # 🇸🇭 E2.0 flag: St. Helena +1F1F8 1F1EE ; fully-qualified # 🇸🇮 E2.0 flag: Slovenia +1F1F8 1F1EF ; fully-qualified # 🇸🇯 E2.0 flag: Svalbard & Jan Mayen +1F1F8 1F1F0 ; fully-qualified # 🇸🇰 E2.0 flag: Slovakia +1F1F8 1F1F1 ; fully-qualified # 🇸🇱 E2.0 flag: Sierra Leone +1F1F8 1F1F2 ; fully-qualified # 🇸🇲 E2.0 flag: San Marino +1F1F8 1F1F3 ; fully-qualified # 🇸🇳 E2.0 flag: Senegal +1F1F8 1F1F4 ; fully-qualified # 🇸🇴 E2.0 flag: Somalia +1F1F8 1F1F7 ; fully-qualified # 🇸🇷 E2.0 flag: Suriname +1F1F8 1F1F8 ; fully-qualified # 🇸🇸 E2.0 flag: South Sudan +1F1F8 1F1F9 ; fully-qualified # 🇸🇹 E2.0 flag: São Tomé & Príncipe +1F1F8 1F1FB ; fully-qualified # 🇸🇻 E2.0 flag: El Salvador +1F1F8 1F1FD ; fully-qualified # 🇸🇽 E2.0 flag: Sint Maarten +1F1F8 1F1FE ; fully-qualified # 🇸🇾 E2.0 flag: Syria +1F1F8 1F1FF ; fully-qualified # 🇸🇿 E2.0 flag: Eswatini +1F1F9 1F1E6 ; fully-qualified # 🇹🇦 E2.0 flag: Tristan da Cunha +1F1F9 1F1E8 ; fully-qualified # 🇹🇨 E2.0 flag: Turks & Caicos Islands +1F1F9 1F1E9 ; fully-qualified # 🇹🇩 E2.0 flag: Chad +1F1F9 1F1EB ; fully-qualified # 🇹🇫 E2.0 flag: French Southern Territories +1F1F9 1F1EC ; fully-qualified # 🇹🇬 E2.0 flag: Togo +1F1F9 1F1ED ; fully-qualified # 🇹🇭 E2.0 flag: Thailand +1F1F9 1F1EF ; fully-qualified # 🇹🇯 E2.0 flag: Tajikistan +1F1F9 1F1F0 ; fully-qualified # 🇹🇰 E2.0 flag: Tokelau +1F1F9 1F1F1 ; fully-qualified # 🇹🇱 E2.0 flag: Timor-Leste +1F1F9 1F1F2 ; fully-qualified # 🇹🇲 E2.0 flag: Turkmenistan +1F1F9 1F1F3 ; fully-qualified # 🇹🇳 E2.0 flag: Tunisia +1F1F9 1F1F4 ; fully-qualified # 🇹🇴 E2.0 flag: Tonga +1F1F9 1F1F7 ; fully-qualified # 🇹🇷 E2.0 flag: Turkey +1F1F9 1F1F9 ; fully-qualified # 🇹🇹 E2.0 flag: Trinidad & Tobago +1F1F9 1F1FB ; fully-qualified # 🇹🇻 E2.0 flag: Tuvalu +1F1F9 1F1FC ; fully-qualified # 🇹🇼 E2.0 flag: Taiwan +1F1F9 1F1FF ; fully-qualified # 🇹🇿 E2.0 flag: Tanzania +1F1FA 1F1E6 ; fully-qualified # 🇺🇦 E2.0 flag: Ukraine +1F1FA 1F1EC ; fully-qualified # 🇺🇬 E2.0 flag: Uganda +1F1FA 1F1F2 ; fully-qualified # 🇺🇲 E2.0 flag: U.S. Outlying Islands +1F1FA 1F1F3 ; fully-qualified # 🇺🇳 E4.0 flag: United Nations +1F1FA 1F1F8 ; fully-qualified # 🇺🇸 E0.6 flag: United States +1F1FA 1F1FE ; fully-qualified # 🇺🇾 E2.0 flag: Uruguay +1F1FA 1F1FF ; fully-qualified # 🇺🇿 E2.0 flag: Uzbekistan +1F1FB 1F1E6 ; fully-qualified # 🇻🇦 E2.0 flag: Vatican City +1F1FB 1F1E8 ; fully-qualified # 🇻🇨 E2.0 flag: St. Vincent & Grenadines +1F1FB 1F1EA ; fully-qualified # 🇻🇪 E2.0 flag: Venezuela +1F1FB 1F1EC ; fully-qualified # 🇻🇬 E2.0 flag: British Virgin Islands +1F1FB 1F1EE ; fully-qualified # 🇻🇮 E2.0 flag: U.S. Virgin Islands +1F1FB 1F1F3 ; fully-qualified # 🇻🇳 E2.0 flag: Vietnam +1F1FB 1F1FA ; fully-qualified # 🇻🇺 E2.0 flag: Vanuatu +1F1FC 1F1EB ; fully-qualified # 🇼🇫 E2.0 flag: Wallis & Futuna +1F1FC 1F1F8 ; fully-qualified # 🇼🇸 E2.0 flag: Samoa +1F1FD 1F1F0 ; fully-qualified # 🇽🇰 E2.0 flag: Kosovo +1F1FE 1F1EA ; fully-qualified # 🇾🇪 E2.0 flag: Yemen +1F1FE 1F1F9 ; fully-qualified # 🇾🇹 E2.0 flag: Mayotte +1F1FF 1F1E6 ; fully-qualified # 🇿🇦 E2.0 flag: South Africa +1F1FF 1F1F2 ; fully-qualified # 🇿🇲 E2.0 flag: Zambia +1F1FF 1F1FC ; fully-qualified # 🇿🇼 E2.0 flag: Zimbabwe + +# subgroup: subdivision-flag +1F3F4 E0067 E0062 E0065 E006E E0067 E007F ; fully-qualified # 🏴󠁧󠁢󠁥󠁮󠁧󠁿 E5.0 flag: England +1F3F4 E0067 E0062 E0073 E0063 E0074 E007F ; fully-qualified # 🏴󠁧󠁢󠁳󠁣󠁴󠁿 E5.0 flag: Scotland +1F3F4 E0067 E0062 E0077 E006C E0073 E007F ; fully-qualified # 🏴󠁧󠁢󠁷󠁬󠁳󠁿 E5.0 flag: Wales + +# Flags subtotal: 275 +# Flags subtotal: 275 w/o modifiers + +# Status Counts +# fully-qualified : 3512 +# minimally-qualified : 817 +# unqualified : 252 +# component : 9 + +#EOF diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 04936155b..98644f84e 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -102,7 +102,7 @@ defmodule Pleroma.Emoji do :ets.insert(@ets, emojis) end - @external_resource "lib/pleroma/emoji-data.txt" + @external_resource "lib/pleroma/emoji-test.txt" emojis = @external_resource @@ -114,17 +114,12 @@ defmodule Pleroma.Emoji do |> String.split(";", parts: 2) |> hd() |> String.trim() - |> String.split("..") - |> case do - [number] -> - <> - - [first, last] -> - String.to_integer(first, 16)..String.to_integer(last, 16) - |> Enum.map(&<<&1::utf8>>) - end + |> String.split() + |> Enum.map(fn codepoint -> + <> + end) + |> Enum.join() end) - |> List.flatten() |> Enum.uniq() for emoji <- emojis do -- cgit v1.2.3 From b6f5e9ac9c801f4fc765629fce5846e447f0ec33 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Dec 2020 16:15:31 +0100 Subject: Emoji: Remove unused emoji-data.txt --- lib/pleroma/emoji-data.txt | 769 --------------------------------------------- 1 file changed, 769 deletions(-) delete mode 100644 lib/pleroma/emoji-data.txt (limited to 'lib') diff --git a/lib/pleroma/emoji-data.txt b/lib/pleroma/emoji-data.txt deleted file mode 100644 index 2fb5c3ff6..000000000 --- a/lib/pleroma/emoji-data.txt +++ /dev/null @@ -1,769 +0,0 @@ -# emoji-data.txt -# Date: 2019-01-15, 12:10:05 GMT -# © 2019 Unicode®, Inc. -# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see http://www.unicode.org/terms_of_use.html -# -# Emoji Data for UTS #51 -# Version: 12.0 -# -# For documentation and usage, see http://www.unicode.org/reports/tr51 -# -# Format: -# ; # -# Note: there is no guarantee as to the structure of whitespace or comments -# -# Characters and sequences are listed in code point order. Users should be shown a more natural order. -# See the CLDR collation order for Emoji. - - -# ================================================ - -# All omitted code points have Emoji=No -# @missing: 0000..10FFFF ; Emoji ; No - -0023 ; Emoji # 1.1 [1] (#️) number sign -002A ; Emoji # 1.1 [1] (*️) asterisk -0030..0039 ; Emoji # 1.1 [10] (0️..9️) digit zero..digit nine -00A9 ; Emoji # 1.1 [1] (©️) copyright -00AE ; Emoji # 1.1 [1] (®️) registered -203C ; Emoji # 1.1 [1] (‼️) double exclamation mark -2049 ; Emoji # 3.0 [1] (⁉️) exclamation question mark -2122 ; Emoji # 1.1 [1] (™️) trade mark -2139 ; Emoji # 3.0 [1] (ℹ️) information -2194..2199 ; Emoji # 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow -21A9..21AA ; Emoji # 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right -231A..231B ; Emoji # 1.1 [2] (⌚..⌛) watch..hourglass done -2328 ; Emoji # 1.1 [1] (⌨️) keyboard -23CF ; Emoji # 4.0 [1] (⏏️) eject button -23E9..23F3 ; Emoji # 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done -23F8..23FA ; Emoji # 7.0 [3] (⏸️..⏺️) pause button..record button -24C2 ; Emoji # 1.1 [1] (Ⓜ️) circled M -25AA..25AB ; Emoji # 1.1 [2] (▪️..▫️) black small square..white small square -25B6 ; Emoji # 1.1 [1] (▶️) play button -25C0 ; Emoji # 1.1 [1] (◀️) reverse button -25FB..25FE ; Emoji # 3.2 [4] (◻️..◾) white medium square..black medium-small square -2600..2604 ; Emoji # 1.1 [5] (☀️..☄️) sun..comet -260E ; Emoji # 1.1 [1] (☎️) telephone -2611 ; Emoji # 1.1 [1] (☑️) check box with check -2614..2615 ; Emoji # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage -2618 ; Emoji # 4.1 [1] (☘️) shamrock -261D ; Emoji # 1.1 [1] (☝️) index pointing up -2620 ; Emoji # 1.1 [1] (☠️) skull and crossbones -2622..2623 ; Emoji # 1.1 [2] (☢️..☣️) radioactive..biohazard -2626 ; Emoji # 1.1 [1] (☦️) orthodox cross -262A ; Emoji # 1.1 [1] (☪️) star and crescent -262E..262F ; Emoji # 1.1 [2] (☮️..☯️) peace symbol..yin yang -2638..263A ; Emoji # 1.1 [3] (☸️..☺️) wheel of dharma..smiling face -2640 ; Emoji # 1.1 [1] (♀️) female sign -2642 ; Emoji # 1.1 [1] (♂️) male sign -2648..2653 ; Emoji # 1.1 [12] (♈..♓) Aries..Pisces -265F..2660 ; Emoji # 1.1 [2] (♟️..♠️) chess pawn..spade suit -2663 ; Emoji # 1.1 [1] (♣️) club suit -2665..2666 ; Emoji # 1.1 [2] (♥️..♦️) heart suit..diamond suit -2668 ; Emoji # 1.1 [1] (♨️) hot springs -267B ; Emoji # 3.2 [1] (♻️) recycling symbol -267E..267F ; Emoji # 4.1 [2] (♾️..♿) infinity..wheelchair symbol -2692..2697 ; Emoji # 4.1 [6] (⚒️..⚗️) hammer and pick..alembic -2699 ; Emoji # 4.1 [1] (⚙️) gear -269B..269C ; Emoji # 4.1 [2] (⚛️..⚜️) atom symbol..fleur-de-lis -26A0..26A1 ; Emoji # 4.0 [2] (⚠️..⚡) warning..high voltage -26AA..26AB ; Emoji # 4.1 [2] (⚪..⚫) white circle..black circle -26B0..26B1 ; Emoji # 4.1 [2] (⚰️..⚱️) coffin..funeral urn -26BD..26BE ; Emoji # 5.2 [2] (⚽..⚾) soccer ball..baseball -26C4..26C5 ; Emoji # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud -26C8 ; Emoji # 5.2 [1] (⛈️) cloud with lightning and rain -26CE ; Emoji # 6.0 [1] (⛎) Ophiuchus -26CF ; Emoji # 5.2 [1] (⛏️) pick -26D1 ; Emoji # 5.2 [1] (⛑️) rescue worker’s helmet -26D3..26D4 ; Emoji # 5.2 [2] (⛓️..⛔) chains..no entry -26E9..26EA ; Emoji # 5.2 [2] (⛩️..⛪) shinto shrine..church -26F0..26F5 ; Emoji # 5.2 [6] (⛰️..⛵) mountain..sailboat -26F7..26FA ; Emoji # 5.2 [4] (⛷️..⛺) skier..tent -26FD ; Emoji # 5.2 [1] (⛽) fuel pump -2702 ; Emoji # 1.1 [1] (✂️) scissors -2705 ; Emoji # 6.0 [1] (✅) check mark button -2708..2709 ; Emoji # 1.1 [2] (✈️..✉️) airplane..envelope -270A..270B ; Emoji # 6.0 [2] (✊..✋) raised fist..raised hand -270C..270D ; Emoji # 1.1 [2] (✌️..✍️) victory hand..writing hand -270F ; Emoji # 1.1 [1] (✏️) pencil -2712 ; Emoji # 1.1 [1] (✒️) black nib -2714 ; Emoji # 1.1 [1] (✔️) check mark -2716 ; Emoji # 1.1 [1] (✖️) multiplication sign -271D ; Emoji # 1.1 [1] (✝️) latin cross -2721 ; Emoji # 1.1 [1] (✡️) star of David -2728 ; Emoji # 6.0 [1] (✨) sparkles -2733..2734 ; Emoji # 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star -2744 ; Emoji # 1.1 [1] (❄️) snowflake -2747 ; Emoji # 1.1 [1] (❇️) sparkle -274C ; Emoji # 6.0 [1] (❌) cross mark -274E ; Emoji # 6.0 [1] (❎) cross mark button -2753..2755 ; Emoji # 6.0 [3] (❓..❕) question mark..white exclamation mark -2757 ; Emoji # 5.2 [1] (❗) exclamation mark -2763..2764 ; Emoji # 1.1 [2] (❣️..❤️) heart exclamation..red heart -2795..2797 ; Emoji # 6.0 [3] (➕..➗) plus sign..division sign -27A1 ; Emoji # 1.1 [1] (➡️) right arrow -27B0 ; Emoji # 6.0 [1] (➰) curly loop -27BF ; Emoji # 6.0 [1] (➿) double curly loop -2934..2935 ; Emoji # 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down -2B05..2B07 ; Emoji # 4.0 [3] (⬅️..⬇️) left arrow..down arrow -2B1B..2B1C ; Emoji # 5.1 [2] (⬛..⬜) black large square..white large square -2B50 ; Emoji # 5.1 [1] (⭐) star -2B55 ; Emoji # 5.2 [1] (⭕) hollow red circle -3030 ; Emoji # 1.1 [1] (〰️) wavy dash -303D ; Emoji # 3.2 [1] (〽️) part alternation mark -3297 ; Emoji # 1.1 [1] (㊗️) Japanese “congratulations” button -3299 ; Emoji # 1.1 [1] (㊙️) Japanese “secret” button -1F004 ; Emoji # 5.1 [1] (🀄) mahjong red dragon -1F0CF ; Emoji # 6.0 [1] (🃏) joker -1F170..1F171 ; Emoji # 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) -1F17E ; Emoji # 6.0 [1] (🅾️) O button (blood type) -1F17F ; Emoji # 5.2 [1] (🅿️) P button -1F18E ; Emoji # 6.0 [1] (🆎) AB button (blood type) -1F191..1F19A ; Emoji # 6.0 [10] (🆑..🆚) CL button..VS button -1F1E6..1F1FF ; Emoji # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z -1F201..1F202 ; Emoji # 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button -1F21A ; Emoji # 5.2 [1] (🈚) Japanese “free of charge” button -1F22F ; Emoji # 5.2 [1] (🈯) Japanese “reserved” button -1F232..1F23A ; Emoji # 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button -1F250..1F251 ; Emoji # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F300..1F320 ; Emoji # 6.0 [33] (🌀..🌠) cyclone..shooting star -1F321 ; Emoji # 7.0 [1] (🌡️) thermometer -1F324..1F32C ; Emoji # 7.0 [9] (🌤️..🌬️) sun behind small cloud..wind face -1F32D..1F32F ; Emoji # 8.0 [3] (🌭..🌯) hot dog..burrito -1F330..1F335 ; Emoji # 6.0 [6] (🌰..🌵) chestnut..cactus -1F336 ; Emoji # 7.0 [1] (🌶️) hot pepper -1F337..1F37C ; Emoji # 6.0 [70] (🌷..🍼) tulip..baby bottle -1F37D ; Emoji # 7.0 [1] (🍽️) fork and knife with plate -1F37E..1F37F ; Emoji # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn -1F380..1F393 ; Emoji # 6.0 [20] (🎀..🎓) ribbon..graduation cap -1F396..1F397 ; Emoji # 7.0 [2] (🎖️..🎗️) military medal..reminder ribbon -1F399..1F39B ; Emoji # 7.0 [3] (🎙️..🎛️) studio microphone..control knobs -1F39E..1F39F ; Emoji # 7.0 [2] (🎞️..🎟️) film frames..admission tickets -1F3A0..1F3C4 ; Emoji # 6.0 [37] (🎠..🏄) carousel horse..person surfing -1F3C5 ; Emoji # 7.0 [1] (🏅) sports medal -1F3C6..1F3CA ; Emoji # 6.0 [5] (🏆..🏊) trophy..person swimming -1F3CB..1F3CE ; Emoji # 7.0 [4] (🏋️..🏎️) person lifting weights..racing car -1F3CF..1F3D3 ; Emoji # 8.0 [5] (🏏..🏓) cricket game..ping pong -1F3D4..1F3DF ; Emoji # 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium -1F3E0..1F3F0 ; Emoji # 6.0 [17] (🏠..🏰) house..castle -1F3F3..1F3F5 ; Emoji # 7.0 [3] (🏳️..🏵️) white flag..rosette -1F3F7 ; Emoji # 7.0 [1] (🏷️) label -1F3F8..1F3FF ; Emoji # 8.0 [8] (🏸..🏿) badminton..dark skin tone -1F400..1F43E ; Emoji # 6.0 [63] (🐀..🐾) rat..paw prints -1F43F ; Emoji # 7.0 [1] (🐿️) chipmunk -1F440 ; Emoji # 6.0 [1] (👀) eyes -1F441 ; Emoji # 7.0 [1] (👁️) eye -1F442..1F4F7 ; Emoji # 6.0[182] (👂..📷) ear..camera -1F4F8 ; Emoji # 7.0 [1] (📸) camera with flash -1F4F9..1F4FC ; Emoji # 6.0 [4] (📹..📼) video camera..videocassette -1F4FD ; Emoji # 7.0 [1] (📽️) film projector -1F4FF ; Emoji # 8.0 [1] (📿) prayer beads -1F500..1F53D ; Emoji # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button -1F549..1F54A ; Emoji # 7.0 [2] (🕉️..🕊️) om..dove -1F54B..1F54E ; Emoji # 8.0 [4] (🕋..🕎) kaaba..menorah -1F550..1F567 ; Emoji # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty -1F56F..1F570 ; Emoji # 7.0 [2] (🕯️..🕰️) candle..mantelpiece clock -1F573..1F579 ; Emoji # 7.0 [7] (🕳️..🕹️) hole..joystick -1F57A ; Emoji # 9.0 [1] (🕺) man dancing -1F587 ; Emoji # 7.0 [1] (🖇️) linked paperclips -1F58A..1F58D ; Emoji # 7.0 [4] (🖊️..🖍️) pen..crayon -1F590 ; Emoji # 7.0 [1] (🖐️) hand with fingers splayed -1F595..1F596 ; Emoji # 7.0 [2] (🖕..🖖) middle finger..vulcan salute -1F5A4 ; Emoji # 9.0 [1] (🖤) black heart -1F5A5 ; Emoji # 7.0 [1] (🖥️) desktop computer -1F5A8 ; Emoji # 7.0 [1] (🖨️) printer -1F5B1..1F5B2 ; Emoji # 7.0 [2] (🖱️..🖲️) computer mouse..trackball -1F5BC ; Emoji # 7.0 [1] (🖼️) framed picture -1F5C2..1F5C4 ; Emoji # 7.0 [3] (🗂️..🗄️) card index dividers..file cabinet -1F5D1..1F5D3 ; Emoji # 7.0 [3] (🗑️..🗓️) wastebasket..spiral calendar -1F5DC..1F5DE ; Emoji # 7.0 [3] (🗜️..🗞️) clamp..rolled-up newspaper -1F5E1 ; Emoji # 7.0 [1] (🗡️) dagger -1F5E3 ; Emoji # 7.0 [1] (🗣️) speaking head -1F5E8 ; Emoji # 7.0 [1] (🗨️) left speech bubble -1F5EF ; Emoji # 7.0 [1] (🗯️) right anger bubble -1F5F3 ; Emoji # 7.0 [1] (🗳️) ballot box with ballot -1F5FA ; Emoji # 7.0 [1] (🗺️) world map -1F5FB..1F5FF ; Emoji # 6.0 [5] (🗻..🗿) mount fuji..moai -1F600 ; Emoji # 6.1 [1] (😀) grinning face -1F601..1F610 ; Emoji # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face -1F611 ; Emoji # 6.1 [1] (😑) expressionless face -1F612..1F614 ; Emoji # 6.0 [3] (😒..😔) unamused face..pensive face -1F615 ; Emoji # 6.1 [1] (😕) confused face -1F616 ; Emoji # 6.0 [1] (😖) confounded face -1F617 ; Emoji # 6.1 [1] (😗) kissing face -1F618 ; Emoji # 6.0 [1] (😘) face blowing a kiss -1F619 ; Emoji # 6.1 [1] (😙) kissing face with smiling eyes -1F61A ; Emoji # 6.0 [1] (😚) kissing face with closed eyes -1F61B ; Emoji # 6.1 [1] (😛) face with tongue -1F61C..1F61E ; Emoji # 6.0 [3] (😜..😞) winking face with tongue..disappointed face -1F61F ; Emoji # 6.1 [1] (😟) worried face -1F620..1F625 ; Emoji # 6.0 [6] (😠..😥) angry face..sad but relieved face -1F626..1F627 ; Emoji # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face -1F628..1F62B ; Emoji # 6.0 [4] (😨..😫) fearful face..tired face -1F62C ; Emoji # 6.1 [1] (😬) grimacing face -1F62D ; Emoji # 6.0 [1] (😭) loudly crying face -1F62E..1F62F ; Emoji # 6.1 [2] (😮..😯) face with open mouth..hushed face -1F630..1F633 ; Emoji # 6.0 [4] (😰..😳) anxious face with sweat..flushed face -1F634 ; Emoji # 6.1 [1] (😴) sleeping face -1F635..1F640 ; Emoji # 6.0 [12] (😵..🙀) dizzy face..weary cat -1F641..1F642 ; Emoji # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face -1F643..1F644 ; Emoji # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes -1F645..1F64F ; Emoji # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands -1F680..1F6C5 ; Emoji # 6.0 [70] (🚀..🛅) rocket..left luggage -1F6CB..1F6CF ; Emoji # 7.0 [5] (🛋️..🛏️) couch and lamp..bed -1F6D0 ; Emoji # 8.0 [1] (🛐) place of worship -1F6D1..1F6D2 ; Emoji # 9.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D5 ; Emoji # 12.0 [1] (🛕) hindu temple -1F6E0..1F6E5 ; Emoji # 7.0 [6] (🛠️..🛥️) hammer and wrench..motor boat -1F6E9 ; Emoji # 7.0 [1] (🛩️) small airplane -1F6EB..1F6EC ; Emoji # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival -1F6F0 ; Emoji # 7.0 [1] (🛰️) satellite -1F6F3 ; Emoji # 7.0 [1] (🛳️) passenger ship -1F6F4..1F6F6 ; Emoji # 9.0 [3] (🛴..🛶) kick scooter..canoe -1F6F7..1F6F8 ; Emoji # 10.0 [2] (🛷..🛸) sled..flying saucer -1F6F9 ; Emoji # 11.0 [1] (🛹) skateboard -1F6FA ; Emoji # 12.0 [1] (🛺) auto rickshaw -1F7E0..1F7EB ; Emoji # 12.0 [12] (🟠..🟫) orange circle..brown square -1F90D..1F90F ; Emoji # 12.0 [3] (🤍..🤏) white heart..pinching hand -1F910..1F918 ; Emoji # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns -1F919..1F91E ; Emoji # 9.0 [6] (🤙..🤞) call me hand..crossed fingers -1F91F ; Emoji # 10.0 [1] (🤟) love-you gesture -1F920..1F927 ; Emoji # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face -1F928..1F92F ; Emoji # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head -1F930 ; Emoji # 9.0 [1] (🤰) pregnant woman -1F931..1F932 ; Emoji # 10.0 [2] (🤱..🤲) breast-feeding..palms up together -1F933..1F93A ; Emoji # 9.0 [8] (🤳..🤺) selfie..person fencing -1F93C..1F93E ; Emoji # 9.0 [3] (🤼..🤾) people wrestling..person playing handball -1F93F ; Emoji # 12.0 [1] (🤿) diving mask -1F940..1F945 ; Emoji # 9.0 [6] (🥀..🥅) wilted flower..goal net -1F947..1F94B ; Emoji # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform -1F94C ; Emoji # 10.0 [1] (🥌) curling stone -1F94D..1F94F ; Emoji # 11.0 [3] (🥍..🥏) lacrosse..flying disc -1F950..1F95E ; Emoji # 9.0 [15] (🥐..🥞) croissant..pancakes -1F95F..1F96B ; Emoji # 10.0 [13] (🥟..🥫) dumpling..canned food -1F96C..1F970 ; Emoji # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts -1F971 ; Emoji # 12.0 [1] (🥱) yawning face -1F973..1F976 ; Emoji # 11.0 [4] (🥳..🥶) partying face..cold face -1F97A ; Emoji # 11.0 [1] (🥺) pleading face -1F97B ; Emoji # 12.0 [1] (🥻) sari -1F97C..1F97F ; Emoji # 11.0 [4] (🥼..🥿) lab coat..flat shoe -1F980..1F984 ; Emoji # 8.0 [5] (🦀..🦄) crab..unicorn -1F985..1F991 ; Emoji # 9.0 [13] (🦅..🦑) eagle..squid -1F992..1F997 ; Emoji # 10.0 [6] (🦒..🦗) giraffe..cricket -1F998..1F9A2 ; Emoji # 11.0 [11] (🦘..🦢) kangaroo..swan -1F9A5..1F9AA ; Emoji # 12.0 [6] (🦥..🦪) sloth..oyster -1F9AE..1F9AF ; Emoji # 12.0 [2] (🦮..🦯) guide dog..probing cane -1F9B0..1F9B9 ; Emoji # 11.0 [10] (🦰..🦹) red hair..supervillain -1F9BA..1F9BF ; Emoji # 12.0 [6] (🦺..🦿) safety vest..mechanical leg -1F9C0 ; Emoji # 8.0 [1] (🧀) cheese wedge -1F9C1..1F9C2 ; Emoji # 11.0 [2] (🧁..🧂) cupcake..salt -1F9C3..1F9CA ; Emoji # 12.0 [8] (🧃..🧊) beverage box..ice cube -1F9CD..1F9CF ; Emoji # 12.0 [3] (🧍..🧏) person standing..deaf person -1F9D0..1F9E6 ; Emoji # 10.0 [23] (🧐..🧦) face with monocle..socks -1F9E7..1F9FF ; Emoji # 11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA70..1FA73 ; Emoji # 12.0 [4] (🩰..🩳) ballet shoes..shorts -1FA78..1FA7A ; Emoji # 12.0 [3] (🩸..🩺) drop of blood..stethoscope -1FA80..1FA82 ; Emoji # 12.0 [3] (🪀..🪂) yo-yo..parachute -1FA90..1FA95 ; Emoji # 12.0 [6] (🪐..🪕) ringed planet..banjo - -# Total elements: 1311 - -# ================================================ - -# All omitted code points have Emoji_Presentation=No -# @missing: 0000..10FFFF ; Emoji_Presentation ; No - -231A..231B ; Emoji_Presentation # 1.1 [2] (⌚..⌛) watch..hourglass done -23E9..23EC ; Emoji_Presentation # 6.0 [4] (⏩..⏬) fast-forward button..fast down button -23F0 ; Emoji_Presentation # 6.0 [1] (⏰) alarm clock -23F3 ; Emoji_Presentation # 6.0 [1] (⏳) hourglass not done -25FD..25FE ; Emoji_Presentation # 3.2 [2] (◽..◾) white medium-small square..black medium-small square -2614..2615 ; Emoji_Presentation # 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage -2648..2653 ; Emoji_Presentation # 1.1 [12] (♈..♓) Aries..Pisces -267F ; Emoji_Presentation # 4.1 [1] (♿) wheelchair symbol -2693 ; Emoji_Presentation # 4.1 [1] (⚓) anchor -26A1 ; Emoji_Presentation # 4.0 [1] (⚡) high voltage -26AA..26AB ; Emoji_Presentation # 4.1 [2] (⚪..⚫) white circle..black circle -26BD..26BE ; Emoji_Presentation # 5.2 [2] (⚽..⚾) soccer ball..baseball -26C4..26C5 ; Emoji_Presentation # 5.2 [2] (⛄..⛅) snowman without snow..sun behind cloud -26CE ; Emoji_Presentation # 6.0 [1] (⛎) Ophiuchus -26D4 ; Emoji_Presentation # 5.2 [1] (⛔) no entry -26EA ; Emoji_Presentation # 5.2 [1] (⛪) church -26F2..26F3 ; Emoji_Presentation # 5.2 [2] (⛲..⛳) fountain..flag in hole -26F5 ; Emoji_Presentation # 5.2 [1] (⛵) sailboat -26FA ; Emoji_Presentation # 5.2 [1] (⛺) tent -26FD ; Emoji_Presentation # 5.2 [1] (⛽) fuel pump -2705 ; Emoji_Presentation # 6.0 [1] (✅) check mark button -270A..270B ; Emoji_Presentation # 6.0 [2] (✊..✋) raised fist..raised hand -2728 ; Emoji_Presentation # 6.0 [1] (✨) sparkles -274C ; Emoji_Presentation # 6.0 [1] (❌) cross mark -274E ; Emoji_Presentation # 6.0 [1] (❎) cross mark button -2753..2755 ; Emoji_Presentation # 6.0 [3] (❓..❕) question mark..white exclamation mark -2757 ; Emoji_Presentation # 5.2 [1] (❗) exclamation mark -2795..2797 ; Emoji_Presentation # 6.0 [3] (➕..➗) plus sign..division sign -27B0 ; Emoji_Presentation # 6.0 [1] (➰) curly loop -27BF ; Emoji_Presentation # 6.0 [1] (➿) double curly loop -2B1B..2B1C ; Emoji_Presentation # 5.1 [2] (⬛..⬜) black large square..white large square -2B50 ; Emoji_Presentation # 5.1 [1] (⭐) star -2B55 ; Emoji_Presentation # 5.2 [1] (⭕) hollow red circle -1F004 ; Emoji_Presentation # 5.1 [1] (🀄) mahjong red dragon -1F0CF ; Emoji_Presentation # 6.0 [1] (🃏) joker -1F18E ; Emoji_Presentation # 6.0 [1] (🆎) AB button (blood type) -1F191..1F19A ; Emoji_Presentation # 6.0 [10] (🆑..🆚) CL button..VS button -1F1E6..1F1FF ; Emoji_Presentation # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z -1F201 ; Emoji_Presentation # 6.0 [1] (🈁) Japanese “here” button -1F21A ; Emoji_Presentation # 5.2 [1] (🈚) Japanese “free of charge” button -1F22F ; Emoji_Presentation # 5.2 [1] (🈯) Japanese “reserved” button -1F232..1F236 ; Emoji_Presentation # 6.0 [5] (🈲..🈶) Japanese “prohibited” button..Japanese “not free of charge” button -1F238..1F23A ; Emoji_Presentation # 6.0 [3] (🈸..🈺) Japanese “application” button..Japanese “open for business” button -1F250..1F251 ; Emoji_Presentation # 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F300..1F320 ; Emoji_Presentation # 6.0 [33] (🌀..🌠) cyclone..shooting star -1F32D..1F32F ; Emoji_Presentation # 8.0 [3] (🌭..🌯) hot dog..burrito -1F330..1F335 ; Emoji_Presentation # 6.0 [6] (🌰..🌵) chestnut..cactus -1F337..1F37C ; Emoji_Presentation # 6.0 [70] (🌷..🍼) tulip..baby bottle -1F37E..1F37F ; Emoji_Presentation # 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn -1F380..1F393 ; Emoji_Presentation # 6.0 [20] (🎀..🎓) ribbon..graduation cap -1F3A0..1F3C4 ; Emoji_Presentation # 6.0 [37] (🎠..🏄) carousel horse..person surfing -1F3C5 ; Emoji_Presentation # 7.0 [1] (🏅) sports medal -1F3C6..1F3CA ; Emoji_Presentation # 6.0 [5] (🏆..🏊) trophy..person swimming -1F3CF..1F3D3 ; Emoji_Presentation # 8.0 [5] (🏏..🏓) cricket game..ping pong -1F3E0..1F3F0 ; Emoji_Presentation # 6.0 [17] (🏠..🏰) house..castle -1F3F4 ; Emoji_Presentation # 7.0 [1] (🏴) black flag -1F3F8..1F3FF ; Emoji_Presentation # 8.0 [8] (🏸..🏿) badminton..dark skin tone -1F400..1F43E ; Emoji_Presentation # 6.0 [63] (🐀..🐾) rat..paw prints -1F440 ; Emoji_Presentation # 6.0 [1] (👀) eyes -1F442..1F4F7 ; Emoji_Presentation # 6.0[182] (👂..📷) ear..camera -1F4F8 ; Emoji_Presentation # 7.0 [1] (📸) camera with flash -1F4F9..1F4FC ; Emoji_Presentation # 6.0 [4] (📹..📼) video camera..videocassette -1F4FF ; Emoji_Presentation # 8.0 [1] (📿) prayer beads -1F500..1F53D ; Emoji_Presentation # 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button -1F54B..1F54E ; Emoji_Presentation # 8.0 [4] (🕋..🕎) kaaba..menorah -1F550..1F567 ; Emoji_Presentation # 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty -1F57A ; Emoji_Presentation # 9.0 [1] (🕺) man dancing -1F595..1F596 ; Emoji_Presentation # 7.0 [2] (🖕..🖖) middle finger..vulcan salute -1F5A4 ; Emoji_Presentation # 9.0 [1] (🖤) black heart -1F5FB..1F5FF ; Emoji_Presentation # 6.0 [5] (🗻..🗿) mount fuji..moai -1F600 ; Emoji_Presentation # 6.1 [1] (😀) grinning face -1F601..1F610 ; Emoji_Presentation # 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face -1F611 ; Emoji_Presentation # 6.1 [1] (😑) expressionless face -1F612..1F614 ; Emoji_Presentation # 6.0 [3] (😒..😔) unamused face..pensive face -1F615 ; Emoji_Presentation # 6.1 [1] (😕) confused face -1F616 ; Emoji_Presentation # 6.0 [1] (😖) confounded face -1F617 ; Emoji_Presentation # 6.1 [1] (😗) kissing face -1F618 ; Emoji_Presentation # 6.0 [1] (😘) face blowing a kiss -1F619 ; Emoji_Presentation # 6.1 [1] (😙) kissing face with smiling eyes -1F61A ; Emoji_Presentation # 6.0 [1] (😚) kissing face with closed eyes -1F61B ; Emoji_Presentation # 6.1 [1] (😛) face with tongue -1F61C..1F61E ; Emoji_Presentation # 6.0 [3] (😜..😞) winking face with tongue..disappointed face -1F61F ; Emoji_Presentation # 6.1 [1] (😟) worried face -1F620..1F625 ; Emoji_Presentation # 6.0 [6] (😠..😥) angry face..sad but relieved face -1F626..1F627 ; Emoji_Presentation # 6.1 [2] (😦..😧) frowning face with open mouth..anguished face -1F628..1F62B ; Emoji_Presentation # 6.0 [4] (😨..😫) fearful face..tired face -1F62C ; Emoji_Presentation # 6.1 [1] (😬) grimacing face -1F62D ; Emoji_Presentation # 6.0 [1] (😭) loudly crying face -1F62E..1F62F ; Emoji_Presentation # 6.1 [2] (😮..😯) face with open mouth..hushed face -1F630..1F633 ; Emoji_Presentation # 6.0 [4] (😰..😳) anxious face with sweat..flushed face -1F634 ; Emoji_Presentation # 6.1 [1] (😴) sleeping face -1F635..1F640 ; Emoji_Presentation # 6.0 [12] (😵..🙀) dizzy face..weary cat -1F641..1F642 ; Emoji_Presentation # 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face -1F643..1F644 ; Emoji_Presentation # 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes -1F645..1F64F ; Emoji_Presentation # 6.0 [11] (🙅..🙏) person gesturing NO..folded hands -1F680..1F6C5 ; Emoji_Presentation # 6.0 [70] (🚀..🛅) rocket..left luggage -1F6CC ; Emoji_Presentation # 7.0 [1] (🛌) person in bed -1F6D0 ; Emoji_Presentation # 8.0 [1] (🛐) place of worship -1F6D1..1F6D2 ; Emoji_Presentation # 9.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D5 ; Emoji_Presentation # 12.0 [1] (🛕) hindu temple -1F6EB..1F6EC ; Emoji_Presentation # 7.0 [2] (🛫..🛬) airplane departure..airplane arrival -1F6F4..1F6F6 ; Emoji_Presentation # 9.0 [3] (🛴..🛶) kick scooter..canoe -1F6F7..1F6F8 ; Emoji_Presentation # 10.0 [2] (🛷..🛸) sled..flying saucer -1F6F9 ; Emoji_Presentation # 11.0 [1] (🛹) skateboard -1F6FA ; Emoji_Presentation # 12.0 [1] (🛺) auto rickshaw -1F7E0..1F7EB ; Emoji_Presentation # 12.0 [12] (🟠..🟫) orange circle..brown square -1F90D..1F90F ; Emoji_Presentation # 12.0 [3] (🤍..🤏) white heart..pinching hand -1F910..1F918 ; Emoji_Presentation # 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns -1F919..1F91E ; Emoji_Presentation # 9.0 [6] (🤙..🤞) call me hand..crossed fingers -1F91F ; Emoji_Presentation # 10.0 [1] (🤟) love-you gesture -1F920..1F927 ; Emoji_Presentation # 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face -1F928..1F92F ; Emoji_Presentation # 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head -1F930 ; Emoji_Presentation # 9.0 [1] (🤰) pregnant woman -1F931..1F932 ; Emoji_Presentation # 10.0 [2] (🤱..🤲) breast-feeding..palms up together -1F933..1F93A ; Emoji_Presentation # 9.0 [8] (🤳..🤺) selfie..person fencing -1F93C..1F93E ; Emoji_Presentation # 9.0 [3] (🤼..🤾) people wrestling..person playing handball -1F93F ; Emoji_Presentation # 12.0 [1] (🤿) diving mask -1F940..1F945 ; Emoji_Presentation # 9.0 [6] (🥀..🥅) wilted flower..goal net -1F947..1F94B ; Emoji_Presentation # 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform -1F94C ; Emoji_Presentation # 10.0 [1] (🥌) curling stone -1F94D..1F94F ; Emoji_Presentation # 11.0 [3] (🥍..🥏) lacrosse..flying disc -1F950..1F95E ; Emoji_Presentation # 9.0 [15] (🥐..🥞) croissant..pancakes -1F95F..1F96B ; Emoji_Presentation # 10.0 [13] (🥟..🥫) dumpling..canned food -1F96C..1F970 ; Emoji_Presentation # 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts -1F971 ; Emoji_Presentation # 12.0 [1] (🥱) yawning face -1F973..1F976 ; Emoji_Presentation # 11.0 [4] (🥳..🥶) partying face..cold face -1F97A ; Emoji_Presentation # 11.0 [1] (🥺) pleading face -1F97B ; Emoji_Presentation # 12.0 [1] (🥻) sari -1F97C..1F97F ; Emoji_Presentation # 11.0 [4] (🥼..🥿) lab coat..flat shoe -1F980..1F984 ; Emoji_Presentation # 8.0 [5] (🦀..🦄) crab..unicorn -1F985..1F991 ; Emoji_Presentation # 9.0 [13] (🦅..🦑) eagle..squid -1F992..1F997 ; Emoji_Presentation # 10.0 [6] (🦒..🦗) giraffe..cricket -1F998..1F9A2 ; Emoji_Presentation # 11.0 [11] (🦘..🦢) kangaroo..swan -1F9A5..1F9AA ; Emoji_Presentation # 12.0 [6] (🦥..🦪) sloth..oyster -1F9AE..1F9AF ; Emoji_Presentation # 12.0 [2] (🦮..🦯) guide dog..probing cane -1F9B0..1F9B9 ; Emoji_Presentation # 11.0 [10] (🦰..🦹) red hair..supervillain -1F9BA..1F9BF ; Emoji_Presentation # 12.0 [6] (🦺..🦿) safety vest..mechanical leg -1F9C0 ; Emoji_Presentation # 8.0 [1] (🧀) cheese wedge -1F9C1..1F9C2 ; Emoji_Presentation # 11.0 [2] (🧁..🧂) cupcake..salt -1F9C3..1F9CA ; Emoji_Presentation # 12.0 [8] (🧃..🧊) beverage box..ice cube -1F9CD..1F9CF ; Emoji_Presentation # 12.0 [3] (🧍..🧏) person standing..deaf person -1F9D0..1F9E6 ; Emoji_Presentation # 10.0 [23] (🧐..🧦) face with monocle..socks -1F9E7..1F9FF ; Emoji_Presentation # 11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA70..1FA73 ; Emoji_Presentation # 12.0 [4] (🩰..🩳) ballet shoes..shorts -1FA78..1FA7A ; Emoji_Presentation # 12.0 [3] (🩸..🩺) drop of blood..stethoscope -1FA80..1FA82 ; Emoji_Presentation # 12.0 [3] (🪀..🪂) yo-yo..parachute -1FA90..1FA95 ; Emoji_Presentation # 12.0 [6] (🪐..🪕) ringed planet..banjo - -# Total elements: 1093 - -# ================================================ - -# All omitted code points have Emoji_Modifier=No -# @missing: 0000..10FFFF ; Emoji_Modifier ; No - -1F3FB..1F3FF ; Emoji_Modifier # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone - -# Total elements: 5 - -# ================================================ - -# All omitted code points have Emoji_Modifier_Base=No -# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No - -261D ; Emoji_Modifier_Base # 1.1 [1] (☝️) index pointing up -26F9 ; Emoji_Modifier_Base # 5.2 [1] (⛹️) person bouncing ball -270A..270B ; Emoji_Modifier_Base # 6.0 [2] (✊..✋) raised fist..raised hand -270C..270D ; Emoji_Modifier_Base # 1.1 [2] (✌️..✍️) victory hand..writing hand -1F385 ; Emoji_Modifier_Base # 6.0 [1] (🎅) Santa Claus -1F3C2..1F3C4 ; Emoji_Modifier_Base # 6.0 [3] (🏂..🏄) snowboarder..person surfing -1F3C7 ; Emoji_Modifier_Base # 6.0 [1] (🏇) horse racing -1F3CA ; Emoji_Modifier_Base # 6.0 [1] (🏊) person swimming -1F3CB..1F3CC ; Emoji_Modifier_Base # 7.0 [2] (🏋️..🏌️) person lifting weights..person golfing -1F442..1F443 ; Emoji_Modifier_Base # 6.0 [2] (👂..👃) ear..nose -1F446..1F450 ; Emoji_Modifier_Base # 6.0 [11] (👆..👐) backhand index pointing up..open hands -1F466..1F478 ; Emoji_Modifier_Base # 6.0 [19] (👦..👸) boy..princess -1F47C ; Emoji_Modifier_Base # 6.0 [1] (👼) baby angel -1F481..1F483 ; Emoji_Modifier_Base # 6.0 [3] (💁..💃) person tipping hand..woman dancing -1F485..1F487 ; Emoji_Modifier_Base # 6.0 [3] (💅..💇) nail polish..person getting haircut -1F48F ; Emoji_Modifier_Base # 6.0 [1] (💏) kiss -1F491 ; Emoji_Modifier_Base # 6.0 [1] (💑) couple with heart -1F4AA ; Emoji_Modifier_Base # 6.0 [1] (💪) flexed biceps -1F574..1F575 ; Emoji_Modifier_Base # 7.0 [2] (🕴️..🕵️) man in suit levitating..detective -1F57A ; Emoji_Modifier_Base # 9.0 [1] (🕺) man dancing -1F590 ; Emoji_Modifier_Base # 7.0 [1] (🖐️) hand with fingers splayed -1F595..1F596 ; Emoji_Modifier_Base # 7.0 [2] (🖕..🖖) middle finger..vulcan salute -1F645..1F647 ; Emoji_Modifier_Base # 6.0 [3] (🙅..🙇) person gesturing NO..person bowing -1F64B..1F64F ; Emoji_Modifier_Base # 6.0 [5] (🙋..🙏) person raising hand..folded hands -1F6A3 ; Emoji_Modifier_Base # 6.0 [1] (🚣) person rowing boat -1F6B4..1F6B6 ; Emoji_Modifier_Base # 6.0 [3] (🚴..🚶) person biking..person walking -1F6C0 ; Emoji_Modifier_Base # 6.0 [1] (🛀) person taking bath -1F6CC ; Emoji_Modifier_Base # 7.0 [1] (🛌) person in bed -1F90F ; Emoji_Modifier_Base # 12.0 [1] (🤏) pinching hand -1F918 ; Emoji_Modifier_Base # 8.0 [1] (🤘) sign of the horns -1F919..1F91E ; Emoji_Modifier_Base # 9.0 [6] (🤙..🤞) call me hand..crossed fingers -1F91F ; Emoji_Modifier_Base # 10.0 [1] (🤟) love-you gesture -1F926 ; Emoji_Modifier_Base # 9.0 [1] (🤦) person facepalming -1F930 ; Emoji_Modifier_Base # 9.0 [1] (🤰) pregnant woman -1F931..1F932 ; Emoji_Modifier_Base # 10.0 [2] (🤱..🤲) breast-feeding..palms up together -1F933..1F939 ; Emoji_Modifier_Base # 9.0 [7] (🤳..🤹) selfie..person juggling -1F93C..1F93E ; Emoji_Modifier_Base # 9.0 [3] (🤼..🤾) people wrestling..person playing handball -1F9B5..1F9B6 ; Emoji_Modifier_Base # 11.0 [2] (🦵..🦶) leg..foot -1F9B8..1F9B9 ; Emoji_Modifier_Base # 11.0 [2] (🦸..🦹) superhero..supervillain -1F9BB ; Emoji_Modifier_Base # 12.0 [1] (🦻) ear with hearing aid -1F9CD..1F9CF ; Emoji_Modifier_Base # 12.0 [3] (🧍..🧏) person standing..deaf person -1F9D1..1F9DD ; Emoji_Modifier_Base # 10.0 [13] (🧑..🧝) person..elf - -# Total elements: 120 - -# ================================================ - -# All omitted code points have Emoji_Component=No -# @missing: 0000..10FFFF ; Emoji_Component ; No - -0023 ; Emoji_Component # 1.1 [1] (#️) number sign -002A ; Emoji_Component # 1.1 [1] (*️) asterisk -0030..0039 ; Emoji_Component # 1.1 [10] (0️..9️) digit zero..digit nine -200D ; Emoji_Component # 1.1 [1] (‍) zero width joiner -20E3 ; Emoji_Component # 3.0 [1] (⃣) combining enclosing keycap -FE0F ; Emoji_Component # 3.2 [1] () VARIATION SELECTOR-16 -1F1E6..1F1FF ; Emoji_Component # 6.0 [26] (🇦..🇿) regional indicator symbol letter a..regional indicator symbol letter z -1F3FB..1F3FF ; Emoji_Component # 8.0 [5] (🏻..🏿) light skin tone..dark skin tone -1F9B0..1F9B3 ; Emoji_Component # 11.0 [4] (🦰..🦳) red hair..white hair -E0020..E007F ; Emoji_Component # 3.1 [96] (󠀠..󠁿) tag space..cancel tag - -# Total elements: 146 - -# ================================================ - -# All omitted code points have Extended_Pictographic=No -# @missing: 0000..10FFFF ; Extended_Pictographic ; No - -00A9 ; Extended_Pictographic# 1.1 [1] (©️) copyright -00AE ; Extended_Pictographic# 1.1 [1] (®️) registered -203C ; Extended_Pictographic# 1.1 [1] (‼️) double exclamation mark -2049 ; Extended_Pictographic# 3.0 [1] (⁉️) exclamation question mark -2122 ; Extended_Pictographic# 1.1 [1] (™️) trade mark -2139 ; Extended_Pictographic# 3.0 [1] (ℹ️) information -2194..2199 ; Extended_Pictographic# 1.1 [6] (↔️..↙️) left-right arrow..down-left arrow -21A9..21AA ; Extended_Pictographic# 1.1 [2] (↩️..↪️) right arrow curving left..left arrow curving right -231A..231B ; Extended_Pictographic# 1.1 [2] (⌚..⌛) watch..hourglass done -2328 ; Extended_Pictographic# 1.1 [1] (⌨️) keyboard -2388 ; Extended_Pictographic# 3.0 [1] (⎈) HELM SYMBOL -23CF ; Extended_Pictographic# 4.0 [1] (⏏️) eject button -23E9..23F3 ; Extended_Pictographic# 6.0 [11] (⏩..⏳) fast-forward button..hourglass not done -23F8..23FA ; Extended_Pictographic# 7.0 [3] (⏸️..⏺️) pause button..record button -24C2 ; Extended_Pictographic# 1.1 [1] (Ⓜ️) circled M -25AA..25AB ; Extended_Pictographic# 1.1 [2] (▪️..▫️) black small square..white small square -25B6 ; Extended_Pictographic# 1.1 [1] (▶️) play button -25C0 ; Extended_Pictographic# 1.1 [1] (◀️) reverse button -25FB..25FE ; Extended_Pictographic# 3.2 [4] (◻️..◾) white medium square..black medium-small square -2600..2605 ; Extended_Pictographic# 1.1 [6] (☀️..★) sun..BLACK STAR -2607..2612 ; Extended_Pictographic# 1.1 [12] (☇..☒) LIGHTNING..BALLOT BOX WITH X -2614..2615 ; Extended_Pictographic# 4.0 [2] (☔..☕) umbrella with rain drops..hot beverage -2616..2617 ; Extended_Pictographic# 3.2 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE -2618 ; Extended_Pictographic# 4.1 [1] (☘️) shamrock -2619 ; Extended_Pictographic# 3.0 [1] (☙) REVERSED ROTATED FLORAL HEART BULLET -261A..266F ; Extended_Pictographic# 1.1 [86] (☚..♯) BLACK LEFT POINTING INDEX..MUSIC SHARP SIGN -2670..2671 ; Extended_Pictographic# 3.0 [2] (♰..♱) WEST SYRIAC CROSS..EAST SYRIAC CROSS -2672..267D ; Extended_Pictographic# 3.2 [12] (♲..♽) UNIVERSAL RECYCLING SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL -267E..267F ; Extended_Pictographic# 4.1 [2] (♾️..♿) infinity..wheelchair symbol -2680..2685 ; Extended_Pictographic# 3.2 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 -2690..2691 ; Extended_Pictographic# 4.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG -2692..269C ; Extended_Pictographic# 4.1 [11] (⚒️..⚜️) hammer and pick..fleur-de-lis -269D ; Extended_Pictographic# 5.1 [1] (⚝) OUTLINED WHITE STAR -269E..269F ; Extended_Pictographic# 5.2 [2] (⚞..⚟) THREE LINES CONVERGING RIGHT..THREE LINES CONVERGING LEFT -26A0..26A1 ; Extended_Pictographic# 4.0 [2] (⚠️..⚡) warning..high voltage -26A2..26B1 ; Extended_Pictographic# 4.1 [16] (⚢..⚱️) DOUBLED FEMALE SIGN..funeral urn -26B2 ; Extended_Pictographic# 5.0 [1] (⚲) NEUTER -26B3..26BC ; Extended_Pictographic# 5.1 [10] (⚳..⚼) CERES..SESQUIQUADRATE -26BD..26BF ; Extended_Pictographic# 5.2 [3] (⚽..⚿) soccer ball..SQUARED KEY -26C0..26C3 ; Extended_Pictographic# 5.1 [4] (⛀..⛃) WHITE DRAUGHTS MAN..BLACK DRAUGHTS KING -26C4..26CD ; Extended_Pictographic# 5.2 [10] (⛄..⛍) snowman without snow..DISABLED CAR -26CE ; Extended_Pictographic# 6.0 [1] (⛎) Ophiuchus -26CF..26E1 ; Extended_Pictographic# 5.2 [19] (⛏️..⛡) pick..RESTRICTED LEFT ENTRY-2 -26E2 ; Extended_Pictographic# 6.0 [1] (⛢) ASTRONOMICAL SYMBOL FOR URANUS -26E3 ; Extended_Pictographic# 5.2 [1] (⛣) HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE -26E4..26E7 ; Extended_Pictographic# 6.0 [4] (⛤..⛧) PENTAGRAM..INVERTED PENTAGRAM -26E8..26FF ; Extended_Pictographic# 5.2 [24] (⛨..⛿) BLACK CROSS ON SHIELD..WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE -2700 ; Extended_Pictographic# 7.0 [1] (✀) BLACK SAFETY SCISSORS -2701..2704 ; Extended_Pictographic# 1.1 [4] (✁..✄) UPPER BLADE SCISSORS..WHITE SCISSORS -2705 ; Extended_Pictographic# 6.0 [1] (✅) check mark button -2708..2709 ; Extended_Pictographic# 1.1 [2] (✈️..✉️) airplane..envelope -270A..270B ; Extended_Pictographic# 6.0 [2] (✊..✋) raised fist..raised hand -270C..2712 ; Extended_Pictographic# 1.1 [7] (✌️..✒️) victory hand..black nib -2714 ; Extended_Pictographic# 1.1 [1] (✔️) check mark -2716 ; Extended_Pictographic# 1.1 [1] (✖️) multiplication sign -271D ; Extended_Pictographic# 1.1 [1] (✝️) latin cross -2721 ; Extended_Pictographic# 1.1 [1] (✡️) star of David -2728 ; Extended_Pictographic# 6.0 [1] (✨) sparkles -2733..2734 ; Extended_Pictographic# 1.1 [2] (✳️..✴️) eight-spoked asterisk..eight-pointed star -2744 ; Extended_Pictographic# 1.1 [1] (❄️) snowflake -2747 ; Extended_Pictographic# 1.1 [1] (❇️) sparkle -274C ; Extended_Pictographic# 6.0 [1] (❌) cross mark -274E ; Extended_Pictographic# 6.0 [1] (❎) cross mark button -2753..2755 ; Extended_Pictographic# 6.0 [3] (❓..❕) question mark..white exclamation mark -2757 ; Extended_Pictographic# 5.2 [1] (❗) exclamation mark -2763..2767 ; Extended_Pictographic# 1.1 [5] (❣️..❧) heart exclamation..ROTATED FLORAL HEART BULLET -2795..2797 ; Extended_Pictographic# 6.0 [3] (➕..➗) plus sign..division sign -27A1 ; Extended_Pictographic# 1.1 [1] (➡️) right arrow -27B0 ; Extended_Pictographic# 6.0 [1] (➰) curly loop -27BF ; Extended_Pictographic# 6.0 [1] (➿) double curly loop -2934..2935 ; Extended_Pictographic# 3.2 [2] (⤴️..⤵️) right arrow curving up..right arrow curving down -2B05..2B07 ; Extended_Pictographic# 4.0 [3] (⬅️..⬇️) left arrow..down arrow -2B1B..2B1C ; Extended_Pictographic# 5.1 [2] (⬛..⬜) black large square..white large square -2B50 ; Extended_Pictographic# 5.1 [1] (⭐) star -2B55 ; Extended_Pictographic# 5.2 [1] (⭕) hollow red circle -3030 ; Extended_Pictographic# 1.1 [1] (〰️) wavy dash -303D ; Extended_Pictographic# 3.2 [1] (〽️) part alternation mark -3297 ; Extended_Pictographic# 1.1 [1] (㊗️) Japanese “congratulations” button -3299 ; Extended_Pictographic# 1.1 [1] (㊙️) Japanese “secret” button -1F000..1F02B ; Extended_Pictographic# 5.1 [44] (🀀..🀫) MAHJONG TILE EAST WIND..MAHJONG TILE BACK -1F02C..1F02F ; Extended_Pictographic# NA [4] (🀬..🀯) .. -1F030..1F093 ; Extended_Pictographic# 5.1[100] (🀰..🂓) DOMINO TILE HORIZONTAL BACK..DOMINO TILE VERTICAL-06-06 -1F094..1F09F ; Extended_Pictographic# NA [12] (🂔..🂟) .. -1F0A0..1F0AE ; Extended_Pictographic# 6.0 [15] (🂠..🂮) PLAYING CARD BACK..PLAYING CARD KING OF SPADES -1F0AF..1F0B0 ; Extended_Pictographic# NA [2] (🂯..🂰) .. -1F0B1..1F0BE ; Extended_Pictographic# 6.0 [14] (🂱..🂾) PLAYING CARD ACE OF HEARTS..PLAYING CARD KING OF HEARTS -1F0BF ; Extended_Pictographic# 7.0 [1] (🂿) PLAYING CARD RED JOKER -1F0C0 ; Extended_Pictographic# NA [1] (🃀) -1F0C1..1F0CF ; Extended_Pictographic# 6.0 [15] (🃁..🃏) PLAYING CARD ACE OF DIAMONDS..joker -1F0D0 ; Extended_Pictographic# NA [1] (🃐) -1F0D1..1F0DF ; Extended_Pictographic# 6.0 [15] (🃑..🃟) PLAYING CARD ACE OF CLUBS..PLAYING CARD WHITE JOKER -1F0E0..1F0F5 ; Extended_Pictographic# 7.0 [22] (🃠..🃵) PLAYING CARD FOOL..PLAYING CARD TRUMP-21 -1F0F6..1F0FF ; Extended_Pictographic# NA [10] (🃶..🃿) .. -1F10D..1F10F ; Extended_Pictographic# NA [3] (🄍..🄏) .. -1F12F ; Extended_Pictographic# 11.0 [1] (🄯) COPYLEFT SYMBOL -1F16C ; Extended_Pictographic# 12.0 [1] (🅬) RAISED MR SIGN -1F16D..1F16F ; Extended_Pictographic# NA [3] (🅭..🅯) .. -1F170..1F171 ; Extended_Pictographic# 6.0 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) -1F17E ; Extended_Pictographic# 6.0 [1] (🅾️) O button (blood type) -1F17F ; Extended_Pictographic# 5.2 [1] (🅿️) P button -1F18E ; Extended_Pictographic# 6.0 [1] (🆎) AB button (blood type) -1F191..1F19A ; Extended_Pictographic# 6.0 [10] (🆑..🆚) CL button..VS button -1F1AD..1F1E5 ; Extended_Pictographic# NA [57] (🆭..🇥) .. -1F201..1F202 ; Extended_Pictographic# 6.0 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button -1F203..1F20F ; Extended_Pictographic# NA [13] (🈃..🈏) .. -1F21A ; Extended_Pictographic# 5.2 [1] (🈚) Japanese “free of charge” button -1F22F ; Extended_Pictographic# 5.2 [1] (🈯) Japanese “reserved” button -1F232..1F23A ; Extended_Pictographic# 6.0 [9] (🈲..🈺) Japanese “prohibited” button..Japanese “open for business” button -1F23C..1F23F ; Extended_Pictographic# NA [4] (🈼..🈿) .. -1F249..1F24F ; Extended_Pictographic# NA [7] (🉉..🉏) .. -1F250..1F251 ; Extended_Pictographic# 6.0 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F252..1F25F ; Extended_Pictographic# NA [14] (🉒..🉟) .. -1F260..1F265 ; Extended_Pictographic# 10.0 [6] (🉠..🉥) ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI -1F266..1F2FF ; Extended_Pictographic# NA[154] (🉦..🋿) .. -1F300..1F320 ; Extended_Pictographic# 6.0 [33] (🌀..🌠) cyclone..shooting star -1F321..1F32C ; Extended_Pictographic# 7.0 [12] (🌡️..🌬️) thermometer..wind face -1F32D..1F32F ; Extended_Pictographic# 8.0 [3] (🌭..🌯) hot dog..burrito -1F330..1F335 ; Extended_Pictographic# 6.0 [6] (🌰..🌵) chestnut..cactus -1F336 ; Extended_Pictographic# 7.0 [1] (🌶️) hot pepper -1F337..1F37C ; Extended_Pictographic# 6.0 [70] (🌷..🍼) tulip..baby bottle -1F37D ; Extended_Pictographic# 7.0 [1] (🍽️) fork and knife with plate -1F37E..1F37F ; Extended_Pictographic# 8.0 [2] (🍾..🍿) bottle with popping cork..popcorn -1F380..1F393 ; Extended_Pictographic# 6.0 [20] (🎀..🎓) ribbon..graduation cap -1F394..1F39F ; Extended_Pictographic# 7.0 [12] (🎔..🎟️) HEART WITH TIP ON THE LEFT..admission tickets -1F3A0..1F3C4 ; Extended_Pictographic# 6.0 [37] (🎠..🏄) carousel horse..person surfing -1F3C5 ; Extended_Pictographic# 7.0 [1] (🏅) sports medal -1F3C6..1F3CA ; Extended_Pictographic# 6.0 [5] (🏆..🏊) trophy..person swimming -1F3CB..1F3CE ; Extended_Pictographic# 7.0 [4] (🏋️..🏎️) person lifting weights..racing car -1F3CF..1F3D3 ; Extended_Pictographic# 8.0 [5] (🏏..🏓) cricket game..ping pong -1F3D4..1F3DF ; Extended_Pictographic# 7.0 [12] (🏔️..🏟️) snow-capped mountain..stadium -1F3E0..1F3F0 ; Extended_Pictographic# 6.0 [17] (🏠..🏰) house..castle -1F3F1..1F3F7 ; Extended_Pictographic# 7.0 [7] (🏱..🏷️) WHITE PENNANT..label -1F3F8..1F3FA ; Extended_Pictographic# 8.0 [3] (🏸..🏺) badminton..amphora -1F400..1F43E ; Extended_Pictographic# 6.0 [63] (🐀..🐾) rat..paw prints -1F43F ; Extended_Pictographic# 7.0 [1] (🐿️) chipmunk -1F440 ; Extended_Pictographic# 6.0 [1] (👀) eyes -1F441 ; Extended_Pictographic# 7.0 [1] (👁️) eye -1F442..1F4F7 ; Extended_Pictographic# 6.0[182] (👂..📷) ear..camera -1F4F8 ; Extended_Pictographic# 7.0 [1] (📸) camera with flash -1F4F9..1F4FC ; Extended_Pictographic# 6.0 [4] (📹..📼) video camera..videocassette -1F4FD..1F4FE ; Extended_Pictographic# 7.0 [2] (📽️..📾) film projector..PORTABLE STEREO -1F4FF ; Extended_Pictographic# 8.0 [1] (📿) prayer beads -1F500..1F53D ; Extended_Pictographic# 6.0 [62] (🔀..🔽) shuffle tracks button..downwards button -1F546..1F54A ; Extended_Pictographic# 7.0 [5] (🕆..🕊️) WHITE LATIN CROSS..dove -1F54B..1F54F ; Extended_Pictographic# 8.0 [5] (🕋..🕏) kaaba..BOWL OF HYGIEIA -1F550..1F567 ; Extended_Pictographic# 6.0 [24] (🕐..🕧) one o’clock..twelve-thirty -1F568..1F579 ; Extended_Pictographic# 7.0 [18] (🕨..🕹️) RIGHT SPEAKER..joystick -1F57A ; Extended_Pictographic# 9.0 [1] (🕺) man dancing -1F57B..1F5A3 ; Extended_Pictographic# 7.0 [41] (🕻..🖣) LEFT HAND TELEPHONE RECEIVER..BLACK DOWN POINTING BACKHAND INDEX -1F5A4 ; Extended_Pictographic# 9.0 [1] (🖤) black heart -1F5A5..1F5FA ; Extended_Pictographic# 7.0 [86] (🖥️..🗺️) desktop computer..world map -1F5FB..1F5FF ; Extended_Pictographic# 6.0 [5] (🗻..🗿) mount fuji..moai -1F600 ; Extended_Pictographic# 6.1 [1] (😀) grinning face -1F601..1F610 ; Extended_Pictographic# 6.0 [16] (😁..😐) beaming face with smiling eyes..neutral face -1F611 ; Extended_Pictographic# 6.1 [1] (😑) expressionless face -1F612..1F614 ; Extended_Pictographic# 6.0 [3] (😒..😔) unamused face..pensive face -1F615 ; Extended_Pictographic# 6.1 [1] (😕) confused face -1F616 ; Extended_Pictographic# 6.0 [1] (😖) confounded face -1F617 ; Extended_Pictographic# 6.1 [1] (😗) kissing face -1F618 ; Extended_Pictographic# 6.0 [1] (😘) face blowing a kiss -1F619 ; Extended_Pictographic# 6.1 [1] (😙) kissing face with smiling eyes -1F61A ; Extended_Pictographic# 6.0 [1] (😚) kissing face with closed eyes -1F61B ; Extended_Pictographic# 6.1 [1] (😛) face with tongue -1F61C..1F61E ; Extended_Pictographic# 6.0 [3] (😜..😞) winking face with tongue..disappointed face -1F61F ; Extended_Pictographic# 6.1 [1] (😟) worried face -1F620..1F625 ; Extended_Pictographic# 6.0 [6] (😠..😥) angry face..sad but relieved face -1F626..1F627 ; Extended_Pictographic# 6.1 [2] (😦..😧) frowning face with open mouth..anguished face -1F628..1F62B ; Extended_Pictographic# 6.0 [4] (😨..😫) fearful face..tired face -1F62C ; Extended_Pictographic# 6.1 [1] (😬) grimacing face -1F62D ; Extended_Pictographic# 6.0 [1] (😭) loudly crying face -1F62E..1F62F ; Extended_Pictographic# 6.1 [2] (😮..😯) face with open mouth..hushed face -1F630..1F633 ; Extended_Pictographic# 6.0 [4] (😰..😳) anxious face with sweat..flushed face -1F634 ; Extended_Pictographic# 6.1 [1] (😴) sleeping face -1F635..1F640 ; Extended_Pictographic# 6.0 [12] (😵..🙀) dizzy face..weary cat -1F641..1F642 ; Extended_Pictographic# 7.0 [2] (🙁..🙂) slightly frowning face..slightly smiling face -1F643..1F644 ; Extended_Pictographic# 8.0 [2] (🙃..🙄) upside-down face..face with rolling eyes -1F645..1F64F ; Extended_Pictographic# 6.0 [11] (🙅..🙏) person gesturing NO..folded hands -1F680..1F6C5 ; Extended_Pictographic# 6.0 [70] (🚀..🛅) rocket..left luggage -1F6C6..1F6CF ; Extended_Pictographic# 7.0 [10] (🛆..🛏️) TRIANGLE WITH ROUNDED CORNERS..bed -1F6D0 ; Extended_Pictographic# 8.0 [1] (🛐) place of worship -1F6D1..1F6D2 ; Extended_Pictographic# 9.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D3..1F6D4 ; Extended_Pictographic# 10.0 [2] (🛓..🛔) STUPA..PAGODA -1F6D5 ; Extended_Pictographic# 12.0 [1] (🛕) hindu temple -1F6D6..1F6DF ; Extended_Pictographic# NA [10] (🛖..🛟) .. -1F6E0..1F6EC ; Extended_Pictographic# 7.0 [13] (🛠️..🛬) hammer and wrench..airplane arrival -1F6ED..1F6EF ; Extended_Pictographic# NA [3] (🛭..🛯) .. -1F6F0..1F6F3 ; Extended_Pictographic# 7.0 [4] (🛰️..🛳️) satellite..passenger ship -1F6F4..1F6F6 ; Extended_Pictographic# 9.0 [3] (🛴..🛶) kick scooter..canoe -1F6F7..1F6F8 ; Extended_Pictographic# 10.0 [2] (🛷..🛸) sled..flying saucer -1F6F9 ; Extended_Pictographic# 11.0 [1] (🛹) skateboard -1F6FA ; Extended_Pictographic# 12.0 [1] (🛺) auto rickshaw -1F6FB..1F6FF ; Extended_Pictographic# NA [5] (🛻..🛿) .. -1F774..1F77F ; Extended_Pictographic# NA [12] (🝴..🝿) .. -1F7D5..1F7D8 ; Extended_Pictographic# 11.0 [4] (🟕..🟘) CIRCLED TRIANGLE..NEGATIVE CIRCLED SQUARE -1F7D9..1F7DF ; Extended_Pictographic# NA [7] (🟙..🟟) .. -1F7E0..1F7EB ; Extended_Pictographic# 12.0 [12] (🟠..🟫) orange circle..brown square -1F7EC..1F7FF ; Extended_Pictographic# NA [20] (🟬..🟿) .. -1F80C..1F80F ; Extended_Pictographic# NA [4] (🠌..🠏) .. -1F848..1F84F ; Extended_Pictographic# NA [8] (🡈..🡏) .. -1F85A..1F85F ; Extended_Pictographic# NA [6] (🡚..🡟) .. -1F888..1F88F ; Extended_Pictographic# NA [8] (🢈..🢏) .. -1F8AE..1F8FF ; Extended_Pictographic# NA [82] (🢮..🣿) .. -1F90C ; Extended_Pictographic# NA [1] (🤌) -1F90D..1F90F ; Extended_Pictographic# 12.0 [3] (🤍..🤏) white heart..pinching hand -1F910..1F918 ; Extended_Pictographic# 8.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns -1F919..1F91E ; Extended_Pictographic# 9.0 [6] (🤙..🤞) call me hand..crossed fingers -1F91F ; Extended_Pictographic# 10.0 [1] (🤟) love-you gesture -1F920..1F927 ; Extended_Pictographic# 9.0 [8] (🤠..🤧) cowboy hat face..sneezing face -1F928..1F92F ; Extended_Pictographic# 10.0 [8] (🤨..🤯) face with raised eyebrow..exploding head -1F930 ; Extended_Pictographic# 9.0 [1] (🤰) pregnant woman -1F931..1F932 ; Extended_Pictographic# 10.0 [2] (🤱..🤲) breast-feeding..palms up together -1F933..1F93A ; Extended_Pictographic# 9.0 [8] (🤳..🤺) selfie..person fencing -1F93C..1F93E ; Extended_Pictographic# 9.0 [3] (🤼..🤾) people wrestling..person playing handball -1F93F ; Extended_Pictographic# 12.0 [1] (🤿) diving mask -1F940..1F945 ; Extended_Pictographic# 9.0 [6] (🥀..🥅) wilted flower..goal net -1F947..1F94B ; Extended_Pictographic# 9.0 [5] (🥇..🥋) 1st place medal..martial arts uniform -1F94C ; Extended_Pictographic# 10.0 [1] (🥌) curling stone -1F94D..1F94F ; Extended_Pictographic# 11.0 [3] (🥍..🥏) lacrosse..flying disc -1F950..1F95E ; Extended_Pictographic# 9.0 [15] (🥐..🥞) croissant..pancakes -1F95F..1F96B ; Extended_Pictographic# 10.0 [13] (🥟..🥫) dumpling..canned food -1F96C..1F970 ; Extended_Pictographic# 11.0 [5] (🥬..🥰) leafy green..smiling face with hearts -1F971 ; Extended_Pictographic# 12.0 [1] (🥱) yawning face -1F972 ; Extended_Pictographic# NA [1] (🥲) -1F973..1F976 ; Extended_Pictographic# 11.0 [4] (🥳..🥶) partying face..cold face -1F977..1F979 ; Extended_Pictographic# NA [3] (🥷..🥹) .. -1F97A ; Extended_Pictographic# 11.0 [1] (🥺) pleading face -1F97B ; Extended_Pictographic# 12.0 [1] (🥻) sari -1F97C..1F97F ; Extended_Pictographic# 11.0 [4] (🥼..🥿) lab coat..flat shoe -1F980..1F984 ; Extended_Pictographic# 8.0 [5] (🦀..🦄) crab..unicorn -1F985..1F991 ; Extended_Pictographic# 9.0 [13] (🦅..🦑) eagle..squid -1F992..1F997 ; Extended_Pictographic# 10.0 [6] (🦒..🦗) giraffe..cricket -1F998..1F9A2 ; Extended_Pictographic# 11.0 [11] (🦘..🦢) kangaroo..swan -1F9A3..1F9A4 ; Extended_Pictographic# NA [2] (🦣..🦤) .. -1F9A5..1F9AA ; Extended_Pictographic# 12.0 [6] (🦥..🦪) sloth..oyster -1F9AB..1F9AD ; Extended_Pictographic# NA [3] (🦫..🦭) .. -1F9AE..1F9AF ; Extended_Pictographic# 12.0 [2] (🦮..🦯) guide dog..probing cane -1F9B0..1F9B9 ; Extended_Pictographic# 11.0 [10] (🦰..🦹) red hair..supervillain -1F9BA..1F9BF ; Extended_Pictographic# 12.0 [6] (🦺..🦿) safety vest..mechanical leg -1F9C0 ; Extended_Pictographic# 8.0 [1] (🧀) cheese wedge -1F9C1..1F9C2 ; Extended_Pictographic# 11.0 [2] (🧁..🧂) cupcake..salt -1F9C3..1F9CA ; Extended_Pictographic# 12.0 [8] (🧃..🧊) beverage box..ice cube -1F9CB..1F9CC ; Extended_Pictographic# NA [2] (🧋..🧌) .. -1F9CD..1F9CF ; Extended_Pictographic# 12.0 [3] (🧍..🧏) person standing..deaf person -1F9D0..1F9E6 ; Extended_Pictographic# 10.0 [23] (🧐..🧦) face with monocle..socks -1F9E7..1F9FF ; Extended_Pictographic# 11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA00..1FA53 ; Extended_Pictographic# 12.0 [84] (🨀..🩓) NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP -1FA54..1FA5F ; Extended_Pictographic# NA [12] (🩔..🩟) .. -1FA60..1FA6D ; Extended_Pictographic# 11.0 [14] (🩠..🩭) XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER -1FA6E..1FA6F ; Extended_Pictographic# NA [2] (🩮..🩯) .. -1FA70..1FA73 ; Extended_Pictographic# 12.0 [4] (🩰..🩳) ballet shoes..shorts -1FA74..1FA77 ; Extended_Pictographic# NA [4] (🩴..🩷) .. -1FA78..1FA7A ; Extended_Pictographic# 12.0 [3] (🩸..🩺) drop of blood..stethoscope -1FA7B..1FA7F ; Extended_Pictographic# NA [5] (🩻..🩿) .. -1FA80..1FA82 ; Extended_Pictographic# 12.0 [3] (🪀..🪂) yo-yo..parachute -1FA83..1FA8F ; Extended_Pictographic# NA [13] (🪃..🪏) .. -1FA90..1FA95 ; Extended_Pictographic# 12.0 [6] (🪐..🪕) ringed planet..banjo -1FA96..1FFFD ; Extended_Pictographic# NA[1384] (🪖..🿽) .. - -# Total elements: 3793 - -#EOF -- cgit v1.2.3 From c9afb350e7a38aa915418c6b98cedd863ca0405b Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 2 Dec 2020 19:16:36 +0400 Subject: Document follow relationship updates and cleanup --- lib/pleroma/following_relationship.ex | 2 +- lib/pleroma/web/streamer.ex | 21 +++++++++------------ lib/pleroma/web/views/streamer_view.ex | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index bc6a7eaf9..5390a58e1 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -96,7 +96,7 @@ defmodule Pleroma.FollowingRelationship do defp after_update(state, %User{} = follower, %User{} = following) do with {:ok, following} <- User.update_follower_count(following), {:ok, follower} <- User.update_following_count(follower) do - Pleroma.Web.Streamer.stream("relationships:update", %{ + Pleroma.Web.Streamer.stream("follow_relationship", %{ state: state, following: following, follower: follower diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 0b6cc89e9..7d4a1304a 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -186,18 +186,15 @@ defmodule Pleroma.Web.Streamer do end) end - defp do_stream("relationships:update", item) do - text = StreamerView.render("relationships_update.json", item) - - [item.follower, item.following] - |> Enum.map(fn %{id: id} -> "user:#{id}" end) - |> Enum.each(fn user_topic -> - Logger.debug("Trying to push relationships:update to #{user_topic}\n\n") - - Registry.dispatch(@registry, user_topic, fn list -> - Enum.each(list, fn {pid, _auth} -> - send(pid, {:text, text}) - end) + defp do_stream("follow_relationship", item) do + text = StreamerView.render("follow_relationships_update.json", item) + user_topic = "user:#{item.follower.id}" + + Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n") + + Registry.dispatch(@registry, user_topic, fn list -> + Enum.each(list, fn {pid, _auth} -> + send(pid, {:text, text}) end) end) end diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 92239a411..4fc14166d 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -74,9 +74,9 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("relationships_update.json", item) do + def render("follow_relationships_update.json", item) do %{ - event: "pleroma:relationships_update", + event: "pleroma:follow_relationships_update", payload: %{ state: item.state, -- cgit v1.2.3 From 8fb259e7395d4dd2bdac407b7eca0840ce490a99 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Dec 2020 16:46:19 +0100 Subject: Emoji: Only accept RGI emoji. "recommended for general interchange" --- lib/pleroma/emoji.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 98644f84e..201212779 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -108,7 +108,10 @@ defmodule Pleroma.Emoji do @external_resource |> File.read!() |> String.split("\n") - |> Enum.filter(fn line -> line != "" and not String.starts_with?(line, "#") end) + |> 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) -- cgit v1.2.3 From 6dcc36baa9b19d18785d6f7ab8ceb7dd941c6180 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 25 Nov 2020 12:44:06 -0600 Subject: Add mix tasks to give additional recovery and debugging options - pleroma.config dump: prints the entire config as it would be exported to the filesystem - pleroma.config dump KEY: prints the configuration under a specific ConfigDB key in the database - pleroma.config keylist: lists the available keys in ConfigDB - pleroma.config keydel KEY: deletes ConfigDB entry stored under the key This should prevent the need for users to manually execute SQL queries. --- lib/mix/tasks/pleroma/config.ex | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 18f99318d..b49854528 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -30,6 +30,83 @@ defmodule Mix.Tasks.Pleroma.Config do migrate_from_db(opts) end + def run(["dump"]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + header = config_header() + + shell_info("#{header}") + + ConfigDB + |> Repo.all() + |> Enum.each(&dump(&1)) + else + _ -> configdb_not_enabled() + end + end + + def run(["dump" | dbkey]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + dbkey = dbkey |> List.first() |> String.to_atom() + + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.key == dbkey do + x |> dump + end + end) + else + _ -> configdb_not_enabled() + end + end + + def run(["keylist"]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + keys = + ConfigDB + |> Repo.all() + |> Enum.map(fn x -> x.key end) + + if length(keys) > 0 do + shell_info("The following configuration keys are set in ConfigDB:\r\n") + keys |> Enum.each(fn x -> shell_info("- #{x}") end) + shell_info("\r\n") + end + else + _ -> configdb_not_enabled() + end + end + + def run(["keydel" | dbkey]) do + unless [] == dbkey do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + dbkey = dbkey |> List.first() |> String.to_atom() + + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.key == dbkey do + x |> delete(true) + end + end) + else + _ -> configdb_not_enabled() + end + else + shell_error( + "You must provide a key to delete. Use the keylist command to get a list of valid keys." + ) + end + end + @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do with true <- Pleroma.Config.get([:configurable_from_database]), @@ -154,4 +231,16 @@ defmodule Mix.Tasks.Pleroma.Config do end defp delete(_config, _), do: :ok + + defp dump(%Pleroma.ConfigDB{} = config) do + value = inspect(config.value, limit: :infinity) + + shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n") + end + + defp configdb_not_enabled do + shell_error( + "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration." + ) + end end -- cgit v1.2.3 From a82ba66662fdcdccf0de384b0f57dd20bef0fd9d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 25 Nov 2020 17:16:23 -0600 Subject: Better deletion message --- lib/mix/tasks/pleroma/config.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index b49854528..675dda0d0 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -227,7 +227,7 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete(config, true) do {:ok, _} = Repo.delete(config) - shell_info("#{config.key} deleted from DB.") + shell_info("#{config.key} deleted from the ConfigDB.") end defp delete(_config, _), do: :ok -- cgit v1.2.3 From e8a4062d9dc042253adc05f2ab964bbd468ace12 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 25 Nov 2020 17:31:44 -0600 Subject: Document how to delete individual configuration groups and completely reset the config without SQL --- lib/mix/tasks/pleroma/config.ex | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 675dda0d0..574f8f4be 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -83,6 +83,19 @@ defmodule Mix.Tasks.Pleroma.Config do end end + def run(["reset"]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") + Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + + shell_info("The ConfigDB settings have been removed from the database.") + else + _ -> configdb_not_enabled() + end + end + def run(["keydel" | dbkey]) do unless [] == dbkey do with true <- Pleroma.Config.get([:configurable_from_database]) do -- cgit v1.2.3 From ada073f2511ae57eb22dc9e8a4220b2382b9f97c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 25 Nov 2020 17:49:36 -0600 Subject: Rename keys to groups --- lib/mix/tasks/pleroma/config.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 574f8f4be..3c94f1f5f 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -64,7 +64,7 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["keylist"]) do + def run(["groups"]) do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() @@ -96,7 +96,7 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["keydel" | dbkey]) do + def run(["groupdel" | dbkey]) do unless [] == dbkey do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() @@ -115,7 +115,7 @@ defmodule Mix.Tasks.Pleroma.Config do end else shell_error( - "You must provide a key to delete. Use the keylist command to get a list of valid keys." + "You must provide a group to delete. Use the groups command to get a list of valid configDB groups." ) end end -- cgit v1.2.3 From a51da3c1d8355de0747605608fc929f5fa345b3f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 12:32:53 -0600 Subject: Sort output by group Not the best sorting, but better than nothing. --- lib/mix/tasks/pleroma/config.ex | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 3c94f1f5f..76753e13c 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -40,6 +40,7 @@ defmodule Mix.Tasks.Pleroma.Config do ConfigDB |> Repo.all() + |> Enum.sort() |> Enum.each(&dump(&1)) else _ -> configdb_not_enabled() -- cgit v1.2.3 From 67437feafc048e56d023370266fe3762405f3199 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 12:33:55 -0600 Subject: Support listing groups, listing keys in a group, and dumping the config based on group or specific key in that group --- lib/mix/tasks/pleroma/config.ex | 75 ++++++++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 76753e13c..5c01b21f8 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -47,35 +47,61 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["dump" | dbkey]) do + def run(["dump" | args]) when is_list(args) and length(args) < 3 do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - dbkey = dbkey |> List.first() |> String.to_atom() - - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.key == dbkey do - x |> dump - end - end) + if length(args) > 1 do + [group, key] = args + dump_key(group, key) + else + [group] = args + dump_group(group) + end else _ -> configdb_not_enabled() end end def run(["groups"]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + groups = + ConfigDB + |> Repo.all() + |> Enum.map(fn x -> x.group end) + |> Enum.sort() + |> Enum.uniq() + + if length(groups) > 0 do + shell_info("The following configuration groups are set in ConfigDB:\r\n") + groups |> Enum.each(fn x -> shell_info("- #{x}") end) + shell_info("\r\n") + end + else + _ -> configdb_not_enabled() + end + end + + def run(["keys" | group]) do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() keys = ConfigDB |> Repo.all() - |> Enum.map(fn x -> x.key end) + |> Enum.map(fn x -> + if x.group == group do + x.key + end + end) + |> Enum.sort() + |> Enum.uniq() + |> Enum.reject(fn x -> x == nil end) if length(keys) > 0 do - shell_info("The following configuration keys are set in ConfigDB:\r\n") + shell_info("The following configuration keys under :#{group} are set in ConfigDB:\r\n") keys |> Enum.each(fn x -> shell_info("- #{x}") end) shell_info("\r\n") end @@ -257,4 +283,29 @@ defmodule Mix.Tasks.Pleroma.Config do "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration." ) end + + defp dump_key(group, key) do + group = group |> String.to_atom() + key = key |> String.to_atom() + + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group && x.key == key do + x |> dump + end + end) + end + + defp dump_group(group) do + group = group |> String.to_atom() + + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group do + x |> dump + end + end) + end end -- cgit v1.2.3 From c6a0ca2213be0eac1233ae28c11e563109771c85 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 13:55:43 -0600 Subject: Improve dumping groups and specific keys; add prompts for delete and reset --- lib/mix/tasks/pleroma/config.ex | 48 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 5c01b21f8..a794344cb 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -47,17 +47,21 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["dump" | args]) when is_list(args) and length(args) < 3 do + def run(["dump", group, key]) do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - if length(args) > 1 do - [group, key] = args - dump_key(group, key) - else - [group] = args - dump_group(group) - end + dump_key(group, key) + else + _ -> configdb_not_enabled() + end + end + + def run(["dump", group]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + dump_group(group) else _ -> configdb_not_enabled() end @@ -114,36 +118,38 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") - Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") + Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") - shell_info("The ConfigDB settings have been removed from the database.") + shell_info("The ConfigDB settings have been removed from the database.") + else + shell_info("No changes made.") + end else _ -> configdb_not_enabled() end end - def run(["groupdel" | dbkey]) do - unless [] == dbkey do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + def run(["delete" | args]) when is_list(args) and length(args) == 2 do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() - dbkey = dbkey |> List.first() |> String.to_atom() + [group, key] = args + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> - if x.key == dbkey do + if x.group == group and x.key == key do x |> delete(true) end end) else - _ -> configdb_not_enabled() + shell_info("No changes made.") end else - shell_error( - "You must provide a group to delete. Use the groups command to get a list of valid configDB groups." - ) + _ -> configdb_not_enabled() end end -- cgit v1.2.3 From ae7d37de0665021373d9bc4d01d648c7d812eaed Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 14:02:45 -0600 Subject: Fix deletion regression due to strings instead of atoms Improve message after successful deletion --- lib/mix/tasks/pleroma/config.ex | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index a794344cb..e5536d16a 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -131,11 +131,34 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["delete" | args]) when is_list(args) and length(args) == 2 do + def run(["delete", group]) do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - [group, key] = args + group = group |> String.to_atom() + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group do + x |> delete(true) + end + end) + else + shell_info("No changes made.") + end + else + _ -> configdb_not_enabled() + end + end + + def run(["delete", group, key]) do + with true <- Pleroma.Config.get([:configurable_from_database]) do + start_pleroma() + + group = group |> String.to_atom() + key = key |> String.to_atom() if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do ConfigDB @@ -273,7 +296,7 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete(config, true) do {:ok, _} = Repo.delete(config) - shell_info("#{config.key} deleted from the ConfigDB.") + shell_info(":#{config.group}, :#{config.key} deleted from the ConfigDB.") end defp delete(_config, _), do: :ok -- cgit v1.2.3 From 3df115b2b0ee9f5ca6f2507550d18002379eeaa8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 14:44:05 -0600 Subject: Support atoms and strings as args to the mix task Improve output. Show the user what will be deleted before the prompt. --- lib/mix/tasks/pleroma/config.ex | 95 +++++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 28 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index e5536d16a..078a4110b 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -36,12 +36,18 @@ defmodule Mix.Tasks.Pleroma.Config do header = config_header() - shell_info("#{header}") + settings = + ConfigDB + |> Repo.all() + |> Enum.sort() - ConfigDB - |> Repo.all() - |> Enum.sort() - |> Enum.each(&dump(&1)) + unless settings == [] do + shell_info("#{header}") + + settings |> Enum.each(&dump(&1)) + else + shell_error("No settings in ConfigDB.") + end else _ -> configdb_not_enabled() end @@ -51,6 +57,9 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() + group = atomize(group) + key = atomize(key) + dump_key(group, key) else _ -> configdb_not_enabled() @@ -61,6 +70,8 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() + group = atomize(group) + dump_group(group) else _ -> configdb_not_enabled() @@ -88,10 +99,12 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["keys" | group]) do + def run(["keys", group]) do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() + group = atomize(group) + keys = ConfigDB |> Repo.all() @@ -124,7 +137,7 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("The ConfigDB settings have been removed from the database.") else - shell_info("No changes made.") + shell_error("No changes made.") end else _ -> configdb_not_enabled() @@ -135,18 +148,26 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = group |> String.to_atom() + group = atomize(group) - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group do - x |> delete(true) - end - end) + if group_exists?(group) do + shell_info("The following settings will be removed from ConfigDB:\n") + + dump_group(group) + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group do + x |> delete(true) + end + end) + else + shell_error("No changes made.") + end else - shell_info("No changes made.") + shell_error("No settings in ConfigDB for :#{group}. Aborting.") end else _ -> configdb_not_enabled() @@ -157,8 +178,8 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = group |> String.to_atom() - key = key |> String.to_atom() + group = atomize(group) + key = atomize(key) if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do ConfigDB @@ -169,7 +190,7 @@ defmodule Mix.Tasks.Pleroma.Config do end end) else - shell_info("No changes made.") + shell_error("No changes made.") end else _ -> configdb_not_enabled() @@ -296,7 +317,10 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete(config, true) do {:ok, _} = Repo.delete(config) - shell_info(":#{config.group}, :#{config.key} deleted from the ConfigDB.") + + shell_info( + "config #{inspect(config.group)}, #{inspect(config.key)} deleted from the ConfigDB." + ) end defp delete(_config, _), do: :ok @@ -313,10 +337,7 @@ defmodule Mix.Tasks.Pleroma.Config do ) end - defp dump_key(group, key) do - group = group |> String.to_atom() - key = key |> String.to_atom() - + defp dump_key(group, key) when is_atom(group) and is_atom(key) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> @@ -326,9 +347,7 @@ defmodule Mix.Tasks.Pleroma.Config do end) end - defp dump_group(group) do - group = group |> String.to_atom() - + defp dump_group(group) when is_atom(group) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> @@ -337,4 +356,24 @@ defmodule Mix.Tasks.Pleroma.Config do end end) end + + defp group_exists?(group) when is_atom(group) do + result = + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group do + x + end + end) + + unless result == [] do + true + else + false + end + end + + defp atomize(x) when is_atom(x), do: x + defp atomize(x) when is_binary(x), do: String.to_atom(x) end -- cgit v1.2.3 From 4bdfcf1682f1429e72102bf9f54ddee9e7ede0bc Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 16:20:28 -0600 Subject: Transform strings to atoms for all cases, including when the atom is a module like Pleroma.Emails.Mailer --- lib/mix/tasks/pleroma/config.ex | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 078a4110b..7ab15e60b 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -57,8 +57,8 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = atomize(group) - key = atomize(key) + group = maybe_atomize(group) + key = maybe_atomize(key) dump_key(group, key) else @@ -70,7 +70,7 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = atomize(group) + group = maybe_atomize(group) dump_group(group) else @@ -103,7 +103,7 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = atomize(group) + group = maybe_atomize(group) keys = ConfigDB @@ -148,7 +148,7 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = atomize(group) + group = maybe_atomize(group) if group_exists?(group) do shell_info("The following settings will be removed from ConfigDB:\n") @@ -178,8 +178,8 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() - group = atomize(group) - key = atomize(key) + group = maybe_atomize(group) + key = maybe_atomize(key) if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do ConfigDB @@ -337,7 +337,7 @@ defmodule Mix.Tasks.Pleroma.Config do ) end - defp dump_key(group, key) when is_atom(group) and is_atom(key) do + defp dump_key(group, key) when is_atom(group) and is_atom(key) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> @@ -374,6 +374,15 @@ defmodule Mix.Tasks.Pleroma.Config do end end - defp atomize(x) when is_atom(x), do: x - defp atomize(x) when is_binary(x), do: String.to_atom(x) + def maybe_atomize(arg) when is_atom(arg), do: arg + + def maybe_atomize(arg) when is_binary(arg) do + chars = String.codepoints(arg) + + if "." in chars do + :"Elixir.#{arg}" + else + String.to_atom(arg) + end + end end -- cgit v1.2.3 From d4320e0daf7c732ba2c791cae697dea27c4919d2 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 16:32:32 -0600 Subject: Both are really atoms --- lib/mix/tasks/pleroma/config.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 7ab15e60b..a7c307f77 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -337,7 +337,7 @@ defmodule Mix.Tasks.Pleroma.Config do ) end - defp dump_key(group, key) when is_atom(group) and is_atom(key) do + defp dump_key(group, key) when is_atom(group) and is_atom(key) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> -- cgit v1.2.3 From 0847e3e496624a97c7eb933cf69a92fd84677ce0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 16:32:46 -0600 Subject: Print whole config when resetting and include a scary looking message. --- lib/mix/tasks/pleroma/config.ex | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index a7c307f77..0c8170c9c 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -131,6 +131,15 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() + shell_info("The following settings will be permanently removed:") + + ConfigDB + |> Repo.all() + |> Enum.sort() + |> Enum.each(&dump(&1)) + + shell_error("THIS CANNOT BE UNDONE!") + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") -- cgit v1.2.3 From d0cb73527f1bc21aa6bb6d21bfcdf58c406c5b0c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 28 Nov 2020 12:05:01 -0600 Subject: Ensure scary warning starts on a new line --- lib/mix/tasks/pleroma/config.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 0c8170c9c..fe0cd81f8 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -138,7 +138,7 @@ defmodule Mix.Tasks.Pleroma.Config do |> Enum.sort() |> Enum.each(&dump(&1)) - shell_error("THIS CANNOT BE UNDONE!") + shell_error("\nTHIS CANNOT BE UNDONE!") if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") -- cgit v1.2.3 From 6334ba9ad3d275e9e50b30319e2dbed3aac35fac Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 28 Nov 2020 12:22:30 -0600 Subject: Remove unnecessary keys command --- lib/mix/tasks/pleroma/config.ex | 28 ---------------------------- 1 file changed, 28 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index fe0cd81f8..f657adf46 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -99,34 +99,6 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def run(["keys", group]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() - - group = maybe_atomize(group) - - keys = - ConfigDB - |> Repo.all() - |> Enum.map(fn x -> - if x.group == group do - x.key - end - end) - |> Enum.sort() - |> Enum.uniq() - |> Enum.reject(fn x -> x == nil end) - - if length(keys) > 0 do - shell_info("The following configuration keys under :#{group} are set in ConfigDB:\r\n") - keys |> Enum.each(fn x -> shell_info("- #{x}") end) - shell_info("\r\n") - end - else - _ -> configdb_not_enabled() - end - end - def run(["reset"]) do with true <- Pleroma.Config.get([:configurable_from_database]) do start_pleroma() -- cgit v1.2.3 From 5135a8189f9e297354a1d9f61f3cb7454711923c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sat, 28 Nov 2020 12:24:37 -0600 Subject: Use inspect instead of faking the output --- lib/mix/tasks/pleroma/config.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index f657adf46..3e1449550 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -148,7 +148,7 @@ defmodule Mix.Tasks.Pleroma.Config do shell_error("No changes made.") end else - shell_error("No settings in ConfigDB for :#{group}. Aborting.") + shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") end else _ -> configdb_not_enabled() @@ -228,7 +228,7 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("Settings for key #{key} migrated.") end) - shell_info("Settings for group :#{group} migrated.") + shell_info("Settings for group #{inspect(group)} migrated.") end defp migrate_from_db(opts) do -- cgit v1.2.3 From 53a5ec195239b399c2bc072f754346eba3b3b6b2 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 29 Nov 2020 12:59:03 -0600 Subject: Left public during debugging --- lib/mix/tasks/pleroma/config.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 3e1449550..a781f3bf1 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -355,9 +355,9 @@ defmodule Mix.Tasks.Pleroma.Config do end end - def maybe_atomize(arg) when is_atom(arg), do: arg + defp maybe_atomize(arg) when is_atom(arg), do: arg - def maybe_atomize(arg) when is_binary(arg) do + defp maybe_atomize(arg) when is_binary(arg) do chars = String.codepoints(arg) if "." in chars do -- cgit v1.2.3 From a7b5280b5b620e3548bbd387752a04c918418f61 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Sun, 29 Nov 2020 13:29:36 -0600 Subject: Centralize check that configdb is enabled which now raises an exception --- lib/mix/tasks/pleroma/config.ex | 235 ++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 129 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index a781f3bf1..df4ee55c1 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -14,11 +14,13 @@ defmodule Mix.Tasks.Pleroma.Config do @moduledoc File.read!("docs/administration/CLI_tasks/config.md") def run(["migrate_to_db"]) do + check_configdb() start_pleroma() migrate_to_db() end def run(["migrate_from_db" | options]) do + check_configdb() start_pleroma() {opts, _} = @@ -31,142 +33,101 @@ defmodule Mix.Tasks.Pleroma.Config do end def run(["dump"]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + check_configdb() + start_pleroma() - header = config_header() + header = config_header() - settings = - ConfigDB - |> Repo.all() - |> Enum.sort() + settings = + ConfigDB + |> Repo.all() + |> Enum.sort() - unless settings == [] do - shell_info("#{header}") + unless settings == [] do + shell_info("#{header}") - settings |> Enum.each(&dump(&1)) - else - shell_error("No settings in ConfigDB.") - end + settings |> Enum.each(&dump(&1)) else - _ -> configdb_not_enabled() + shell_error("No settings in ConfigDB.") end end def run(["dump", group, key]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + check_configdb() + start_pleroma() - group = maybe_atomize(group) - key = maybe_atomize(key) + group = maybe_atomize(group) + key = maybe_atomize(key) - dump_key(group, key) - else - _ -> configdb_not_enabled() - end + dump_key(group, key) end def run(["dump", group]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + check_configdb() + start_pleroma() - group = maybe_atomize(group) + group = maybe_atomize(group) - dump_group(group) - else - _ -> configdb_not_enabled() - end + dump_group(group) end def run(["groups"]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + check_configdb() + start_pleroma() - groups = - ConfigDB - |> Repo.all() - |> Enum.map(fn x -> x.group end) - |> Enum.sort() - |> Enum.uniq() - - if length(groups) > 0 do - shell_info("The following configuration groups are set in ConfigDB:\r\n") - groups |> Enum.each(fn x -> shell_info("- #{x}") end) - shell_info("\r\n") - end - else - _ -> configdb_not_enabled() + groups = + ConfigDB + |> Repo.all() + |> Enum.map(fn x -> x.group end) + |> Enum.sort() + |> Enum.uniq() + + if length(groups) > 0 do + shell_info("The following configuration groups are set in ConfigDB:\r\n") + groups |> Enum.each(fn x -> shell_info("- #{x}") end) + shell_info("\r\n") end end def run(["reset"]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + check_configdb() + start_pleroma() - shell_info("The following settings will be permanently removed:") + shell_info("The following settings will be permanently removed:") - ConfigDB - |> Repo.all() - |> Enum.sort() - |> Enum.each(&dump(&1)) + ConfigDB + |> Repo.all() + |> Enum.sort() + |> Enum.each(&dump(&1)) - shell_error("\nTHIS CANNOT BE UNDONE!") + shell_error("\nTHIS CANNOT BE UNDONE!") - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") - Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") + Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") - shell_info("The ConfigDB settings have been removed from the database.") - else - shell_error("No changes made.") - end + shell_info("The ConfigDB settings have been removed from the database.") else - _ -> configdb_not_enabled() + shell_error("No changes made.") end end def run(["delete", group]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() - - group = maybe_atomize(group) - - if group_exists?(group) do - shell_info("The following settings will be removed from ConfigDB:\n") - - dump_group(group) + check_configdb() + start_pleroma() - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group do - x |> delete(true) - end - end) - else - shell_error("No changes made.") - end - else - shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") - end - else - _ -> configdb_not_enabled() - end - end + group = maybe_atomize(group) - def run(["delete", group, key]) do - with true <- Pleroma.Config.get([:configurable_from_database]) do - start_pleroma() + if group_exists?(group) do + shell_info("The following settings will be removed from ConfigDB:\n") - group = maybe_atomize(group) - key = maybe_atomize(key) + dump_group(group) if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> - if x.group == group and x.key == key do + if x.group == group do x |> delete(true) end end) @@ -174,14 +135,33 @@ defmodule Mix.Tasks.Pleroma.Config do shell_error("No changes made.") end else - _ -> configdb_not_enabled() + shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") + end + end + + def run(["delete", group, key]) do + check_configdb() + start_pleroma() + + group = maybe_atomize(group) + key = maybe_atomize(key) + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group and x.key == key do + x |> delete(true) + end + end) + else + shell_error("No changes made.") end end @spec migrate_to_db(Path.t() | nil) :: any() def migrate_to_db(file_path \\ nil) do - with true <- Pleroma.Config.get([:configurable_from_database]), - :ok <- Pleroma.Config.DeprecationWarnings.warn() do + with :ok <- Pleroma.Config.DeprecationWarnings.warn() do config_file = if file_path do file_path @@ -195,8 +175,7 @@ defmodule Mix.Tasks.Pleroma.Config do do_migrate_to_db(config_file) else - :error -> deprecation_error() - _ -> migration_error() + _ -> deprecation_error() end end @@ -232,41 +211,31 @@ defmodule Mix.Tasks.Pleroma.Config do end defp migrate_from_db(opts) do - if Pleroma.Config.get([:configurable_from_database]) do - env = opts[:env] || Pleroma.Config.get(:env) - - config_path = - if Pleroma.Config.get(:release) do - :config_path - |> Pleroma.Config.get() - |> Path.dirname() - else - "config" - end - |> Path.join("#{env}.exported_from_db.secret.exs") + env = opts[:env] || Pleroma.Config.get(:env) - file = File.open!(config_path, [:write, :utf8]) + config_path = + if Pleroma.Config.get(:release) do + :config_path + |> Pleroma.Config.get() + |> Path.dirname() + else + "config" + end + |> Path.join("#{env}.exported_from_db.secret.exs") - IO.write(file, config_header()) + file = File.open!(config_path, [:write, :utf8]) - ConfigDB - |> Repo.all() - |> Enum.each(&write_and_delete(&1, file, opts[:delete])) + IO.write(file, config_header()) - :ok = File.close(file) - System.cmd("mix", ["format", config_path]) + ConfigDB + |> Repo.all() + |> Enum.each(&write_and_delete(&1, file, opts[:delete])) - shell_info( - "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs" - ) - else - migration_error() - end - end + :ok = File.close(file) + System.cmd("mix", ["format", config_path]) - defp migration_error do - shell_error( - "Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`" + shell_info( + "Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs" ) end @@ -313,7 +282,7 @@ defmodule Mix.Tasks.Pleroma.Config do end defp configdb_not_enabled do - shell_error( + raise( "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration." ) end @@ -366,4 +335,12 @@ defmodule Mix.Tasks.Pleroma.Config do String.to_atom(arg) end end + + defp check_configdb() do + with true <- Pleroma.Config.get([:configurable_from_database]) do + :ok + else + _ -> configdb_not_enabled() + end + end end -- cgit v1.2.3 From 13947999ad28eac6668a601bf957d2e64edda9d3 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 2 Dec 2020 12:33:34 -0600 Subject: Use a callback strategy to short circuit the functions and print a nice error --- lib/mix/tasks/pleroma/config.ex | 199 +++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 95 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index df4ee55c1..d509f150e 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -14,149 +14,158 @@ defmodule Mix.Tasks.Pleroma.Config do @moduledoc File.read!("docs/administration/CLI_tasks/config.md") def run(["migrate_to_db"]) do - check_configdb() - start_pleroma() - migrate_to_db() + check_configdb(fn -> + start_pleroma() + migrate_to_db() + end) end def run(["migrate_from_db" | options]) do - check_configdb() - start_pleroma() + check_configdb(fn -> + start_pleroma() - {opts, _} = - OptionParser.parse!(options, - strict: [env: :string, delete: :boolean], - aliases: [d: :delete] - ) + {opts, _} = + OptionParser.parse!(options, + strict: [env: :string, delete: :boolean], + aliases: [d: :delete] + ) - migrate_from_db(opts) + migrate_from_db(opts) + end) end def run(["dump"]) do - check_configdb() - start_pleroma() + check_configdb(fn -> + start_pleroma() - header = config_header() + header = config_header() - settings = - ConfigDB - |> Repo.all() - |> Enum.sort() + settings = + ConfigDB + |> Repo.all() + |> Enum.sort() - unless settings == [] do - shell_info("#{header}") + unless settings == [] do + shell_info("#{header}") - settings |> Enum.each(&dump(&1)) - else - shell_error("No settings in ConfigDB.") - end + settings |> Enum.each(&dump(&1)) + else + shell_error("No settings in ConfigDB.") + end + end) end def run(["dump", group, key]) do - check_configdb() - start_pleroma() + check_configdb(fn -> + start_pleroma() - group = maybe_atomize(group) - key = maybe_atomize(key) + group = maybe_atomize(group) + key = maybe_atomize(key) - dump_key(group, key) + dump_key(group, key) + end) end def run(["dump", group]) do - check_configdb() - start_pleroma() + check_configdb(fn -> + start_pleroma() - group = maybe_atomize(group) + group = maybe_atomize(group) - dump_group(group) + dump_group(group) + end) end def run(["groups"]) do - check_configdb() - start_pleroma() - - groups = - ConfigDB - |> Repo.all() - |> Enum.map(fn x -> x.group end) - |> Enum.sort() - |> Enum.uniq() + check_configdb(fn -> + start_pleroma() - if length(groups) > 0 do - shell_info("The following configuration groups are set in ConfigDB:\r\n") - groups |> Enum.each(fn x -> shell_info("- #{x}") end) - shell_info("\r\n") - end + groups = + ConfigDB + |> Repo.all() + |> Enum.map(fn x -> x.group end) + |> Enum.sort() + |> Enum.uniq() + + if length(groups) > 0 do + shell_info("The following configuration groups are set in ConfigDB:\r\n") + groups |> Enum.each(fn x -> shell_info("- #{x}") end) + shell_info("\r\n") + end + end) end def run(["reset"]) do - check_configdb() - start_pleroma() + check_configdb(fn -> + start_pleroma() - shell_info("The following settings will be permanently removed:") + shell_info("The following settings will be permanently removed:") - ConfigDB - |> Repo.all() - |> Enum.sort() - |> Enum.each(&dump(&1)) + ConfigDB + |> Repo.all() + |> Enum.sort() + |> Enum.each(&dump(&1)) - shell_error("\nTHIS CANNOT BE UNDONE!") + shell_error("\nTHIS CANNOT BE UNDONE!") - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") - Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") + Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") - shell_info("The ConfigDB settings have been removed from the database.") - else - shell_error("No changes made.") - end + shell_info("The ConfigDB settings have been removed from the database.") + else + shell_error("No changes made.") + end + end) end def run(["delete", group]) do - check_configdb() - start_pleroma() + check_configdb(fn -> + start_pleroma() - group = maybe_atomize(group) + group = maybe_atomize(group) - if group_exists?(group) do - shell_info("The following settings will be removed from ConfigDB:\n") + if group_exists?(group) do + shell_info("The following settings will be removed from ConfigDB:\n") - dump_group(group) + dump_group(group) + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group do + x |> delete(true) + end + end) + else + shell_error("No changes made.") + end + else + shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") + end + end) + end + + def run(["delete", group, key]) do + check_configdb(fn -> + start_pleroma() + + group = maybe_atomize(group) + key = maybe_atomize(key) if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do ConfigDB |> Repo.all() |> Enum.filter(fn x -> - if x.group == group do + if x.group == group and x.key == key do x |> delete(true) end end) else shell_error("No changes made.") end - else - shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") - end - end - - def run(["delete", group, key]) do - check_configdb() - start_pleroma() - - group = maybe_atomize(group) - key = maybe_atomize(key) - - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group and x.key == key do - x |> delete(true) - end - end) - else - shell_error("No changes made.") - end + end) end @spec migrate_to_db(Path.t() | nil) :: any() @@ -282,7 +291,7 @@ defmodule Mix.Tasks.Pleroma.Config do end defp configdb_not_enabled do - raise( + shell_error( "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration." ) end @@ -336,9 +345,9 @@ defmodule Mix.Tasks.Pleroma.Config do end end - defp check_configdb() do + defp check_configdb(callback) do with true <- Pleroma.Config.get([:configurable_from_database]) do - :ok + callback.() else _ -> configdb_not_enabled() end -- cgit v1.2.3 From 25fab7da69e2a6019598132e5d776d7cebe42045 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 2 Dec 2020 13:00:07 -0600 Subject: No need for a separate functions here --- lib/mix/tasks/pleroma/config.ex | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index d509f150e..e53e21a0b 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -184,7 +184,8 @@ defmodule Mix.Tasks.Pleroma.Config do do_migrate_to_db(config_file) else - _ -> deprecation_error() + _ -> + shell_error("Migration is not allowed until all deprecation warnings have been resolved.") end end @@ -248,10 +249,6 @@ defmodule Mix.Tasks.Pleroma.Config do ) end - defp deprecation_error do - shell_error("Migration is not allowed until all deprecation warnings have been resolved.") - end - if Code.ensure_loaded?(Config.Reader) do defp config_header, do: "import Config\r\n\r\n" defp read_file(config_file), do: Config.Reader.read_imports!(config_file) @@ -290,12 +287,6 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n") end - defp configdb_not_enabled do - shell_error( - "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration." - ) - end - defp dump_key(group, key) when is_atom(group) and is_atom(key) do ConfigDB |> Repo.all() @@ -349,7 +340,10 @@ defmodule Mix.Tasks.Pleroma.Config do with true <- Pleroma.Config.get([:configurable_from_database]) do callback.() else - _ -> configdb_not_enabled() + _ -> + shell_error( + "ConfigDB not enabled. Please check the value of :configurable_from_database in your configuration." + ) end end end -- cgit v1.2.3 From 20a911f9f725088e841f2ebce220b26b1b4fe222 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 2 Dec 2020 14:22:59 -0600 Subject: Add comment for this mysterious behavior --- lib/mix/tasks/pleroma/config.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index e53e21a0b..e2c4cc680 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -329,6 +329,8 @@ defmodule Mix.Tasks.Pleroma.Config do defp maybe_atomize(arg) when is_binary(arg) do chars = String.codepoints(arg) + # hack to make sure input like Pleroma.Mailer.Foo is formatted correctly + # for matching against values returned by Ecto if "." in chars do :"Elixir.#{arg}" else -- cgit v1.2.3 From e379ab8277f552d66737963a9c908ae3fc01c1ff Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 2 Dec 2020 16:24:32 -0600 Subject: Add --force flag for delete and reset commands Bunch of reorganization and consolidation --- lib/mix/tasks/pleroma/config.ex | 110 +++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 41 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index e2c4cc680..014782c35 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -95,7 +95,7 @@ defmodule Mix.Tasks.Pleroma.Config do end) end - def run(["reset"]) do + def run(["reset" | options]) do check_configdb(fn -> start_pleroma() @@ -108,7 +108,11 @@ defmodule Mix.Tasks.Pleroma.Config do shell_error("\nTHIS CANNOT BE UNDONE!") - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + proceed? = + "--force" in options or + shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) + + if proceed? do Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") @@ -119,53 +123,46 @@ defmodule Mix.Tasks.Pleroma.Config do end) end - def run(["delete", group]) do - check_configdb(fn -> - start_pleroma() + def run(["delete", "--force", group, key]) do + start_pleroma() - group = maybe_atomize(group) + group = maybe_atomize(group) + key = maybe_atomize(key) - if group_exists?(group) do - shell_info("The following settings will be removed from ConfigDB:\n") + delete_key(group, key) + end - dump_group(group) + def run(["delete", "--force", group]) do + start_pleroma() - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group do - x |> delete(true) - end - end) - else - shell_error("No changes made.") - end - else - shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") - end - end) + group = maybe_atomize(group) + + delete_group(group) end def run(["delete", group, key]) do - check_configdb(fn -> - start_pleroma() + start_pleroma() - group = maybe_atomize(group) - key = maybe_atomize(key) + group = maybe_atomize(group) + key = maybe_atomize(key) - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group and x.key == key do - x |> delete(true) - end - end) - else - shell_error("No changes made.") - end - end) + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + delete_key(group, key) + else + shell_error("No changes made.") + end + end + + def run(["delete", group]) do + start_pleroma() + + group = maybe_atomize(group) + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + delete_group(group) + else + shell_error("No changes made.") + end end @spec migrate_to_db(Path.t() | nil) :: any() @@ -275,7 +272,7 @@ defmodule Mix.Tasks.Pleroma.Config do {:ok, _} = Repo.delete(config) shell_info( - "config #{inspect(config.group)}, #{inspect(config.key)} deleted from the ConfigDB." + "config #{inspect(config.group)}, #{inspect(config.key)} was deleted from the ConfigDB." ) end @@ -348,4 +345,35 @@ defmodule Mix.Tasks.Pleroma.Config do ) end end + + defp delete_key(group, key) do + check_configdb(fn -> + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group and x.key == key do + x |> delete(true) + end + end) + end) + end + + defp delete_group(group) do + check_configdb(fn -> + with true <- group_exists?(group) do + shell_info("The following settings will be removed from ConfigDB:\n") + dump_group(group) + + ConfigDB + |> Repo.all() + |> Enum.filter(fn x -> + if x.group == group do + x |> delete(true) + end + end) + else + _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") + end + end) + end end -- cgit v1.2.3 From 16bdc2bcd0600ae4c1fcb55eaa84824af01ee61e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 2 Dec 2020 16:34:23 -0600 Subject: Make the --force flag for reset command consistent with the others and deduplicate db truncation --- lib/mix/tasks/pleroma/config.ex | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 014782c35..ebaf2c623 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -95,7 +95,15 @@ defmodule Mix.Tasks.Pleroma.Config do end) end - def run(["reset" | options]) do + def run(["reset", "--force"]) do + check_configdb(fn -> + start_pleroma() + truncatedb() + shell_info("The ConfigDB settings have been removed from the database.") + end) + end + + def run(["reset"]) do check_configdb(fn -> start_pleroma() @@ -108,13 +116,8 @@ defmodule Mix.Tasks.Pleroma.Config do shell_error("\nTHIS CANNOT BE UNDONE!") - proceed? = - "--force" in options or - shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) - - if proceed? do - Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") - Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + truncatedb() shell_info("The ConfigDB settings have been removed from the database.") else @@ -189,8 +192,7 @@ defmodule Mix.Tasks.Pleroma.Config do defp do_migrate_to_db(config_file) do if File.exists?(config_file) do shell_info("Migrating settings from file: #{Path.expand(config_file)}") - Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") - Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + truncatedb() custom_config = config_file @@ -376,4 +378,9 @@ defmodule Mix.Tasks.Pleroma.Config do end end) end + + defp truncatedb() do + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") + Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + end end -- cgit v1.2.3 From fa0d0b602f10a3671ff00151028990c57d8ab447 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 3 Dec 2020 16:17:39 +0100 Subject: Emoji: Also accept regional indicators --- lib/pleroma/emoji.ex | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 201212779..513fb59f8 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -104,6 +104,11 @@ defmodule Pleroma.Emoji do @external_resource "lib/pleroma/emoji-test.txt" + regional_indicators = + Enum.map(127_462..127_487, fn codepoint -> + <> + end) + emojis = @external_resource |> File.read!() @@ -125,6 +130,8 @@ defmodule Pleroma.Emoji do end) |> Enum.uniq() + emojis = emojis ++ regional_indicators + for emoji <- emojis do def is_unicode_emoji?(unquote(emoji)), do: true end -- cgit v1.2.3 From 95e908e4e2273a4b07218e45b46ecbeaa0f08e1c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 3 Dec 2020 09:58:24 -0600 Subject: Credo --- lib/mix/tasks/pleroma/config.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index ebaf2c623..a6173e0e2 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -379,7 +379,7 @@ defmodule Mix.Tasks.Pleroma.Config do end) end - defp truncatedb() do + defp truncatedb do Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") end -- cgit v1.2.3 From 60c4ac0f708b4a67d6168ed327327dcb13e7219f Mon Sep 17 00:00:00 2001 From: feld Date: Thu, 3 Dec 2020 16:03:14 +0000 Subject: Apply 6 suggestion(s) to 1 file(s) --- lib/mix/tasks/pleroma/config.ex | 61 ++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index a6173e0e2..f4bb84a13 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -62,7 +62,10 @@ defmodule Mix.Tasks.Pleroma.Config do group = maybe_atomize(group) key = maybe_atomize(key) - dump_key(group, key) + %{group: group, key: key} + |> ConfigDB.get_by_params() + |> Repo.all() + |> Enum.each(&dump/1) end) end @@ -297,44 +300,27 @@ defmodule Mix.Tasks.Pleroma.Config do end defp dump_group(group) when is_atom(group) do - ConfigDB + %{group: group} + |> ConfigDB.get_by_params() |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group do - x |> dump - end - end) + |> Enum.each(&dump/1) end - defp group_exists?(group) when is_atom(group) do - result = - ConfigDB + defp group_exists?(group) do + %{group: group} + |> ConfigDB.get_by_params() |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group do - x - end - end) - - unless result == [] do - true - else - false - end + |> Enum.empty?() end defp maybe_atomize(arg) when is_atom(arg), do: arg defp maybe_atomize(arg) when is_binary(arg) do - chars = String.codepoints(arg) - - # hack to make sure input like Pleroma.Mailer.Foo is formatted correctly - # for matching against values returned by Ecto - if "." in chars do - :"Elixir.#{arg}" + if Pleroma.ConfigDB.module_name?(arg) do + String.to_existing_atom("Elixir." <> arg) else String.to_atom(arg) - end + end end defp check_configdb(callback) do @@ -350,13 +336,9 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete_key(group, key) do check_configdb(fn -> - ConfigDB + ConfigDB.get_by_params(%{group: group, key: key}) |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group and x.key == key do - x |> delete(true) - end - end) + |> Enum.each(&delete(&1, true)) end) end @@ -366,13 +348,10 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("The following settings will be removed from ConfigDB:\n") dump_group(group) - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group do - x |> delete(true) - end - end) + ConfigDB.get_by_params(%{group: group}) + |> Repo.all() + |> Enum.each(&delete(&1, true)) + else _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") end -- cgit v1.2.3 From 7fd4f4908bc31b3b4cc9d73a79169c3b3f08714c Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 3 Dec 2020 10:03:44 -0600 Subject: dump_key/2 no longer used --- lib/mix/tasks/pleroma/config.ex | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index f4bb84a13..137aef038 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -289,16 +289,6 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n") end - defp dump_key(group, key) when is_atom(group) and is_atom(key) do - ConfigDB - |> Repo.all() - |> Enum.filter(fn x -> - if x.group == group && x.key == key do - x |> dump - end - end) - end - defp dump_group(group) when is_atom(group) do %{group: group} |> ConfigDB.get_by_params() -- cgit v1.2.3 From a02eb8839650ecbf8bcad9bd6d346fc280985cae Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Thu, 3 Dec 2020 19:34:23 +0300 Subject: config_db search methods --- lib/mix/tasks/pleroma/config.ex | 30 +++++++++++++----------------- lib/pleroma/config_db.ex | 12 +++++++++++- 2 files changed, 24 insertions(+), 18 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 137aef038..63d8c46b5 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -62,9 +62,8 @@ defmodule Mix.Tasks.Pleroma.Config do group = maybe_atomize(group) key = maybe_atomize(key) - %{group: group, key: key} - |> ConfigDB.get_by_params() - |> Repo.all() + group + |> ConfigDB.get_all_by_group_and_key(key) |> Enum.each(&dump/1) end) end @@ -290,17 +289,15 @@ defmodule Mix.Tasks.Pleroma.Config do end defp dump_group(group) when is_atom(group) do - %{group: group} - |> ConfigDB.get_by_params() - |> Repo.all() + group + |> ConfigDB.get_all_by_group() |> Enum.each(&dump/1) end defp group_exists?(group) do - %{group: group} - |> ConfigDB.get_by_params() - |> Repo.all() - |> Enum.empty?() + group + |> ConfigDB.get_all_by_group() + |> Enum.empty?() end defp maybe_atomize(arg) when is_atom(arg), do: arg @@ -310,7 +307,7 @@ defmodule Mix.Tasks.Pleroma.Config do String.to_existing_atom("Elixir." <> arg) else String.to_atom(arg) - end + end end defp check_configdb(callback) do @@ -326,8 +323,8 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete_key(group, key) do check_configdb(fn -> - ConfigDB.get_by_params(%{group: group, key: key}) - |> Repo.all() + group + |> ConfigDB.get_all_by_group_and_key(key) |> Enum.each(&delete(&1, true)) end) end @@ -338,10 +335,9 @@ defmodule Mix.Tasks.Pleroma.Config do shell_info("The following settings will be removed from ConfigDB:\n") dump_group(group) - ConfigDB.get_by_params(%{group: group}) - |> Repo.all() - |> Enum.each(&delete(&1, true)) - + group + |> ConfigDB.get_all_by_group() + |> Enum.each(&delete(&1, true)) else _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") end diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex index e5b7811aa..2c3c0cb5c 100644 --- a/lib/pleroma/config_db.ex +++ b/lib/pleroma/config_db.ex @@ -6,7 +6,7 @@ defmodule Pleroma.ConfigDB do use Ecto.Schema import Ecto.Changeset - import Ecto.Query, only: [select: 3] + import Ecto.Query, only: [select: 3, from: 2] import Pleroma.Web.Gettext alias __MODULE__ @@ -41,6 +41,16 @@ defmodule Pleroma.ConfigDB do end) end + @spec get_all_by_group(atom() | String.t()) :: [t()] + def get_all_by_group(group) do + from(c in ConfigDB, where: c.group == ^group) |> Repo.all() + end + + @spec get_all_by_group_and_key(atom() | String.t(), atom() | String.t()) :: [t()] + def get_all_by_group_and_key(group, key) do + from(c in ConfigDB, where: c.group == ^group and c.key == ^key) |> Repo.all() + end + @spec get_by_params(map()) :: ConfigDB.t() | nil def get_by_params(params), do: Repo.get_by(ConfigDB, params) -- cgit v1.2.3 From 4aad066091b63d88dcffa20458a097407da4f5b0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Dec 2020 11:04:53 -0600 Subject: Use Enum.any? to ensure we return true if there are results --- lib/mix/tasks/pleroma/config.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 63d8c46b5..d2e9a3760 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -297,7 +297,7 @@ defmodule Mix.Tasks.Pleroma.Config do defp group_exists?(group) do group |> ConfigDB.get_all_by_group() - |> Enum.empty?() + |> Enum.any?() end defp maybe_atomize(arg) when is_atom(arg), do: arg -- cgit v1.2.3 From 685e5c8509b4c08bb74eab2438912031ab9b1c19 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Dec 2020 11:09:13 -0600 Subject: Use Pleroma.ConfigDB.delete/1 instead of rolling our own --- lib/mix/tasks/pleroma/config.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index d2e9a3760..7ec791b36 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -323,9 +323,7 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete_key(group, key) do check_configdb(fn -> - group - |> ConfigDB.get_all_by_group_and_key(key) - |> Enum.each(&delete(&1, true)) + Pleroma.ConfigDB.delete(%{group: group, key: key}) end) end -- cgit v1.2.3 From 696d39c3dc32da1e3e163abb413f42d68c3a731f Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Dec 2020 11:19:58 -0600 Subject: Fix deleting an entire group. Also utilize Pleroma.ConfigDB.delete/1 --- lib/mix/tasks/pleroma/config.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 7ec791b36..00e7be6f4 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -334,8 +334,10 @@ defmodule Mix.Tasks.Pleroma.Config do dump_group(group) group - |> ConfigDB.get_all_by_group() - |> Enum.each(&delete(&1, true)) + |> Pleroma.ConfigDB.get_all_by_group() + |> Enum.each(fn config -> + Pleroma.ConfigDB.delete(%{group: config.group, key: config.key}) + end) else _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") end -- cgit v1.2.3 From 3bf5c5b0156e1357db22df8e377c5cd5c5c8ea5a Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Dec 2020 11:30:48 -0600 Subject: Ensure deleting entire group prints out settings that will be removed before actually removing them --- lib/mix/tasks/pleroma/config.ex | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 00e7be6f4..99dfd0dc3 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -142,7 +142,13 @@ defmodule Mix.Tasks.Pleroma.Config do group = maybe_atomize(group) - delete_group(group) + with true <- group_exists?(group) do + shell_info("The following settings will be removed from ConfigDB:\n") + dump_group(group) + delete_group(group) + else + _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") + end end def run(["delete", group, key]) do @@ -163,10 +169,17 @@ defmodule Mix.Tasks.Pleroma.Config do group = maybe_atomize(group) - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - delete_group(group) + with true <- group_exists?(group) do + shell_info("The following settings will be removed from ConfigDB:\n") + dump_group(group) + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + delete_group(group) + else + shell_error("No changes made.") + end else - shell_error("No changes made.") + _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") end end @@ -329,18 +342,11 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete_group(group) do check_configdb(fn -> - with true <- group_exists?(group) do - shell_info("The following settings will be removed from ConfigDB:\n") - dump_group(group) - - group - |> Pleroma.ConfigDB.get_all_by_group() - |> Enum.each(fn config -> - Pleroma.ConfigDB.delete(%{group: config.group, key: config.key}) - end) - else - _ -> shell_error("No settings in ConfigDB for #{inspect(group)}. Aborting.") - end + group + |> Pleroma.ConfigDB.get_all_by_group() + |> Enum.each(fn config -> + Pleroma.ConfigDB.delete(%{group: config.group, key: config.key}) + end) end) end -- cgit v1.2.3 From 9dfda37821d663c4b2f8e113336a517d694abee0 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 4 Dec 2020 11:37:49 -0600 Subject: More compact representation --- lib/mix/tasks/pleroma/config.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 99dfd0dc3..25f1ca05d 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -344,9 +344,7 @@ defmodule Mix.Tasks.Pleroma.Config do check_configdb(fn -> group |> Pleroma.ConfigDB.get_all_by_group() - |> Enum.each(fn config -> - Pleroma.ConfigDB.delete(%{group: config.group, key: config.key}) - end) + |> Enum.each(&ConfigDB.delete/1) end) end -- cgit v1.2.3 From 50aadc3d5cc35e5210cb12c4858ecfdba4df56b1 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Fri, 27 Nov 2020 13:42:28 -0600 Subject: shell_yes?/1 was not showing the correct message and always defaults to yes which is dangerous --- lib/mix/pleroma.ex | 6 ------ lib/mix/tasks/pleroma/user.ex | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index cd3f44074..7575f0ef8 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -98,12 +98,6 @@ defmodule Mix.Pleroma do end end - def shell_yes?(message) do - if mix_shell?(), - do: Mix.shell().yes?("Continue?"), - else: shell_prompt(message, "Continue?") in ~w(Yn Y y) - end - def shell_info(message) do if mix_shell?(), do: Mix.shell().info(message), diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index a8d251411..ca9c8579f 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -60,7 +60,7 @@ defmodule Mix.Tasks.Pleroma.User do - admin: #{if(admin?, do: "true", else: "false")} """) - proceed? = assume_yes? or shell_yes?("Continue?") + proceed? = assume_yes? or shell_prompt("Continue?", "n") in ~w(Yn Y y) if proceed? do start_pleroma() -- cgit v1.2.3 From e9859b68fcb9c38b2ec27a45ffe0921e8d78b5e1 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sun, 6 Dec 2020 13:59:10 +0300 Subject: [#3112] Ensured presence and consistency of :user and :token assigns (EnsureUserTokenAssignsPlug). Refactored auth info dropping functions. --- lib/pleroma/helpers/auth_helper.ex | 6 +++ lib/pleroma/web.ex | 3 +- .../web/plugs/admin_secret_authentication_plug.ex | 18 ++++---- lib/pleroma/web/plugs/ensure_user_key_plug.ex | 19 -------- .../web/plugs/ensure_user_token_assigns_plug.ex | 36 +++++++++++++++ .../web/plugs/mapped_signature_to_identity_plug.ex | 51 ++++++++++++---------- lib/pleroma/web/plugs/o_auth_scopes_plug.ex | 12 +---- lib/pleroma/web/plugs/user_enabled_plug.ex | 10 ++--- lib/pleroma/web/router.ex | 7 ++- 9 files changed, 90 insertions(+), 72 deletions(-) delete mode 100644 lib/pleroma/web/plugs/ensure_user_key_plug.ex create mode 100644 lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex (limited to 'lib') diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex index 392fa7d5d..8f87b38be 100644 --- a/lib/pleroma/helpers/auth_helper.ex +++ b/lib/pleroma/helpers/auth_helper.ex @@ -20,20 +20,26 @@ defmodule Pleroma.Helpers.AuthHelper do |> OAuthScopesPlug.skip_plug() end + @doc "Drops authentication info from connection" def drop_auth_info(conn) do + # To simplify debugging, setting a private variable on `conn` if auth info is dropped conn |> assign(:user, nil) |> assign(:token, nil) + |> put_private(:authentication_ignored, true) end + @doc "Gets OAuth token string from session" def get_session_token(%Conn{} = conn) do get_session(conn, @oauth_token_session_key) end + @doc "Updates OAuth token string in session" def put_session_token(%Conn{} = conn, token) when is_binary(token) do put_session(conn, @oauth_token_session_key, token) end + @doc "Deletes OAuth token string from session" def delete_session_token(%Conn{} = conn) do delete_session(conn, @oauth_token_session_key) end diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index 6ed19d3dd..3ca20455d 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -20,6 +20,7 @@ defmodule Pleroma.Web do below. """ + alias Pleroma.Helpers.AuthHelper alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug @@ -75,7 +76,7 @@ defmodule Pleroma.Web do defp maybe_drop_authentication_if_oauth_check_ignored(conn) do if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do - OAuthScopesPlug.drop_auth_info(conn) + AuthHelper.drop_auth_info(conn) else conn end diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex index ff49801f4..ff851a874 100644 --- a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex +++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex @@ -13,13 +13,6 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do options end - def secret_token do - case Pleroma.Config.get(:admin_token) do - blank when blank in [nil, ""] -> nil - token -> token - end - end - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(conn, _) do @@ -30,7 +23,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do end end - def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do + defp authenticate(%{params: %{"admin_token" => admin_token}} = conn) do if admin_token == secret_token() do assign_admin_user(conn) else @@ -38,7 +31,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do end end - def authenticate(conn) do + defp authenticate(conn) do token = secret_token() case get_req_header(conn, "x-admin-token") do @@ -48,6 +41,13 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do end end + defp secret_token do + case Pleroma.Config.get(:admin_token) do + blank when blank in [nil, ""] -> nil + token -> token + end + end + defp assign_admin_user(conn) do conn |> assign(:user, %User{is_admin: true}) diff --git a/lib/pleroma/web/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex deleted file mode 100644 index 31608dbbf..000000000 --- a/lib/pleroma/web/plugs/ensure_user_key_plug.ex +++ /dev/null @@ -1,19 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do - import Plug.Conn - - @moduledoc "Ensures `conn.assigns.user` is initialized." - - def init(opts) do - opts - end - - def call(%{assigns: %{user: _}} = conn, _), do: conn - - def call(conn, _) do - assign(conn, :user, nil) - end -end diff --git a/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex new file mode 100644 index 000000000..4253458b2 --- /dev/null +++ b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug do + import Plug.Conn + + alias Pleroma.Helpers.AuthHelper + alias Pleroma.User + alias Pleroma.Web.OAuth.Token + + @moduledoc "Ensures presence and consistency of :user and :token assigns." + + def init(opts) do + opts + end + + def call(%{assigns: %{user: %User{id: user_id}} = assigns} = conn, _) do + with %Token{user_id: ^user_id} <- assigns[:token] do + conn + else + %Token{} -> + # A safety net for abnormal (unexpected) scenario: :token belongs to another user + AuthHelper.drop_auth_info(conn) + + _ -> + assign(conn, :token, nil) + end + end + + def call(conn, _) do + conn + |> assign(:user, nil) + |> assign(:token, nil) + end +end diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index f44d4dee5..a0a0c5a9b 100644 --- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do + alias Pleroma.Helpers.AuthHelper alias Pleroma.Signature alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils @@ -12,34 +13,16 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do def init(options), do: options - defp key_id_from_conn(conn) do - with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), - {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do - ap_id - else - _ -> - nil - end - end - - defp user_from_key_id(conn) do - with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), - {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do - user - else - _ -> - nil - end - end - - def call(%{assigns: %{user: _}} = conn, _opts), do: conn + def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn # if this has payload make sure it is signed by the same actor that made it def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do with actor_id <- Utils.get_ap_id(actor), {:user, %User{} = user} <- {:user, user_from_key_id(conn)}, {:user_match, true} <- {:user_match, user.ap_id == actor_id} do - assign(conn, :user, user) + conn + |> assign(:user, user) + |> AuthHelper.skip_oauth() else {:user_match, false} -> Logger.debug("Failed to map identity from signature (payload actor mismatch)") @@ -57,7 +40,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do # no payload, probably a signed fetch def call(%{assigns: %{valid_signature: true}} = conn, _opts) do with %User{} = user <- user_from_key_id(conn) do - assign(conn, :user, user) + conn + |> assign(:user, user) + |> AuthHelper.skip_oauth() else _ -> Logger.debug("Failed to map identity from signature (no payload actor mismatch)") @@ -68,4 +53,24 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do # no signature at all def call(conn, _opts), do: conn + + defp key_id_from_conn(conn) do + with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), + {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do + ap_id + else + _ -> + nil + end + end + + defp user_from_key_id(conn) do + with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), + {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do + user + else + _ -> + nil + end + end end diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex index cfc30837c..e6d398b14 100644 --- a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do import Pleroma.Web.Gettext alias Pleroma.Config + alias Pleroma.Helpers.AuthHelper use Pleroma.Web, :plug @@ -28,7 +29,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do conn options[:fallback] == :proceed_unauthenticated -> - drop_auth_info(conn) + AuthHelper.drop_auth_info(conn) true -> missing_scopes = scopes -- matched_scopes @@ -44,15 +45,6 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do end end - @doc "Drops authentication info from connection" - def drop_auth_info(conn) do - # To simplify debugging, setting a private variable on `conn` if auth info is dropped - conn - |> put_private(:authentication_ignored, true) - |> assign(:user, nil) - |> assign(:token, nil) - end - @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" def filter_descendants(scopes, supported_scopes) do Enum.filter( diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex index 291d1f568..4f1b163bd 100644 --- a/lib/pleroma/web/plugs/user_enabled_plug.ex +++ b/lib/pleroma/web/plugs/user_enabled_plug.ex @@ -11,12 +11,10 @@ defmodule Pleroma.Web.Plugs.UserEnabledPlug do end def call(%{assigns: %{user: %User{} = user}} = conn, _) do - case User.account_status(user) do - :active -> - conn - - _ -> - AuthHelper.drop_auth_info(conn) + if User.account_status(user) == :active do + conn + else + AuthHelper.drop_auth_info(conn) end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b3462ba00..aefc9f0be 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -34,7 +34,7 @@ defmodule Pleroma.Web.Router do plug(:fetch_session) plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.UserEnabledPlug) - plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) end pipeline :expect_authentication do @@ -55,7 +55,7 @@ defmodule Pleroma.Web.Router do pipeline :after_auth do plug(Pleroma.Web.Plugs.UserEnabledPlug) plug(Pleroma.Web.Plugs.SetUserSessionIdPlug) - plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) end pipeline :base_api do @@ -99,7 +99,7 @@ defmodule Pleroma.Web.Router do pipeline :pleroma_html do plug(:browser) plug(:authenticate) - plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) end pipeline :well_known do @@ -291,7 +291,6 @@ defmodule Pleroma.Web.Router do post("/main/ostatus", UtilController, :remote_subscribe) get("/ostatus_subscribe", RemoteFollowController, :follow) - post("/ostatus_subscribe", RemoteFollowController, :do_follow) end -- cgit v1.2.3 From e00c66714590948ef917909779772155e20a3c96 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sun, 6 Dec 2020 18:02:30 +0300 Subject: [#3174] Refactoring: ConfigDB fetching functions, ConfigDB tests. Minor fixes. --- lib/mix/tasks/pleroma/config.ex | 22 ++++++++++++---------- lib/pleroma/config_db.ex | 8 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 25f1ca05d..b5d802948 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -5,6 +5,7 @@ defmodule Mix.Tasks.Pleroma.Config do use Mix.Task + import Ecto.Query import Mix.Pleroma alias Pleroma.ConfigDB @@ -48,7 +49,7 @@ defmodule Mix.Tasks.Pleroma.Config do unless settings == [] do shell_info("#{header}") - settings |> Enum.each(&dump(&1)) + Enum.each(settings, &dump(&1)) else shell_error("No settings in ConfigDB.") end @@ -63,8 +64,8 @@ defmodule Mix.Tasks.Pleroma.Config do key = maybe_atomize(key) group - |> ConfigDB.get_all_by_group_and_key(key) - |> Enum.each(&dump/1) + |> ConfigDB.get_by_group_and_key(key) + |> dump() end) end @@ -84,10 +85,9 @@ defmodule Mix.Tasks.Pleroma.Config do groups = ConfigDB + |> distinct([c], true) + |> select([c], c.group) |> Repo.all() - |> Enum.map(fn x -> x.group end) - |> Enum.sort() - |> Enum.uniq() if length(groups) > 0 do shell_info("The following configuration groups are set in ConfigDB:\r\n") @@ -295,12 +295,14 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete(_config, _), do: :ok - defp dump(%Pleroma.ConfigDB{} = config) do + defp dump(%ConfigDB{} = config) do value = inspect(config.value, limit: :infinity) shell_info("config #{inspect(config.group)}, #{inspect(config.key)}, #{value}\r\n\r\n") end + defp dump(_), do: :noop + defp dump_group(group) when is_atom(group) do group |> ConfigDB.get_all_by_group() @@ -316,7 +318,7 @@ defmodule Mix.Tasks.Pleroma.Config do defp maybe_atomize(arg) when is_atom(arg), do: arg defp maybe_atomize(arg) when is_binary(arg) do - if Pleroma.ConfigDB.module_name?(arg) do + if ConfigDB.module_name?(arg) do String.to_existing_atom("Elixir." <> arg) else String.to_atom(arg) @@ -336,14 +338,14 @@ defmodule Mix.Tasks.Pleroma.Config do defp delete_key(group, key) do check_configdb(fn -> - Pleroma.ConfigDB.delete(%{group: group, key: key}) + ConfigDB.delete(%{group: group, key: key}) end) end defp delete_group(group) do check_configdb(fn -> group - |> Pleroma.ConfigDB.get_all_by_group() + |> ConfigDB.get_all_by_group() |> Enum.each(&ConfigDB.delete/1) end) end diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex index 2c3c0cb5c..8e8bb732f 100644 --- a/lib/pleroma/config_db.ex +++ b/lib/pleroma/config_db.ex @@ -46,13 +46,13 @@ defmodule Pleroma.ConfigDB do from(c in ConfigDB, where: c.group == ^group) |> Repo.all() end - @spec get_all_by_group_and_key(atom() | String.t(), atom() | String.t()) :: [t()] - def get_all_by_group_and_key(group, key) do - from(c in ConfigDB, where: c.group == ^group and c.key == ^key) |> Repo.all() + @spec get_by_group_and_key(atom() | String.t(), atom() | String.t()) :: t() | nil + def get_by_group_and_key(group, key) do + get_by_params(%{group: group, key: key}) end @spec get_by_params(map()) :: ConfigDB.t() | nil - def get_by_params(params), do: Repo.get_by(ConfigDB, params) + def get_by_params(%{group: _, key: _} = params), do: Repo.get_by(ConfigDB, params) @spec changeset(ConfigDB.t(), map()) :: Changeset.t() def changeset(config, params \\ %{}) do -- cgit v1.2.3 From d817bae802c40bd2db9a88970cf24e98374b0af0 Mon Sep 17 00:00:00 2001 From: feld Date: Mon, 7 Dec 2020 17:13:29 +0000 Subject: Apply 1 suggestion(s) to 1 file(s) --- lib/mix/tasks/pleroma/config.ex | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index b5d802948..2ecad3578 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -316,6 +316,8 @@ defmodule Mix.Tasks.Pleroma.Config do end defp maybe_atomize(arg) when is_atom(arg), do: arg + + defp maybe_atomize(":" <> arg), do: maybe_atomize(arg) defp maybe_atomize(arg) when is_binary(arg) do if ConfigDB.module_name?(arg) do -- cgit v1.2.3 From 61494b5245619eda38f05d010511df068280cff8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 7 Dec 2020 11:22:07 -0600 Subject: Formatting --- lib/mix/tasks/pleroma/config.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 2ecad3578..d1af0a60c 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -316,8 +316,8 @@ defmodule Mix.Tasks.Pleroma.Config do end defp maybe_atomize(arg) when is_atom(arg), do: arg - - defp maybe_atomize(":" <> arg), do: maybe_atomize(arg) + + defp maybe_atomize(":" <> arg), do: maybe_atomize(arg) defp maybe_atomize(arg) when is_binary(arg) do if ConfigDB.module_name?(arg) do -- cgit v1.2.3 From 93428d7c11ce30d38fa23192c9a15e2e713a50be Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 7 Dec 2020 11:45:56 -0600 Subject: Print out settings that will be removed when specifying the group and key for consistency Fix error message when specified key doesn't exist --- lib/mix/tasks/pleroma/config.ex | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index d1af0a60c..d7e2e97e7 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -134,7 +134,18 @@ defmodule Mix.Tasks.Pleroma.Config do group = maybe_atomize(group) key = maybe_atomize(key) - delete_key(group, key) + with true <- key_exists?(group, key) do + shell_info("The following settings will be removed from ConfigDB:\n") + + group + |> ConfigDB.get_by_group_and_key(key) + |> dump() + + delete_key(group, key) + else + _ -> + shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.") + end end def run(["delete", "--force", group]) do @@ -157,10 +168,21 @@ defmodule Mix.Tasks.Pleroma.Config do group = maybe_atomize(group) key = maybe_atomize(key) - if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do - delete_key(group, key) + with true <- key_exists?(group, key) do + shell_info("The following settings will be removed from ConfigDB:\n") + + group + |> ConfigDB.get_by_group_and_key(key) + |> dump() + + if shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do + delete_key(group, key) + else + shell_error("No changes made.") + end else - shell_error("No changes made.") + _ -> + shell_error("No settings in ConfigDB for #{inspect(group)}, #{inspect(key)}. Aborting.") end end @@ -315,6 +337,13 @@ defmodule Mix.Tasks.Pleroma.Config do |> Enum.any?() end + defp key_exists?(group, key) do + group + |> ConfigDB.get_by_group_and_key(key) + |> is_nil + |> Kernel.!() + end + defp maybe_atomize(arg) when is_atom(arg), do: arg defp maybe_atomize(":" <> arg), do: maybe_atomize(arg) -- cgit v1.2.3 From e1a2e8b17cca0d9f50b72fcea0ec5ffb8e613db1 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 7 Dec 2020 20:09:34 +0100 Subject: instance: Do not fetch unreachable instances Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/2346 --- lib/pleroma/instances/instance.ex | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index df471a39d..c9ca3aac7 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -166,7 +166,8 @@ defmodule Pleroma.Instances.Instance do defp scrape_favicon(%URI{} = instance_uri) do try do - with {:ok, %Tesla.Env{body: html}} <- + with {_, true} <- {:reachable, reachable?(instance_uri.host)}, + {:ok, %Tesla.Env{body: html}} <- Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], pool: :media), {_, [favicon_rel | _]} when is_binary(favicon_rel) <- {:parse, @@ -175,7 +176,15 @@ defmodule Pleroma.Instances.Instance do {:merge, URI.merge(instance_uri, favicon_rel) |> to_string()} do favicon else - _ -> nil + {:reachable, false} -> + Logger.debug( + "Instance.scrape_favicon(\"#{to_string(instance_uri)}\") ignored unreachable host" + ) + + nil + + _ -> + nil end rescue e -> -- cgit v1.2.3 From 1403798820da21660fb8787ffaf9f54817597636 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Mon, 7 Dec 2020 21:18:51 +0100 Subject: instance.reachable?: Limit to binary input --- lib/pleroma/instances/instance.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index c9ca3aac7..2e1696fe2 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -77,7 +77,7 @@ defmodule Pleroma.Instances.Instance do ) end - def reachable?(_), do: true + def reachable?(url_or_host) when is_binary(url_or_host), do: true def set_reachable(url_or_host) when is_binary(url_or_host) do with host <- host(url_or_host), -- cgit v1.2.3 From fb3fd692c66ca0f09c25067c2d024157144e1118 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 7 Dec 2020 16:36:44 -0600 Subject: Add a startup error for modified Repo pool_size --- lib/pleroma/application_requirements.ex | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'lib') diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index b977257a3..41f6c6e34 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -24,6 +24,7 @@ defmodule Pleroma.ApplicationRequirements do |> check_migrations_applied!() |> check_welcome_message_config!() |> check_rum!() + |> check_repo_pool_size!() |> handle_result() end @@ -188,6 +189,24 @@ defmodule Pleroma.ApplicationRequirements do defp check_system_commands!(result), do: result + defp check_repo_pool_size!(:ok) do + if Pleroma.Config.get([Pleroma.Repo, :pool_size], 10) != 10 and + not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do + Logger.error(""" + !!!CONFIG WARNING!!! + The database pool size has been altered from the recommended value of 10.\n + Please revert or ensure your database is tuned appropriately and then set\n + `config :pleroma, :dangerzone, override_repo_pool_size: true`. + """) + + {:error, "Repo.pool_size above recommended value."} + else + :ok + end + end + + defp check_repo_pool_size!(result), do: result + defp check_filter(filter, command_required) do filters = Config.get([Pleroma.Upload, :filters]) -- cgit v1.2.3 From 5b9b7b488807e86ecf3c648e8c6a1f1d86bf9806 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 8 Dec 2020 16:16:43 +0000 Subject: Apply 1 suggestion(s) to 1 file(s) --- lib/pleroma/application_requirements.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 41f6c6e34..2c1864ef1 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -199,7 +199,7 @@ defmodule Pleroma.ApplicationRequirements do `config :pleroma, :dangerzone, override_repo_pool_size: true`. """) - {:error, "Repo.pool_size above recommended value."} + {:error, "Repo.pool_size different than recommended value."} else :ok end -- cgit v1.2.3 From 50d16a9e27189800f69901c4e90aa6f41bdf3193 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 8 Dec 2020 17:30:10 +0100 Subject: ApplicationRequirements: Add test, more text for pool size. --- lib/pleroma/application_requirements.ex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 2c1864ef1..e61576644 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -194,9 +194,15 @@ defmodule Pleroma.ApplicationRequirements do not Pleroma.Config.get([:dangerzone, :override_repo_pool_size], false) do Logger.error(""" !!!CONFIG WARNING!!! - The database pool size has been altered from the recommended value of 10.\n - Please revert or ensure your database is tuned appropriately and then set\n + + The database pool size has been altered from the recommended value of 10. + + Please revert or ensure your database is tuned appropriately and then set `config :pleroma, :dangerzone, override_repo_pool_size: true`. + + If you are experiencing database timeouts, please check the "Optimizing + your PostgreSQL performance" section in the documentation. If you still + encounter issues after that, please open an issue on the tracker. """) {:error, "Repo.pool_size different than recommended value."} -- cgit v1.2.3 From 7da0349d731058f00904186ebdab9a7514b37a14 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 9 Dec 2020 19:59:46 +0300 Subject: Changed default OAuth token expiration time to 30 days. --- lib/pleroma/mfa/token.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex index 69b64c0e8..82d3817cc 100644 --- a/lib/pleroma/mfa/token.ex +++ b/lib/pleroma/mfa/token.ex @@ -11,7 +11,7 @@ defmodule Pleroma.MFA.Token do alias Pleroma.User alias Pleroma.Web.OAuth.Authorization - @expires 300 + @expires 3600 * 24 * 30 @type t() :: %__MODULE__{} -- cgit v1.2.3 From 7fff9c1bee009c7b05679ad8bd57de8bcf58e610 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Wed, 9 Dec 2020 21:14:39 +0300 Subject: Tweaks to OAuth entities expiration: changed default to 30 days, removed hardcoded values usage, fixed OAuthView (expires_in). --- lib/pleroma/mfa/token.ex | 2 +- lib/pleroma/web/o_auth/authorization.ex | 4 +++- lib/pleroma/web/o_auth/o_auth_view.ex | 4 +--- lib/pleroma/web/o_auth/token.ex | 12 +++++++----- 4 files changed, 12 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/mfa/token.ex b/lib/pleroma/mfa/token.ex index 82d3817cc..69b64c0e8 100644 --- a/lib/pleroma/mfa/token.ex +++ b/lib/pleroma/mfa/token.ex @@ -11,7 +11,7 @@ defmodule Pleroma.MFA.Token do alias Pleroma.User alias Pleroma.Web.OAuth.Authorization - @expires 3600 * 24 * 30 + @expires 300 @type t() :: %__MODULE__{} diff --git a/lib/pleroma/web/o_auth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex index 268ee5b63..e766dcada 100644 --- a/lib/pleroma/web/o_auth/authorization.ex +++ b/lib/pleroma/web/o_auth/authorization.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.OAuth.Authorization do alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token import Ecto.Changeset import Ecto.Query @@ -53,7 +54,8 @@ defmodule Pleroma.Web.OAuth.Authorization do end defp add_lifetime(changeset) do - put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)) + lifespan = Token.lifespan() + put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan)) end @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t() diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex index f55247ebd..d22b2f7fe 100644 --- a/lib/pleroma/web/o_auth/o_auth_view.ex +++ b/lib/pleroma/web/o_auth/o_auth_view.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.OAuth.OAuthView do token_type: "Bearer", access_token: token.token, refresh_token: token.refresh_token, - expires_in: expires_in(), + expires_in: NaiveDateTime.diff(token.valid_until, NaiveDateTime.utc_now()), scope: Enum.join(token.scopes, " "), created_at: Utils.format_created_at(token) } @@ -25,6 +25,4 @@ defmodule Pleroma.Web.OAuth.OAuthView do response end end - - defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) end diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex index 9170a7ec7..886117d15 100644 --- a/lib/pleroma/web/o_auth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -27,6 +27,10 @@ defmodule Pleroma.Web.OAuth.Token do timestamps() end + def lifespan do + Pleroma.Config.get!([:oauth2, :token_expires_in]) + end + @doc "Gets token by unique access token" @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(token) do @@ -83,11 +87,11 @@ defmodule Pleroma.Web.OAuth.Token do end defp put_valid_until(changeset, attrs) do - expires_in = - Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in())) + valid_until = + Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan())) changeset - |> change(%{valid_until: expires_in}) + |> change(%{valid_until: valid_until}) |> validate_required([:valid_until]) end @@ -138,6 +142,4 @@ defmodule Pleroma.Web.OAuth.Token do end def is_expired?(_), do: false - - defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) end -- cgit v1.2.3 From 6aece536eb394fd82e1368e7ae3e484959d05d8c Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 12 Dec 2020 20:35:38 +0300 Subject: instance.gen task: Only show files which will be actually overwritten --- lib/mix/tasks/pleroma/instance.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index ac8688424..a4f1c81bc 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -253,7 +253,7 @@ defmodule Mix.Tasks.Pleroma.Instance do else shell_error( "The task would have overwritten the following files:\n" <> - (Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <> + (Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <> "Rerun with `--force` to overwrite them." ) end -- cgit v1.2.3 From 7133c0c5ea7f9966d92e53acb52429746fbe51e6 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 12 Dec 2020 20:37:14 +0300 Subject: instance.gen: Warn that stripping exif requires exiftool And default to no if it is not installed Closes #2343 --- lib/mix/tasks/pleroma/instance.ex | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index a4f1c81bc..853c4eaa2 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -161,12 +161,21 @@ defmodule Mix.Tasks.Pleroma.Instance do ) |> Path.expand() + {strip_uploads_message, strip_uploads_default} = + if Pleroma.Utils.command_available?("exiftool") do + {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)", + "y"} + else + {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)", + "n"} + end + strip_uploads = get_option( options, :strip_uploads, - "Do you want to strip location (GPS) data from uploaded images? (y/n)", - "y" + strip_uploads_message, + strip_uploads_default ) === "y" anonymize_uploads = -- cgit v1.2.3 From cebe3c7deff87ba24f43efcf50499c45d3b3e3f9 Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Sat, 12 Dec 2020 17:30:08 +0300 Subject: Fix for dropping posts/notifs in WS when mix task is executed - start oban in mix tasks with empty queues, plugins and crontab - fix for update_users_following_followers_counts - fix for removed logo.png - typo in resend confirmation emails mix task docs - fix for uploads mix task (start Majic.Pool) - fix for creating user mix task (start :fast_html app) --- lib/mix/pleroma.ex | 16 ++++++++++++++-- lib/mix/tasks/pleroma/database.ex | 12 +++++++++--- lib/pleroma/emails/user_email.ex | 4 ++-- lib/pleroma/web/feed/feed_view.ex | 2 +- lib/pleroma/web/templates/email/digest.html.eex | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) (limited to 'lib') diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 7575f0ef8..a33a9951c 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -12,7 +12,8 @@ defmodule Mix.Pleroma do :cachex, :flake_id, :swoosh, - :timex + :timex, + :fast_html ] @cachex_children ["object", "user", "scrubber", "web_resp"] @doc "Common functions to be reused in mix tasks" @@ -37,12 +38,23 @@ defmodule Mix.Pleroma do Enum.each(apps, &Application.ensure_all_started/1) + oban_config = [ + crontab: [], + repo: Pleroma.Repo, + log: false, + queues: [], + plugins: [] + ] + children = [ Pleroma.Repo, + Pleroma.Emoji, {Pleroma.Config.TransferTask, false}, Pleroma.Web.Endpoint, - {Oban, Pleroma.Config.get(Oban)} + {Oban, oban_config}, + {Majic.Pool, + [name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]} ] ++ http_children(adapter) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index a01c36ece..22151ce08 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -48,9 +48,15 @@ defmodule Mix.Tasks.Pleroma.Database do def run(["update_users_following_followers_counts"]) do start_pleroma() - User - |> Repo.all() - |> Enum.each(&User.update_follower_count/1) + Repo.transaction( + fn -> + from(u in User, select: u) + |> Repo.stream() + |> Stream.each(&User.update_follower_count/1) + |> Stream.run() + end, + timeout: :infinity + ) end def run(["prune_objects" | args]) do diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 806a61fd2..e6829b862 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -151,7 +151,7 @@ defmodule Pleroma.Emails.UserEmail do logo_path = if is_nil(logo) do - Path.join(:code.priv_dir(:pleroma), "static/static/logo.png") + Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg") else Path.join(Config.get([:instance, :static_dir]), logo) end @@ -162,7 +162,7 @@ defmodule Pleroma.Emails.UserEmail do |> subject("Your digest from #{instance_name()}") |> put_layout(false) |> render_body("digest.html", html_data) - |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline)) + |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline)) end end diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 56c024617..30e0a2a55 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Web.Feed.FeedView do def feed_logo do case Pleroma.Config.get([:feed, :logo]) do nil -> - "#{Pleroma.Web.base_url()}/static/logo.png" + "#{Pleroma.Web.base_url()}/static/logo.svg" logo -> "#{Pleroma.Web.base_url()}#{logo}" diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex index 860df5f9c..60eceff22 100644 --- a/lib/pleroma/web/templates/email/digest.html.eex +++ b/lib/pleroma/web/templates/email/digest.html.eex @@ -126,7 +126,7 @@
Image -- cgit v1.2.3 From 6dac2ac71a0005419d1440b5e5daeab3aaabf889 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 14 Dec 2020 13:27:42 -0600 Subject: Minor refactoring of the logic for hiding followers/following counts. Field is not nullable anymore, and this is more readable. --- lib/pleroma/web/mastodon_api/views/account_view.ex | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 3158d09ed..026ae9458 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -187,18 +187,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = - if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do - user.following_count || 0 - else - 0 - end + if !user.hide_follows_count or !user.hide_follows or opts[:for] == user, + do: user.following_count, + else: 0 followers_count = - if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do - user.follower_count || 0 - else - 0 - end + if !user.hide_followers_count or !user.hide_followers or opts[:for] == user, + do: user.follower_count, + else: 0 bot = user.actor_type == "Service" -- cgit v1.2.3