From 6e1ec4c5da6da4bd301080a8c35f8483d89e095f Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 26 Aug 2019 16:29:51 -0500 Subject: [PATCH 01/28] ActivityPub: Basic EmojiReactions. --- lib/pleroma/web/activity_pub/activity_pub.ex | 10 +++++++++ lib/pleroma/web/activity_pub/utils.ex | 10 +++++++++ test/web/activity_pub/activity_pub_test.exs | 22 ++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 172c952d4..a6088a012 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -312,6 +312,16 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do end end + def react_with_emoji(user, object, emoji, options \\ []) do + with local <- Keyword.get(options, :local, true), + activity_id <- Keyword.get(options, :activity_id, nil), + is_emoji?(emoji), + reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), + {:ok, activity} <- insert(reaction_data, local) do + {:ok, activity, object} + end + end + # TODO: This is weird, maybe we shouldn't check here if we can make the activity. def like( %User{ap_id: ap_id} = user, diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 1c3058658..d57830da3 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -296,6 +296,16 @@ def get_object_likes(%{data: %{"id" => id}}) do Repo.all(query) end + def is_emoji?(emoji) do + String.length(emoji) == 1 + end + + def make_emoji_reaction_data(user, object, emoji, activity_id) do + make_like_data(user, object, activity_id) + |> Map.put("type", "EmojiReaction") + |> Map.put("content", emoji) + end + def make_like_data( %User{ap_id: ap_id} = actor, %{data: %{"actor" => object_actor_id, "id" => id}} = object, diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 1515f4eb6..cf93f624a 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -675,6 +675,28 @@ test "returns reblogs for users for whom reblogs have not been muted" do end end + describe "react to an object" do + test "adds an emoji reaction activity to the db" do + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "๐Ÿ”ฅ") + + assert reaction_activity + + assert reaction_activity.data["actor"] == reactor.ap_id + assert reaction_activity.data["type"] == "EmojiReaction" + assert reaction_activity.data["content"] == "๐Ÿ”ฅ" + assert reaction_activity.data["object"] == object.data["id"] + assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]] + assert reaction_activity.data["context"] == object.data["context"] + # assert object.data["reaction_count"] == 1 + # assert object.data["reactions"] == [user.ap_id] + end + end + describe "like an object" do test "adds a like activity to the db" do note_activity = insert(:note_activity) From a0b21c89284304bea90f2774f17d5b2b7b3c1359 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 26 Aug 2019 16:47:31 -0500 Subject: [PATCH 02/28] Transmogrifier: Handle incoming emoji reactions. --- .../web/activity_pub/transmogrifier.ex | 21 +++++++++++++ test/fixtures/emoji-reaction.json | 30 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 18 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 test/fixtures/emoji-reaction.json diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 36340a3a1..9132df8cb 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -572,6 +572,27 @@ def handle_incoming( end end + def handle_incoming( + %{ + "type" => "EmojiReaction", + "object" => object_id, + "actor" => _actor, + "id" => id, + "content" => emoji + } = data, + _options + ) do + with actor <- Containment.get_actor(data), + {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), + {:ok, object} <- get_obj_helper(object_id), + {:ok, activity, _object} <- + ActivityPub.react_with_emoji(actor, object, emoji, activity_id: id, local: false) do + {:ok, activity} + else + _e -> :error + end + end + def handle_incoming( %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data, _options diff --git a/test/fixtures/emoji-reaction.json b/test/fixtures/emoji-reaction.json new file mode 100644 index 000000000..3812e43ad --- /dev/null +++ b/test/fixtures/emoji-reaction.json @@ -0,0 +1,30 @@ +{ + "type": "EmojiReaction", + "signature": { + "type": "RsaSignature2017", + "signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==", + "creator": "http://mastodon.example.org/users/admin#main-key", + "created": "2018-02-17T18:57:49Z" + }, + "object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454", + "content": "๐Ÿ‘Œ", + "nickname": "lain", + "id": "http://mastodon.example.org/users/admin#reactions/2", + "actor": "http://mastodon.example.org/users/admin", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "toot": "http://joinmastodon.org/ns#", + "sensitive": "as:sensitive", + "ostatus": "http://ostatus.org#", + "movedTo": "as:movedTo", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "atomUri": "ostatus:atomUri", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji" + } + ] +} diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 0661d5d7c..6df707370 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -311,6 +311,24 @@ test "it works for incoming likes" do assert data["object"] == activity.data["object"] end + test "it works for incoming emoji reactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "EmojiReaction" + assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" + assert data["object"] == activity.data["object"] + assert data["content"] == "๐Ÿ‘Œ" + end + test "it returns an error for incoming unlikes wihout a like activity" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) From b770ed1d9940230d4bd97113abdc220ca7d8eb1a Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 27 Aug 2019 17:56:28 -0500 Subject: [PATCH 03/28] CommonAPI: Support emoji reactions. --- lib/pleroma/web/common_api/common_api.ex | 10 ++++++++++ test/web/common_api/common_api_test.exs | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 5faddc9f4..3e1aa4818 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -123,6 +123,16 @@ def unfavorite(id_or_ap_id, user) do end end + def react_with_emoji(id, user, emoji) do + with %Activity{} = activity <- Activity.get_by_id(id), + object <- Object.normalize(activity) do + ActivityPub.react_with_emoji(user, object, emoji) + else + _ -> + {:error, dgettext("errors", "Could not add reaction emoji")} + end + end + def vote(user, object, choices) do with "Question" <- object.data["type"], {:author, false} <- {:author, object.data["actor"] == user.ap_id}, diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index f28a66090..7cb1202fc 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -222,6 +222,20 @@ test "it can handle activities that expire" do end describe "reactions" do + test "reacting to a status with an emoji" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "๐Ÿ‘") + + assert reaction.data["actor"] == user.ap_id + assert reaction.data["content"] == "๐Ÿ‘" + + # TODO: test error case. + end + test "repeating a status" do user = insert(:user) other_user = insert(:user) From 9bc12b88b3b4b90ee1b55ebf49a13665a751ef1a Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 3 Sep 2019 16:50:04 -0500 Subject: [PATCH 04/28] ActivityPub: Save emoji reactions in object. --- lib/pleroma/web/activity_pub/activity_pub.ex | 3 ++- lib/pleroma/web/activity_pub/utils.ex | 27 +++++++++++++++++++- test/web/activity_pub/activity_pub_test.exs | 4 +-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 532db17c4..6cd168427 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -317,7 +317,8 @@ def react_with_emoji(user, object, emoji, options \\ []) do activity_id <- Keyword.get(options, :activity_id, nil), is_emoji?(emoji), reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), - {:ok, activity} <- insert(reaction_data, local) do + {:ok, activity} <- insert(reaction_data, local), + {:ok, object} <- add_emoji_reaction_to_object(activity, object) do {:ok, activity, object} end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 32e22d8d7..1e6a67deb 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -321,10 +321,21 @@ def make_like_data( @spec update_element_in_object(String.t(), list(any), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def update_element_in_object(property, element, object) do + length = + if is_map(element) do + element + |> Map.values() + |> List.flatten() + |> length() + else + element + |> length() + end + data = Map.merge( object.data, - %{"#{property}_count" => length(element), "#{property}s" => element} + %{"#{property}_count" => length, "#{property}s" => element} ) object @@ -332,6 +343,20 @@ def update_element_in_object(property, element, object) do |> Object.update_and_set_cache() end + @spec add_emoji_reaction_to_object(Activity.t(), Object.t()) :: + {:ok, Object.t()} | {:error, Ecto.Changeset.t()} + + def add_emoji_reaction_to_object( + %Activity{data: %{"content" => emoji, "actor" => actor}}, + object + ) do + reactions = object.data["reactions"] || %{} + emoji_actors = reactions[emoji] || [] + new_emoji_actors = [actor | emoji_actors] |> Enum.uniq() + new_reactions = Map.put(reactions, emoji, new_emoji_actors) + update_element_in_object("reaction", new_reactions, object) + end + @spec add_like_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index b168e85a0..67dfb9394 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -694,8 +694,8 @@ test "adds an emoji reaction activity to the db" do assert reaction_activity.data["object"] == object.data["id"] assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]] assert reaction_activity.data["context"] == object.data["context"] - # assert object.data["reaction_count"] == 1 - # assert object.data["reactions"] == [user.ap_id] + assert object.data["reaction_count"] == 1 + assert object.data["reactions"]["๐Ÿ”ฅ"] == [reactor.ap_id] end end From 99ea990a16a417cd316b3464ef380746171ecb55 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 4 Sep 2019 12:20:35 -0500 Subject: [PATCH 05/28] PleromaAPIController: Add emoji reactions. --- .../web/pleroma_api/pleroma_api_controller.ex | 11 +++++++++++ lib/pleroma/web/router.ex | 1 + .../pleroma_api/pleroma_api_controller_test.exs | 15 +++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index b6d2bf86b..740ea4747 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -7,11 +7,22 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] + alias Pleroma.Activity alias Pleroma.Conversation.Participation alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.StatusView + def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do + with {:ok, _activity, _object} <- CommonAPI.react_with_emoji(activity_id, user, emoji), + activity = Activity.get_by_id(activity_id) do + conn + |> put_view(StatusView) + |> render("status.json", %{activity: activity, for: user, as: :activity}) + end + end + def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do with %Participation{} = participation <- Participation.get(participation_id), true <- user.id == participation.user_id do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 969dc66fd..6cca54211 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -277,6 +277,7 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) patch("/conversations/:id", PleromaAPIController, :update_conversation) + post("/statuses/:id/react_with_emoji", PleromaAPIController, :react_with_emoji) end end diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs index ed6b79727..aab0c774e 100644 --- a/test/web/pleroma_api/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/pleroma_api_controller_test.exs @@ -11,6 +11,21 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do import Pleroma.Factory + test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + + result = + conn + |> assign(:user, other_user) + |> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "โ˜•"}) + + assert %{"id" => id} = json_response(result, 200) + assert to_string(activity.id) == id + end + test "/api/v1/pleroma/conversations/:id", %{conn: conn} do user = insert(:user) other_user = insert(:user) From 05e9776517498370ab8f7b7afa0408f6ee979844 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 12 Sep 2019 18:48:25 +0200 Subject: [PATCH 06/28] PleromaAPIController: Add endpoint to fetch emoji reactions. --- .../web/pleroma_api/pleroma_api_controller.ex | 28 +++++++++++++++++++ lib/pleroma/web/router.ex | 6 ++++ .../pleroma_api_controller_test.exs | 25 +++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index 740ea4747..bb090d37f 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -8,12 +8,40 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User alias Pleroma.Conversation.Participation alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.StatusView + def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do + with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), + %Object{data: %{"reactions" => emoji_reactions}} <- Object.normalize(activity) do + reactions = + Enum.reduce(emoji_reactions, %{}, fn {emoji, users}, res -> + users = + users + |> Enum.map(&User.get_cached_by_ap_id/1) + + res + |> Map.put( + emoji, + AccountView.render("accounts.json", %{users: users, for: user, as: :user}) + ) + end) + + conn + |> json(reactions) + else + _e -> + conn + |> json(%{}) + end + end + def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do with {:ok, _activity, _object} <- CommonAPI.react_with_emoji(activity_id, user, emoji), activity = Activity.get_by_id(activity_id) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6cca54211..ec6179420 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -265,6 +265,12 @@ defmodule Pleroma.Web.Router do end end + scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do + pipe_through(:api) + + get("/statuses/:id/emoji_reactions_by", PleromaAPIController, :emoji_reactions_by) + end + scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:authenticated_api) diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs index aab0c774e..71e4d3e1c 100644 --- a/test/web/pleroma_api/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/pleroma_api_controller_test.exs @@ -8,6 +8,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do alias Pleroma.Conversation.Participation alias Pleroma.Repo alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.AccountView import Pleroma.Factory @@ -26,6 +27,30 @@ test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do assert to_string(activity.id) == id end + test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") + |> json_response(200) + + assert result == %{} + + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "๐ŸŽ…") + + result = + conn + |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") + |> json_response(200) + + [represented_user] = result["๐ŸŽ…"] + assert represented_user["id"] == other_user.id + end + test "/api/v1/pleroma/conversations/:id", %{conn: conn} do user = insert(:user) other_user = insert(:user) From 8d4b661ecb549ba554815ca55a29ec5872c68380 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 12 Sep 2019 18:59:13 +0200 Subject: [PATCH 07/28] Transmogrifier: Strip internal emoji reaction fields. --- lib/pleroma/web/activity_pub/transmogrifier.ex | 4 +++- test/web/activity_pub/transmogrifier_test.exs | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 9132df8cb..0b9cc4499 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -995,9 +995,11 @@ def prepare_attachments(object) do |> Map.put("attachment", attachments) end - defp strip_internal_fields(object) do + def strip_internal_fields(object) do object |> Map.drop([ + "reactions", + "reaction_count", "likes", "like_count", "announcements", diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6df707370..20d274a02 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -491,6 +491,20 @@ test "it strips internal likes" do refute Map.has_key?(object.data, "likes") end + test "it strips internal reactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "๐Ÿ“ข") + + %{object: object} = Activity.get_by_id_with_object(activity.id) + assert Map.has_key?(object.data, "reactions") + assert Map.has_key?(object.data, "reaction_count") + + object_data = Transmogrifier.strip_internal_fields(object.data) + refute Map.has_key?(object_data, "reactions") + refute Map.has_key?(object_data, "reaction_count") + end + test "it works for incoming update activities" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() From a697f0d79148358da828d24ebfe12bbb9bb33b34 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 13 Sep 2019 02:11:02 +0200 Subject: [PATCH 08/28] Emoji: Add function to detect if a character is an emoji --- lib/pleroma/emoji-data.txt | 769 +++++++++++++++++++++++++++++++++++++ lib/pleroma/emoji.ex | 25 ++ test/emoji_test.exs | 8 + 3 files changed, 802 insertions(+) create mode 100644 lib/pleroma/emoji-data.txt diff --git a/lib/pleroma/emoji-data.txt b/lib/pleroma/emoji-data.txt new file mode 100644 index 000000000..2fb5c3ff6 --- /dev/null +++ b/lib/pleroma/emoji-data.txt @@ -0,0 +1,769 @@ +# 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 diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 66e20f0e4..3f5169007 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -260,4 +260,29 @@ defp match_group_patterns(group_patterns, matcher) do Enum.any?(patterns, matcher) && group end) end + + {:ok, file} = File.read("lib/pleroma/emoji-data.txt") + + @unicode_emoji file + |> String.split("\n") + |> Enum.filter(fn line -> String.starts_with?(line, ["1", "2", "3", "4"]) end) + |> Enum.map(fn line -> String.split(line) |> List.first() end) + |> Enum.map(fn line -> + case String.split(line, "..") do + [number] -> + String.to_integer(number, 16) + + [first, last] -> + Range.new(String.to_integer(first, 16), String.to_integer(last, 16)) + |> Enum.to_list() + end + end) + |> List.flatten() + |> Enum.filter(&is_integer/1) + |> Enum.uniq() + |> Enum.map(fn n -> :unicode.characters_to_binary([n], :utf32) end) + + def is_unicode_emoji?(str) do + str in @unicode_emoji + end end diff --git a/test/emoji_test.exs b/test/emoji_test.exs index 07ac6ff1d..467357291 100644 --- a/test/emoji_test.exs +++ b/test/emoji_test.exs @@ -6,6 +6,14 @@ defmodule Pleroma.EmojiTest do use ExUnit.Case, async: true alias Pleroma.Emoji + describe "is_unicode_emoji?/1" do + test "tells if a string is an unicode emoji" do + refute Emoji.is_unicode_emoji?("X") + assert Emoji.is_unicode_emoji?("โ˜‚") + assert Emoji.is_unicode_emoji?("๐Ÿฅบ") + end + end + describe "get_all/0" do setup do emoji_list = Emoji.get_all() From e5b3ad3d049a7c665285f724c53f6cafb0e10118 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 13 Sep 2019 16:06:34 +0200 Subject: [PATCH 09/28] ActivityPub: Use is_unicode_emoji? function. --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/utils.ex | 4 ---- test/web/pleroma_api/pleroma_api_controller_test.exs | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 6cd168427..4ee9b1885 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -315,7 +315,7 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do def react_with_emoji(user, object, emoji, options \\ []) do with local <- Keyword.get(options, :local, true), activity_id <- Keyword.get(options, :activity_id, nil), - is_emoji?(emoji), + Pleroma.Emoji.is_unicode_emoji?(emoji), reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), {:ok, activity} <- insert(reaction_data, local), {:ok, object} <- add_emoji_reaction_to_object(activity, object) do diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 1e6a67deb..95e040c6c 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -277,10 +277,6 @@ def get_object_likes(%{data: %{"id" => id}}) do |> Repo.all() end - def is_emoji?(emoji) do - String.length(emoji) == 1 - end - def make_emoji_reaction_data(user, object, emoji, activity_id) do make_like_data(user, object, activity_id) |> Map.put("type", "EmojiReaction") diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs index 71e4d3e1c..fa85d9174 100644 --- a/test/web/pleroma_api/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/pleroma_api_controller_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do alias Pleroma.Conversation.Participation alias Pleroma.Repo alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MastodonAPI.AccountView import Pleroma.Factory From f649a2e972b70dfefb7bfc110b27a0194cda51c5 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 13 Sep 2019 16:19:50 +0200 Subject: [PATCH 10/28] Pleroma API Docs: Documented Emoji reaction endpoints. --- docs/api/pleroma_api.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index b134b31a8..c846df1da 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -354,3 +354,28 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa * Params: * `recipients`: A list of ids of users that should receive posts to this conversation. This will replace the current list of recipients, so submit the full list. The owner of owner of the conversation will always be part of the set of recipients, though. * Response: JSON, statuses (200 - healthy, 503 unhealthy) + +# Emoji Reactions + +Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. + +## `POST /api/v1/pleroma/statuses/:id/react_with_emoji` +### React to a post with a unicode emoji +* Method: `POST` +* Authentication: required +* Params: `emoji`: A single character unicode emoji +* Response: JSON, the status. + +## `GET /api/v1/pleroma/statuses/:id/emoji_reactions_by` +### Get an object of emoji to account mappings with accounts that reacted to the post +* Method: `GET` +* Authentication: optional +* Params: None +* Response: JSON, a map of emoji to account list mappings. +* Example Response: +```json +{ + "๐Ÿ˜€" => [{"id" => "xyz.."...}, {"id" => "zyx..."}], + "๐Ÿ—ก" => [{"id" => "abc..."}] +} +``` From 3ff55322201fa7f6b22368988095963fcb82d6e4 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 13 Sep 2019 16:41:30 +0200 Subject: [PATCH 11/28] Linting. --- lib/pleroma/web/pleroma_api/pleroma_api_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index 110240115..d41091d93 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -8,10 +8,10 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.User alias Pleroma.Conversation.Participation alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView From 6fe2f554c36be1ef03ac1d1104a78d0686f48a26 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 13 Sep 2019 18:27:32 +0200 Subject: [PATCH 12/28] Emoji: Generate emoji detecting functions at compile time. Suggested by jvalim --- lib/pleroma/emoji.ex | 46 +++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 3f5169007..65f2d5f39 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -261,28 +261,34 @@ defp match_group_patterns(group_patterns, matcher) do end) end - {:ok, file} = File.read("lib/pleroma/emoji-data.txt") + @external_resource "lib/pleroma/emoji-data.txt" - @unicode_emoji file - |> String.split("\n") - |> Enum.filter(fn line -> String.starts_with?(line, ["1", "2", "3", "4"]) end) - |> Enum.map(fn line -> String.split(line) |> List.first() end) - |> Enum.map(fn line -> - case String.split(line, "..") do - [number] -> - String.to_integer(number, 16) + emojis = + @external_resource + |> File.read!() + |> String.split("\n") + |> Enum.filter(fn line -> line != "" and not String.starts_with?(line, "#") end) + |> Enum.map(fn line -> + line + |> String.split(";", parts: 2) + |> hd() + |> String.trim() + |> String.split("..") + |> case do + [number] -> + <> - [first, last] -> - Range.new(String.to_integer(first, 16), String.to_integer(last, 16)) - |> Enum.to_list() - end - end) - |> List.flatten() - |> Enum.filter(&is_integer/1) - |> Enum.uniq() - |> Enum.map(fn n -> :unicode.characters_to_binary([n], :utf32) end) + [first, last] -> + String.to_integer(first, 16)..String.to_integer(last, 16) + |> Enum.map(&<<&1::utf8>>) + end + end) + |> List.flatten() + |> Enum.uniq() - def is_unicode_emoji?(str) do - str in @unicode_emoji + for emoji <- emojis do + def is_unicode_emoji?(unquote(emoji)), do: true end + + def is_unicode_emoji?(_), do: false end From 0aef18bcf4896adbd6bb7ea59e9399cb09dbfa3c Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 30 Sep 2019 14:26:59 +0200 Subject: [PATCH 13/28] Litepub Context: Add EmojiReaction. --- priv/static/schemas/litepub-0.1.jsonld | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index f01c2c33a..5e44ad819 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -32,7 +32,8 @@ "uploadMedia": { "@id": "litepub:uploadMedia", "@type": "@id" - } + }, + "EmojiReaction": "litepub:EmojiReaction" } ] } From 04a2910f33405db368687f8749b405eeac06df63 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 30 Sep 2019 15:13:05 +0200 Subject: [PATCH 14/28] Pleroma.Constants: Fix typo. --- lib/pleroma/constants.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 7d775c3c2..1a432e681 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Constants do const(object_internal_fields, do: [ "reactions", - "reactions_count", + "reaction_count", "likes", "like_count", "announcements", From 6068d2254e2ed00260dc840f18824dc0e0835afa Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 30 Sep 2019 15:13:25 +0200 Subject: [PATCH 15/28] PleromaAPIController: Fixes and refactoring. --- .../controllers/pleroma_api_controller.ex | 17 ++++++----------- test/web/activity_pub/transmogrifier_test.exs | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 474b8d079..39d371ff7 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -23,17 +23,12 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), %Object{data: %{"reactions" => emoji_reactions}} <- Object.normalize(activity) do reactions = - Enum.reduce(emoji_reactions, %{}, fn {emoji, users}, res -> - users = - users - |> Enum.map(&User.get_cached_by_ap_id/1) - - res - |> Map.put( - emoji, - AccountView.render("accounts.json", %{users: users, for: user, as: :user}) - ) + emoji_reactions + |> Enum.map(fn {emoji, users} -> + users = Enum.map(users, &User.get_cached_by_ap_id/1) + {emoji, AccountView.render("accounts.json", %{users: users, for: user, as: :user})} end) + |> Enum.into(%{}) conn |> json(reactions) @@ -49,7 +44,7 @@ def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "e activity = Activity.get_by_id(activity_id) do conn |> put_view(StatusView) - |> render("status.json", %{activity: activity, for: user, as: :activity}) + |> render("show.json", %{activity: activity, for: user, as: :activity}) end end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index ba2a43296..f1ceb20d2 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1263,7 +1263,7 @@ test "it can handle Listen activities" do {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data) end end From 08256e9299494c0bcd1a295c6079263277b21ba7 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 30 Sep 2019 15:51:09 +0200 Subject: [PATCH 16/28] ActivityPub: Federate reactions. --- lib/pleroma/web/activity_pub/activity_pub.ex | 3 ++- test/web/activity_pub/activity_pub_test.exs | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 77fa23db4..a6fb67a28 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -313,7 +313,8 @@ def react_with_emoji(user, object, emoji, options \\ []) do Pleroma.Emoji.is_unicode_emoji?(emoji), reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), {:ok, activity} <- insert(reaction_data, local), - {:ok, object} <- add_emoji_reaction_to_object(activity, object) do + {:ok, object} <- add_emoji_reaction_to_object(activity, object), + :ok <- maybe_federate(activity) do {:ok, activity, object} end end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 3e6f389bc..4dc7d96d2 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -727,6 +727,18 @@ test "returns reblogs for users for whom reblogs have not been muted" do end describe "react to an object" do + test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do + Pleroma.Config.put([:instance, :federating], true) + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "๐Ÿ”ฅ") + + assert called(Pleroma.Web.Federator.publish(reaction_activity)) + end + test "adds an emoji reaction activity to the db" do user = insert(:user) reactor = insert(:user) From 19bc0b8c79765dc485e081651a4e4c589d18b970 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 30 Sep 2019 16:38:19 +0200 Subject: [PATCH 17/28] . --- lib/pleroma/web/activity_pub/activity_pub.ex | 7 +++++++ test/web/activity_pub/activity_pub_test.exs | 14 ++++++++++++++ test/web/common_api/common_api_test.exs | 10 ++++++++++ .../pleroma_api/pleroma_api_controller_test.exs | 16 ++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a6fb67a28..1f201d587 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -319,6 +319,13 @@ def react_with_emoji(user, object, emoji, options \\ []) do end end + def unreact_with_emoji(user, reaction_id, option \\ []) do + with local <- Keyword.get(options, :local, true), + activity_id <- Keyword.get(options, :activity_id, nil), + %Activity{actor: ^user.ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id), + unreact_data + end + # TODO: This is weird, maybe we shouldn't check here if we can make the activity. def like( %User{ap_id: ap_id} = user, diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 4dc7d96d2..5a6464350 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -760,6 +760,20 @@ test "adds an emoji reaction activity to the db" do end end + describe "unreacting to an object" do + test "adds an emoji reaction activity to the db" do + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "๐Ÿ”ฅ") + {:ok, unreaction_activity} = ActivityPub.unreact_with_emoji(reactor, reaction_activity.id) + + IO.inspect(object) + end + end + describe "like an object" do test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do Pleroma.Config.put([:instance, :federating], true) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 011fcfca9..9e3bf6cc8 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -236,6 +236,16 @@ test "reacting to a status with an emoji" do # TODO: test error case. end + test "unreacting to a status with an emoji" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "๐Ÿ‘") + + assert false + end + test "repeating a status" do user = insert(:user) other_user = insert(:user) diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/pleroma_api_controller_test.exs index 3c2a087ca..82d23ea5b 100644 --- a/test/web/pleroma_api/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/pleroma_api_controller_test.exs @@ -27,6 +27,22 @@ test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do assert to_string(activity.id) == id end + test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "โ˜•") + + result = + conn + |> assign(:user, other_user) + |> post("/api/v1/pleroma/statuses/#{activity.id}/unreact_with_emoji", %{"emoji" => "โ˜•"}) + + assert %{"id" => id} = json_response(result, 200) + assert to_string(activity.id) == id + end + test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do user = insert(:user) other_user = insert(:user) From dfe5c958eb94326f5a7743c9de0de073260db926 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Oct 2019 15:08:20 +0200 Subject: [PATCH 18/28] ActivityPub: Add undo for emoji reactions. --- lib/pleroma/web/activity_pub/activity_pub.ex | 13 +++++-- lib/pleroma/web/activity_pub/utils.ex | 37 ++++++++++++++++++++ lib/pleroma/web/router.ex | 2 -- test/web/activity_pub/activity_pub_test.exs | 32 ++++++++++++++--- 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a9e53141d..458d3590d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -319,11 +319,18 @@ def react_with_emoji(user, object, emoji, options \\ []) do end end - def unreact_with_emoji(user, reaction_id, option \\ []) do + def unreact_with_emoji(user, reaction_id, options \\ []) do with local <- Keyword.get(options, :local, true), activity_id <- Keyword.get(options, :activity_id, nil), - %Activity{actor: ^user.ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id), - unreact_data + user_ap_id <- user.ap_id, + %Activity{actor: ^user_ap_id} = reaction_activity <- Activity.get_by_ap_id(reaction_id), + object <- Object.normalize(reaction_activity), + unreact_data <- make_undo_data(user, reaction_activity, activity_id), + {:ok, activity} <- insert(unreact_data, local), + {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object), + :ok <- maybe_federate(activity) do + {:ok, activity, object} + end end # TODO: This is weird, maybe we shouldn't check here if we can make the activity. diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 4c146fd86..7807fa9cd 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -337,6 +337,24 @@ def add_emoji_reaction_to_object( update_element_in_object("reaction", new_reactions, object) end + def remove_emoji_reaction_from_object( + %Activity{data: %{"content" => emoji, "actor" => actor}}, + object + ) do + reactions = object.data["reactions"] || %{} + emoji_actors = reactions[emoji] || [] + new_emoji_actors = List.delete(emoji_actors, actor) + + new_reactions = + if new_emoji_actors == [] do + Map.delete(reactions, emoji) + else + Map.put(reactions, emoji, new_emoji_actors) + end + + update_element_in_object("reaction", new_reactions, object) + end + @spec add_like_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do @@ -522,6 +540,25 @@ def make_unlike_data( |> maybe_put("id", activity_id) end + def make_undo_data( + %User{ap_id: actor, follower_address: follower_address}, + %Activity{ + data: %{"id" => undone_activity_id, "context" => context}, + actor: undone_activity_actor + }, + activity_id \\ nil + ) do + %{ + "type" => "Undo", + "actor" => actor, + "object" => undone_activity_id, + "to" => [follower_address, undone_activity_actor], + "cc" => [Pleroma.Constants.as_public()], + "context" => context + } + |> maybe_put("id", activity_id) + end + @spec add_announce_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_announce_to_object( diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index eae1f676b..ff3dc273e 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -293,8 +293,6 @@ defmodule Pleroma.Web.Router do end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do - pipe_through(:authenticated_api) - scope [] do pipe_through(:authenticated_api) pipe_through(:oauth_read) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 8c9c2c89e..36a82d6a1 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -761,16 +761,40 @@ test "adds an emoji reaction activity to the db" do end describe "unreacting to an object" do - test "adds an emoji reaction activity to the db" do + test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do + Pleroma.Config.put([:instance, :federating], true) user = insert(:user) reactor = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) assert object = Object.normalize(activity) - {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "๐Ÿ”ฅ") - {:ok, unreaction_activity} = ActivityPub.unreact_with_emoji(reactor, reaction_activity.id) + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "๐Ÿ”ฅ") - IO.inspect(object) + assert called(Pleroma.Web.Federator.publish(reaction_activity)) + + {:ok, unreaction_activity, _object} = + ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) + + assert called(Pleroma.Web.Federator.publish(unreaction_activity)) + end + + test "adds an undo activity to the db" do + user = insert(:user) + reactor = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) + assert object = Object.normalize(activity) + + {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "๐Ÿ”ฅ") + + {:ok, unreaction_activity, _object} = + ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) + + assert unreaction_activity.actor == reactor.ap_id + assert unreaction_activity.data["object"] == reaction_activity.data["id"] + + object = Object.get_by_ap_id(object.data["id"]) + assert object.data["reaction_count"] == 0 + assert object.data["reactions"] == %{} end end From 9cfe9a57c5a31318abf02fa510d4fdf78505bd97 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Oct 2019 15:38:57 +0200 Subject: [PATCH 19/28] CommonAPI: Add unreactions. --- lib/pleroma/web/activity_pub/utils.ex | 13 +++++++++++++ lib/pleroma/web/common_api/common_api.ex | 9 +++++++++ test/web/common_api/common_api_test.exs | 5 ++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 7807fa9cd..82e2d8f76 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -452,6 +452,19 @@ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do |> Repo.one() end + def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do + %{data: %{"object" => object_ap_id}} = Activity.get_by_id(internal_activity_id) + + "EmojiReaction" + |> Activity.Queries.by_type() + |> where(actor: ^ap_id) + |> where([activity], fragment("?->>'content' = ?", activity.data, ^emoji)) + |> Activity.Queries.by_object_id(object_ap_id) + |> order_by([activity], fragment("? desc nulls last", activity.id)) + |> limit(1) + |> Repo.one() + end + #### Announce-related helpers @doc """ diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 53ada8fab..995d4b1af 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -125,6 +125,15 @@ def react_with_emoji(id, user, emoji) do end end + def unreact_with_emoji(id, user, emoji) do + with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji) do + ActivityPub.unreact_with_emoji(user, reaction_activity.data["id"]) + else + _ -> + {:error, dgettext("errors", "Could not remove reaction emoji")} + end + end + def vote(user, %{data: %{"type" => "Question"}} = object, choices) do with :ok <- validate_not_author(object, user), :ok <- validate_existing_votes(user, object), diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index e048ed217..b9e785885 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -243,7 +243,10 @@ test "unreacting to a status with an emoji" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "๐Ÿ‘") - assert false + {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "๐Ÿ‘") + + assert unreaction.data["type"] == "Undo" + assert unreaction.data["object"] == reaction.data["id"] end test "repeating a status" do From 391c7362921ba94a0084b7a25c4126ce7026dc55 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Oct 2019 18:13:10 +0200 Subject: [PATCH 20/28] PleromaAPI: Fix emoji_reactions_by --- .../web/pleroma_api/controllers/pleroma_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 39d371ff7..884b3d877 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -26,7 +26,7 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) emoji_reactions |> Enum.map(fn {emoji, users} -> users = Enum.map(users, &User.get_cached_by_ap_id/1) - {emoji, AccountView.render("accounts.json", %{users: users, for: user, as: :user})} + {emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})} end) |> Enum.into(%{}) From 4cb603e1df7c9d13db1aa4285e2a18b9be71cd78 Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Oct 2019 18:19:16 +0200 Subject: [PATCH 21/28] PleromaAPI: Add unreacting. --- .../controllers/pleroma_api_controller.ex | 14 +++++++++++++- lib/pleroma/web/router.ex | 1 + .../controllers/pleroma_api_controller_test.exs | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 884b3d877..8aee7d7c5 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -41,7 +41,19 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) def react_with_emoji(%{assigns: %{user: user}} = conn, %{"id" => activity_id, "emoji" => emoji}) do with {:ok, _activity, _object} <- CommonAPI.react_with_emoji(activity_id, user, emoji), - activity = Activity.get_by_id(activity_id) do + activity <- Activity.get_by_id(activity_id) do + conn + |> put_view(StatusView) + |> render("show.json", %{activity: activity, for: user, as: :activity}) + end + end + + def unreact_with_emoji(%{assigns: %{user: user}} = conn, %{ + "id" => activity_id, + "emoji" => emoji + }) do + with {:ok, _activity, _object} <- CommonAPI.unreact_with_emoji(activity_id, user, emoji), + activity <- Activity.get_by_id(activity_id) do conn |> put_view(StatusView) |> render("show.json", %{activity: activity, for: user, as: :activity}) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ff3dc273e..87d373f55 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -305,6 +305,7 @@ defmodule Pleroma.Web.Router do pipe_through(:oauth_write) patch("/conversations/:id", PleromaAPIController, :update_conversation) post("/statuses/:id/react_with_emoji", PleromaAPIController, :react_with_emoji) + post("/statuses/:id/unreact_with_emoji", PleromaAPIController, :unreact_with_emoji) post("/notifications/read", PleromaAPIController, :read_notification) patch("/accounts/update_avatar", AccountController, :update_avatar) diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index 82d23ea5b..3a5dbdeea 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do alias Pleroma.Conversation.Participation alias Pleroma.Notification + alias Pleroma.Object alias Pleroma.Repo alias Pleroma.Web.CommonAPI @@ -41,6 +42,10 @@ test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do assert %{"id" => id} = json_response(result, 200) assert to_string(activity.id) == id + + object = Object.normalize(activity) + + assert object.data["reaction_count"] == 0 end test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do From 0e41951eab405ea5a3016bbb0897eba4aa7c6a0b Mon Sep 17 00:00:00 2001 From: lain Date: Wed, 2 Oct 2019 18:20:40 +0200 Subject: [PATCH 22/28] Pleroma API Readme: Document unreaction endpoint. --- docs/api/pleroma_api.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api/pleroma_api.md b/docs/api/pleroma_api.md index 7862e6301..e672aec96 100644 --- a/docs/api/pleroma_api.md +++ b/docs/api/pleroma_api.md @@ -483,6 +483,13 @@ Emoji reactions work a lot like favourites do. They make it possible to react to * Params: `emoji`: A single character unicode emoji * Response: JSON, the status. +## `POST /api/v1/pleroma/statuses/:id/unreact_with_emoji` +### Remove a reaction to a post with a unicode emoji +* Method: `POST` +* Authentication: required +* Params: `emoji`: A single character unicode emoji +* Response: JSON, the status. + ## `GET /api/v1/pleroma/statuses/:id/emoji_reactions_by` ### Get an object of emoji to account mappings with accounts that reacted to the post * Method: `GET` From c9043c6c808129f4f6236d03bf05e5a46f0c6e14 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 3 Oct 2019 18:37:23 +0200 Subject: [PATCH 23/28] Transmogrifier: Handle incoming Undos for EmojiReactions. --- .../web/activity_pub/transmogrifier.ex | 22 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 20 ++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index cb868c336..ea209b5ea 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -721,6 +721,28 @@ def handle_incoming( end end + def handle_incoming( + %{ + "type" => "Undo", + "object" => %{"type" => "EmojiReaction", "id" => reaction_activity_id}, + "actor" => _actor, + "id" => id + } = data, + _options + ) do + with actor <- Containment.get_actor(data), + {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), + {:ok, activity, _} <- + ActivityPub.unreact_with_emoji(actor, reaction_activity_id, + activity_id: id, + local: false + ) do + {:ok, activity} + else + _e -> :error + end + end + def handle_incoming( %{ "type" => "Undo", diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 5bb435457..a9544caf2 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -359,6 +359,25 @@ test "it works for incoming emoji reactions" do assert data["content"] == "๐Ÿ‘Œ" end + test "it works for incoming emoji reaction undos" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "๐Ÿ‘Œ") + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", reaction_activity.data["id"]) + |> Map.put("actor", user.ap_id) + + {:ok, activity} = Transmogrifier.handle_incoming(data) + + assert activity.actor == user.ap_id + assert activity.data["id"] == data["id"] + assert activity.data["type"] == "Undo" + end + test "it returns an error for incoming unlikes wihout a like activity" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) @@ -1116,7 +1135,6 @@ test "it inlines private announced objects" do {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) - object = modified["object"] assert modified["object"]["content"] == "hey" assert modified["object"]["actor"] == modified["object"]["attributedTo"] From 8e83c8423fa846e82103b20798f0cbde676354ff Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 4 Oct 2019 14:49:59 +0200 Subject: [PATCH 24/28] Update Changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a71a9dae6..35c062dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items - Pleroma API: `POST /api/v1/pleroma/scrobble` to scrobble a media item - Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance` +- Pleroma API: Add Emoji reactions ### Changed - **Breaking:** Elixir >=1.8 is now required (was >= 1.7) From 43a211bcb111720622a89da3016372f5a3a12184 Mon Sep 17 00:00:00 2001 From: lain Date: Fri, 4 Oct 2019 17:01:04 +0200 Subject: [PATCH 25/28] Transmogrifier: Handle misskey likes with reactions like EmojiReactions. --- .../web/activity_pub/transmogrifier.ex | 27 +++++++++++++++++++ test/fixtures/misskey-like.json | 14 ++++++++++ test/web/activity_pub/transmogrifier_test.exs | 18 +++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 test/fixtures/misskey-like.json diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ea209b5ea..54c18bc0e 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -560,6 +560,33 @@ def handle_incoming( end end + @misskey_reactions %{ + "like" => "๐Ÿ‘", + "love" => "โค๏ธ", + "laugh" => "๐Ÿ˜†", + "hmm" => "๐Ÿค”", + "surprise" => "๐Ÿ˜ฎ", + "congrats" => "๐ŸŽ‰", + "angry" => "๐Ÿ’ข", + "confused" => "๐Ÿ˜ฅ", + "rip" => "๐Ÿ˜‡", + "pudding" => "๐Ÿฎ" + } + + @doc "Rewrite misskey likes into EmojiReactions" + def handle_incoming( + %{ + "type" => "Like", + "_misskey_reaction" => reaction + } = data, + options + ) do + data + |> Map.put("type", "EmojiReaction") + |> Map.put("content", @misskey_reactions[reaction]) + |> handle_incoming(options) + end + def handle_incoming( %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data, _options diff --git a/test/fixtures/misskey-like.json b/test/fixtures/misskey-like.json new file mode 100644 index 000000000..84d56f473 --- /dev/null +++ b/test/fixtures/misskey-like.json @@ -0,0 +1,14 @@ +{ + "@context" : [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + {"Hashtag" : "as:Hashtag"} + ], + "_misskey_reaction" : "pudding", + "actor": "http://mastodon.example.org/users/admin", + "cc" : ["https://testing.pleroma.lol/users/lain"], + "id" : "https://misskey.xyz/75149198-2f45-46e4-930a-8b0538297075", + "nickname" : "lain", + "object" : "https://testing.pleroma.lol/objects/c331bbf7-2eb9-4801-a709-2a6103492a5a", + "type" : "Like" +} diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index a9544caf2..530a762fa 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -341,6 +341,24 @@ test "it works for incoming likes" do assert data["object"] == activity.data["object"] end + test "it works for incoming misskey likes, turning them into EmojiReactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/misskey-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == data["actor"] + assert data["type"] == "EmojiReaction" + assert data["id"] == data["id"] + assert data["object"] == activity.data["object"] + assert data["content"] == "๐Ÿฎ" + end + test "it works for incoming emoji reactions" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) From 6a85f7d1eabd957588a2f9b8dfea5b7f982573be Mon Sep 17 00:00:00 2001 From: lain Date: Sat, 5 Oct 2019 10:45:42 +0200 Subject: [PATCH 26/28] Transmogrifier: Extend misskey like compatibility. --- .../web/activity_pub/transmogrifier.ex | 5 +++-- test/web/activity_pub/transmogrifier_test.exs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 54c18bc0e..2c6edb0b1 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -570,7 +570,8 @@ def handle_incoming( "angry" => "๐Ÿ’ข", "confused" => "๐Ÿ˜ฅ", "rip" => "๐Ÿ˜‡", - "pudding" => "๐Ÿฎ" + "pudding" => "๐Ÿฎ", + "star" => "โญ" } @doc "Rewrite misskey likes into EmojiReactions" @@ -583,7 +584,7 @@ def handle_incoming( ) do data |> Map.put("type", "EmojiReaction") - |> Map.put("content", @misskey_reactions[reaction]) + |> Map.put("content", @misskey_reactions[reaction] || reaction) |> handle_incoming(options) end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 530a762fa..9156b23e3 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -359,6 +359,25 @@ test "it works for incoming misskey likes, turning them into EmojiReactions" do assert data["content"] == "๐Ÿฎ" end + test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/misskey-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("_misskey_reaction", "โญ") + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == data["actor"] + assert data["type"] == "EmojiReaction" + assert data["id"] == data["id"] + assert data["object"] == activity.data["object"] + assert data["content"] == "โญ" + end + test "it works for incoming emoji reactions" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) From d580eedfe93ef08fea7869945c0fd4fe908cb82d Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 7 Oct 2019 12:40:33 +0200 Subject: [PATCH 27/28] Linting. --- .../web/pleroma_api/controllers/pleroma_api_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 16c581a95..6db213475 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do alias Pleroma.Conversation.Participation alias Pleroma.Notification alias Pleroma.Object - alias Pleroma.User alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView From 7a322713c33e8ef2fdc326f9e35d1fcbe7590c93 Mon Sep 17 00:00:00 2001 From: lain Date: Thu, 14 Nov 2019 08:21:06 +0000 Subject: [PATCH 28/28] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9b447d09..0464f4571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,7 +59,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read - Mastodon API: Add `/api/v1/markers` for managing timeline read markers - Mastodon API: Add the `recipients` parameter to `GET /api/v1/conversations` -- Pleroma API: Add Emoji reactions - Configuration: `feed` option for user atom feed. - Pleroma API: Add Emoji reactions