Skip to content
Merged
Show file tree
Hide file tree
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
16 changes: 16 additions & 0 deletions libraries/Microsoft.Bot.Builder/Teams/TeamsActivityHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,8 @@ protected override Task OnEventActivityAsync(ITurnContext<IEventActivity> turnCo
{
switch (turnContext.Activity.Name)
{
case "application/vnd.microsoft.readReceipt":
return OnTeamsReadReceiptAsync(JObject.FromObject(turnContext.Activity.Value).ToObject<ReadReceiptInfo>(), turnContext, cancellationToken);
case "application/vnd.microsoft.meetingStart":
return OnTeamsMeetingStartAsync(JObject.FromObject(turnContext.Activity.Value).ToObject<MeetingStartEventDetails>(), turnContext, cancellationToken);
case "application/vnd.microsoft.meetingEnd":
Expand Down Expand Up @@ -793,6 +795,20 @@ protected virtual Task OnTeamsMeetingEndAsync(MeetingEndEventDetails meeting, IT
return Task.CompletedTask;
}

/// <summary>
/// Invoked when a read receipt for a previously sent message is received from the connector.
/// Override this in a derived class to provide logic for when the bot receives a read receipt event.
/// </summary>
/// <param name="readReceiptInfo">Information regarding the read receipt. i.e. Id of the message last read by the user.</param>
/// <param name="turnContext">A strongly-typed context object for this turn.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects
/// or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected virtual Task OnTeamsReadReceiptAsync(ReadReceiptInfo readReceiptInfo, ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <summary>
/// Safely casts an object to an object of type <typeparamref name="T"/> .
/// </summary>
Expand Down
81 changes: 81 additions & 0 deletions libraries/Microsoft.Bot.Schema/Teams/ReadReceiptInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Newtonsoft.Json;

namespace Microsoft.Bot.Schema.Teams
{
/// <summary>
/// General information about a read receipt.
/// </summary>
public partial class ReadReceiptInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="ReadReceiptInfo"/> class.
/// </summary>
public ReadReceiptInfo()
{
CustomInit();
}

/// <summary>
/// Initializes a new instance of the <see cref="ReadReceiptInfo"/> class.
/// </summary>
/// <param name="lastReadMessageId">The id of the last read message.</param>
public ReadReceiptInfo(string lastReadMessageId)
{
LastReadMessageId = lastReadMessageId;
CustomInit();
}

/// <summary>
/// Gets or sets the id of the last read message.
/// </summary>
/// <value>
/// The id of the last read message.
/// </value>
[JsonProperty(PropertyName = "lastReadMessageId")]
public string LastReadMessageId { get; set; }

/// <summary>
/// Helper method useful for determining if a message has been read. This method
/// converts the strings to longs. If the compareMessageId is less than or equal to
/// the lastReadMessageId, then the message has been read.
/// </summary>
/// <param name="compareMessageId">The id of the message to compare.</param>
/// <param name="lastReadMessageId">The id of the last message read by the user.</param>
/// <returns>True if the compareMessageId is less than or equal to the lastReadMessageId.</returns>
public static bool IsMessageRead(string compareMessageId, string lastReadMessageId)
{
if (string.IsNullOrEmpty(compareMessageId) || string.IsNullOrEmpty(lastReadMessageId))
{
return false;
}

if (long.TryParse(compareMessageId, out long compareMessageIdLong)
&& long.TryParse(lastReadMessageId, out long lastReadMessageIdLong))
{
// if compareMessageId is smaller than lastReadMessageId, it means the user read the bot's message.
return (compareMessageIdLong.CompareTo(lastReadMessageIdLong) <= 0) ? true : false;
}

return false;
}

/// <summary>
/// Helper method useful for determining if a message has been read.
/// If the compareMessageId is less than or equal to the LastReadMessageId,
/// then the message has been read.
/// </summary>
/// <param name="compareMessageId">The id of the message to compare.</param>
/// <returns>True if the compareMessageId is less than or equal to the lastReadMessageId.</returns>
public bool IsMessageRead(string compareMessageId)
{
return IsMessageRead(compareMessageId, LastReadMessageId);
}

/// <summary>
/// An initialization method that performs custom operations like setting defaults.
/// </summary>
partial void CustomInit();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,39 @@ void CaptureSend(Activity[] arg)
Assert.Contains("1:02:03 AM", activitiesToSend[0].Text); // Date format differs between OSs, so we just Assert.Contains instead of Assert.Equals
}

[Fact]
public async Task TeamsReadReceiptEvent()
{
// Arrange
var activity = new Activity
{
ChannelId = Channels.Msteams,
Type = ActivityTypes.Event,
Name = "application/vnd.microsoft.readReceipt",
Value = JObject.Parse(@"{""lastReadMessageId"":""10101010""}"),
};

Activity[] activitiesToSend = null;
void CaptureSend(Activity[] arg)
{
activitiesToSend = arg;
}

var turnContext = new TurnContext(new SimpleAdapter(CaptureSend), activity);

// Act
var bot = new TestActivityHandler();
await ((IBot)bot).OnTurnAsync(turnContext);

// Assert
Assert.Equal(2, bot.Record.Count);
Assert.Equal("OnEventActivityAsync", bot.Record[0]);
Assert.Equal("OnTeamsReadReceiptAsync", bot.Record[1]);
Assert.NotNull(activitiesToSend);
Assert.Single(activitiesToSend);
Assert.Equal("10101010", activitiesToSend[0].Text);
}

private class NotImplementedAdapter : BotAdapter
{
public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken)
Expand Down Expand Up @@ -1251,6 +1284,13 @@ protected override Task OnMembersRemovedAsync(IList<ChannelAccount> membersRemov
return Task.CompletedTask;
}

protected override Task OnTeamsReadReceiptAsync(ReadReceiptInfo readReceiptInfo, ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
Record.Add(MethodBase.GetCurrentMethod().Name);
turnContext.SendActivityAsync(readReceiptInfo.LastReadMessageId);
return Task.CompletedTask;
}

// Invoke
protected override Task<InvokeResponse> OnInvokeActivityAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
Expand Down
26 changes: 26 additions & 0 deletions tests/Microsoft.Bot.Schema.Tests/Teams/ReadReceiptInfoTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Bot.Schema.Teams;
using Xunit;

namespace Microsoft.Bot.Schema.Tests.Teams
{
public class ReadReceiptInfoTests
{
[Theory]
[InlineData("1000", "1000", true)]
[InlineData("1001", "1000", true)]
[InlineData("1000", "1001", false)]
[InlineData("1000", null, false)]
[InlineData(null, "1000", false)]
public void ReadReceiptInfoTest(string lastRead, string compare, bool isRead)
{
var info = new ReadReceiptInfo(lastRead);

Assert.Equal(info.LastReadMessageId, lastRead);
Assert.Equal(info.IsMessageRead(compare), isRead);
Assert.Equal(ReadReceiptInfo.IsMessageRead(compare, lastRead), isRead);
}
}
}