Repository: microsoft/botbuilder-python

Issue:
Add support for Message Reactions to ActivityHandler 
ActivityHandler should be extended to include MessageReactions. This has now been added to the C# and The JavaScript.

Here is a pointer to the JavaScript implementation:

https://github.com/microsoft/botbuilder-js/pull/1038



Discussion:

Resolving patch:
diff --git a/libraries/botbuilder-core/botbuilder/core/activity_handler.py b/libraries/botbuilder-core/botbuilder/core/activity_handler.py
--- a/libraries/botbuilder-core/botbuilder/core/activity_handler.py
+++ b/libraries/botbuilder-core/botbuilder/core/activity_handler.py
@@ -1,7 +1,8 @@
 # Copyright (c) Microsoft Corporation. All rights reserved.
 # Licensed under the MIT License.
+from typing import List
 
-from botbuilder.schema import ActivityTypes, ChannelAccount
+from botbuilder.schema import ActivityTypes, ChannelAccount, MessageReaction
 from .turn_context import TurnContext
 
 
@@ -27,6 +28,8 @@ async def on_turn(self, turn_context: TurnContext):
             await self.on_message_activity(turn_context)
         elif turn_context.activity.type == ActivityTypes.conversation_update:
             await self.on_conversation_update_activity(turn_context)
+        elif turn_context.activity.type == ActivityTypes.message_reaction:
+            await self.on_message_reaction_activity(turn_context)
         elif turn_context.activity.type == ActivityTypes.event:
             await self.on_event_activity(turn_context)
         else:
@@ -64,6 +67,27 @@ async def on_members_removed_activity(
     ):  # pylint: disable=unused-argument
         return
 
+    async def on_message_reaction_activity(self, turn_context: TurnContext):
+        if turn_context.activity.reactions_added is not None:
+            await self.on_reactions_added(
+                turn_context.activity.reactions_added, turn_context
+            )
+
+        if turn_context.activity.reactions_removed is not None:
+            await self.on_reactions_removed(
+                turn_context.activity.reactions_removed, turn_context
+            )
+
+    async def on_reactions_added(  # pylint: disable=unused-argument
+        self, message_reactions: List[MessageReaction], turn_context: TurnContext
+    ):
+        return
+
+    async def on_reactions_removed(  # pylint: disable=unused-argument
+        self, message_reactions: List[MessageReaction], turn_context: TurnContext
+    ):
+        return
+
     async def on_event_activity(self, turn_context: TurnContext):
         if turn_context.activity.name == "tokens/response":
             return await self.on_token_response_event(turn_context)


Test patch:
diff --git a/libraries/botbuilder-core/tests/test_activity_handler.py b/libraries/botbuilder-core/tests/test_activity_handler.py
new file mode 100644
--- /dev/null
+++ b/libraries/botbuilder-core/tests/test_activity_handler.py
@@ -0,0 +1,98 @@
+from typing import List
+
+import aiounittest
+from botbuilder.core import ActivityHandler, BotAdapter, TurnContext
+from botbuilder.schema import (
+    Activity,
+    ActivityTypes,
+    ChannelAccount,
+    ConversationReference,
+    MessageReaction,
+)
+
+
+class TestingActivityHandler(ActivityHandler):
+    def __init__(self):
+        self.record: List[str] = []
+
+    async def on_message_activity(self, turn_context: TurnContext):
+        self.record.append("on_message_activity")
+        return await super().on_message_activity(turn_context)
+
+    async def on_members_added_activity(
+        self, members_added: ChannelAccount, turn_context: TurnContext
+    ):
+        self.record.append("on_members_added_activity")
+        return await super().on_members_added_activity(members_added, turn_context)
+
+    async def on_members_removed_activity(
+        self, members_removed: ChannelAccount, turn_context: TurnContext
+    ):
+        self.record.append("on_members_removed_activity")
+        return await super().on_members_removed_activity(members_removed, turn_context)
+
+    async def on_message_reaction_activity(self, turn_context: TurnContext):
+        self.record.append("on_message_reaction_activity")
+        return await super().on_message_reaction_activity(turn_context)
+
+    async def on_reactions_added(
+        self, message_reactions: List[MessageReaction], turn_context: TurnContext
+    ):
+        self.record.append("on_reactions_added")
+        return await super().on_reactions_added(message_reactions, turn_context)
+
+    async def on_reactions_removed(
+        self, message_reactions: List[MessageReaction], turn_context: TurnContext
+    ):
+        self.record.append("on_reactions_removed")
+        return await super().on_reactions_removed(message_reactions, turn_context)
+
+    async def on_token_response_event(self, turn_context: TurnContext):
+        self.record.append("on_token_response_event")
+        return await super().on_token_response_event(turn_context)
+
+    async def on_event(self, turn_context: TurnContext):
+        self.record.append("on_event")
+        return await super().on_event(turn_context)
+
+    async def on_unrecognized_activity_type(self, turn_context: TurnContext):
+        self.record.append("on_unrecognized_activity_type")
+        return await super().on_unrecognized_activity_type(turn_context)
+
+
+class NotImplementedAdapter(BotAdapter):
+    async def delete_activity(
+        self, context: TurnContext, reference: ConversationReference
+    ):
+        raise NotImplementedError()
+
+    async def send_activities(self, context: TurnContext, activities: List[Activity]):
+        raise NotImplementedError()
+
+    async def update_activity(self, context: TurnContext, activity: Activity):
+        raise NotImplementedError()
+
+
+class TestActivityHandler(aiounittest.AsyncTestCase):
+    async def test_message_reaction(self):
+        # Note the code supports multiple adds and removes in the same activity though
+        # a channel may decide to send separate activities for each. For example, Teams
+        # sends separate activities each with a single add and a single remove.
+
+        # Arrange
+        activity = Activity(
+            type=ActivityTypes.message_reaction,
+            reactions_added=[MessageReaction(type="sad")],
+            reactions_removed=[MessageReaction(type="angry")],
+        )
+        turn_context = TurnContext(NotImplementedAdapter(), activity)
+
+        # Act
+        bot = TestingActivityHandler()
+        await bot.on_turn(turn_context)
+
+        # Assert
+        assert len(bot.record) == 3
+        assert bot.record[0] == "on_message_reaction_activity"
+        assert bot.record[1] == "on_reactions_added"
+        assert bot.record[2] == "on_reactions_removed"