Skip to content

Port: Support Teams message edit, message soft delete, message undelete activities #2183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 30, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions libraries/botbuilder-core/botbuilder/core/activity_handler.py
Original file line number Diff line number Diff line change
@@ -68,6 +68,10 @@ async def on_turn(

if turn_context.activity.type == ActivityTypes.message:
await self.on_message_activity(turn_context)
elif turn_context.activity.type == ActivityTypes.message_update:
await self.on_message_update_activity(turn_context)
elif turn_context.activity.type == ActivityTypes.message_delete:
await self.on_message_delete_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:
@@ -107,6 +111,34 @@ async def on_message_activity( # pylint: disable=unused-argument
"""
return

async def on_message_update_activity( # pylint: disable=unused-argument
self, turn_context: TurnContext
):
"""
Override this method in a derived class to provide logic specific to activities,
such as the conversational logic.

:param turn_context: The context object for this turn
:type turn_context: :class:`botbuilder.core.TurnContext`

:returns: A task that represents the work queued to execute
"""
return

async def on_message_delete_activity( # pylint: disable=unused-argument
self, turn_context: TurnContext
):
"""
Override this method in a derived class to provide logic specific to activities,
such as the conversational logic.

:param turn_context: The context object for this turn
:type turn_context: :class:`botbuilder.core.TurnContext`

:returns: A task that represents the work queued to execute
"""
return

async def on_conversation_update_activity(self, turn_context: TurnContext):
"""
Invoked when a conversation update activity is received from the channel when the base behavior of
Original file line number Diff line number Diff line change
@@ -1042,3 +1042,73 @@ async def on_teams_meeting_participants_leave_event(
:returns: A task that represents the work queued to execute.
"""
return

async def on_message_update_activity(self, turn_context: TurnContext):
"""
Invoked when a message update activity is received, such as a message edit or undelete.

:param turn_context: A context object for this turn.

:returns: A task that represents the work queued to execute.
"""
if turn_context.activity.channel_id == Channels.ms_teams:
channel_data = TeamsChannelData().deserialize(
turn_context.activity.channel_data
)

if channel_data:
if channel_data.event_type == "editMessage":
return await self.on_teams_message_edit(turn_context)
if channel_data.event_type == "undeleteMessage":
return await self.on_teams_message_undelete(turn_context)

return await super().on_message_update_activity(turn_context)

async def on_message_delete_activity(self, turn_context: TurnContext):
"""
Invoked when a message delete activity is received, such as a soft delete message.

:param turn_context: A context object for this turn.

:returns: A task that represents the work queued to execute.
"""
if turn_context.activity.channel_id == Channels.ms_teams:
channel_data = TeamsChannelData().deserialize(
turn_context.activity.channel_data
)

if channel_data:
if channel_data.event_type == "softDeleteMessage":
return await self.on_teams_message_soft_delete(turn_context)

return await super().on_message_delete_activity(turn_context)

async def on_teams_message_edit(self, turn_context: TurnContext):
"""
Invoked when a Teams edit message event activity is received.

:param turn_context: A context object for this turn.

:returns: A task that represents the work queued to execute.
"""
return

async def on_teams_message_undelete(self, turn_context: TurnContext):
"""
Invoked when a Teams undo soft delete message event activity is received.

:param turn_context: A context object for this turn.

:returns: A task that represents the work queued to execute.
"""
return

async def on_teams_message_soft_delete(self, turn_context: TurnContext):
"""
Invoked when a Teams soft delete message event activity is received.

:param turn_context: A context object for this turn.

:returns: A task that represents the work queued to execute.
"""
return
139 changes: 139 additions & 0 deletions libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
TabContext,
MeetingParticipantsEventDetails,
ReadReceiptInfo,
TeamsChannelData,
)
from botframework.connector import Channels
from simple_adapter import SimpleAdapter
@@ -351,6 +352,26 @@ async def on_teams_meeting_end_event(
turn_context.activity.value, turn_context
)

async def on_message_update_activity(self, turn_context: TurnContext):
self.record.append("on_message_update_activity")
return await super().on_message_update_activity(turn_context)

async def on_teams_message_edit(self, turn_context: TurnContext):
self.record.append("on_teams_message_edit")
return await super().on_teams_message_edit(turn_context)

async def on_teams_message_undelete(self, turn_context: TurnContext):
self.record.append("on_teams_message_undelete")
return await super().on_teams_message_undelete(turn_context)

async def on_message_delete_activity(self, turn_context: TurnContext):
self.record.append("on_message_delete_activity")
return await super().on_message_delete_activity(turn_context)

async def on_teams_message_soft_delete(self, turn_context: TurnContext):
self.record.append("on_teams_message_soft_delete")
return await super().on_teams_message_soft_delete(turn_context)

async def on_teams_meeting_participants_join_event(
self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext
):
@@ -1254,6 +1275,124 @@ async def test_on_teams_meeting_end_event(self):
assert bot.record[0] == "on_event_activity"
assert bot.record[1] == "on_teams_meeting_end_event"

async def test_message_update_activity_teams_message_edit(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_update,
channel_data=TeamsChannelData(event_type="editMessage"),
channel_id=Channels.ms_teams,
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(2, len(bot.record))
self.assertEqual("on_message_update_activity", bot.record[0])
self.assertEqual("on_teams_message_edit", bot.record[1])

async def test_message_update_activity_teams_message_undelete(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_update,
channel_data=TeamsChannelData(event_type="undeleteMessage"),
channel_id=Channels.ms_teams,
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(2, len(bot.record))
self.assertEqual("on_message_update_activity", bot.record[0])
self.assertEqual("on_teams_message_undelete", bot.record[1])

async def test_message_update_activity_teams_message_undelete_no_msteams(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_update,
channel_data=TeamsChannelData(event_type="undeleteMessage"),
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(1, len(bot.record))
self.assertEqual("on_message_update_activity", bot.record[0])

async def test_message_update_activity_teams_no_channel_data(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_update,
channel_id=Channels.ms_teams,
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(1, len(bot.record))
self.assertEqual("on_message_update_activity", bot.record[0])

async def test_message_delete_activity_teams_message_soft_delete(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_delete,
channel_data=TeamsChannelData(event_type="softDeleteMessage"),
channel_id=Channels.ms_teams,
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(2, len(bot.record))
self.assertEqual("on_message_delete_activity", bot.record[0])
self.assertEqual("on_teams_message_soft_delete", bot.record[1])

async def test_message_delete_activity_teams_message_soft_delete_no_msteams(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_delete,
channel_data=TeamsChannelData(event_type="softDeleteMessage"),
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(1, len(bot.record))
self.assertEqual("on_message_delete_activity", bot.record[0])

async def test_message_delete_activity_teams_no_channel_data(self):
# Arrange
activity = Activity(
type=ActivityTypes.message_delete,
channel_id=Channels.ms_teams,
)
turn_context = TurnContext(SimpleAdapter(), activity)

# Act
bot = TestingTeamsActivityHandler()
await bot.on_turn(turn_context)

# Assert
self.assertEqual(1, len(bot.record))
self.assertEqual("on_message_delete_activity", bot.record[0])

async def test_on_teams_meeting_participants_join_event(self):
# arrange
activity = Activity(
34 changes: 34 additions & 0 deletions libraries/botbuilder-core/tests/test_activity_handler.py
Original file line number Diff line number Diff line change
@@ -26,6 +26,14 @@ 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_message_update_activity(self, turn_context: TurnContext):
self.record.append("on_message_update_activity")
return await super().on_message_update_activity(turn_context)

async def on_message_delete_activity(self, turn_context: TurnContext):
self.record.append("on_message_delete_activity")
return await super().on_message_delete_activity(turn_context)

async def on_members_added_activity(
self, members_added: ChannelAccount, turn_context: TurnContext
):
@@ -208,6 +216,32 @@ async def test_invoke_should_not_match(self):
assert bot.record[0] == "on_invoke_activity"
assert adapter.activity.value.status == int(HTTPStatus.NOT_IMPLEMENTED)

async def test_on_message_update_activity(self):
activity = Activity(type=ActivityTypes.message_update)

adapter = TestInvokeAdapter()
turn_context = TurnContext(adapter, activity)

# Act
bot = TestingActivityHandler()
await bot.on_turn(turn_context)

assert len(bot.record) == 1
assert bot.record[0] == "on_message_update_activity"

async def test_on_message_delete_activity(self):
activity = Activity(type=ActivityTypes.message_delete)

adapter = TestInvokeAdapter()
turn_context = TurnContext(adapter, activity)

# Act
bot = TestingActivityHandler()
await bot.on_turn(turn_context)

assert len(bot.record) == 1
assert bot.record[0] == "on_message_delete_activity"

async def test_on_end_of_conversation_activity(self):
activity = Activity(type=ActivityTypes.end_of_conversation)