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
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,23 @@ public class MessageReactionActivity() : Activity(ActivityType.MessageReaction)
[JsonPropertyOrder(122)]
public IList<Messages.Reaction>? ReactionsRemoved { get; set; }

[Obsolete("Use the Reactions client instead.")]
public MessageReactionActivity AddReaction(Messages.Reaction reaction)
{
ReactionsAdded ??= [];
ReactionsAdded.Add(reaction);
return this;
}

[Obsolete("Use the Reactions client instead.")]
public MessageReactionActivity AddReaction(Messages.ReactionType type)
{
ReactionsAdded ??= [];
ReactionsAdded.Add(new() { Type = type });
return this;
}

[Obsolete("Use the Reactions client instead.")]
public MessageReactionActivity RemoveReaction(Messages.Reaction reaction)
{
ReactionsRemoved ??= [];
Expand All @@ -58,6 +61,7 @@ public MessageReactionActivity RemoveReaction(Messages.Reaction reaction)
return this;
}

[Obsolete("Use the Reactions client instead.")]
public MessageReactionActivity RemoveReaction(Messages.ReactionType type)
{
ReactionsRemoved ??= [];
Expand Down
14 changes: 14 additions & 0 deletions Libraries/Microsoft.Teams.Api/Clients/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ public class ApiClient : Client
public virtual TeamClient Teams { get; }
public virtual MeetingClient Meetings { get; }

/// <summary>
/// Gets the underlying <see cref="IHttpClient"/> instance used by this <see cref="ApiClient"/>
/// and its sub-clients to perform HTTP requests.
/// </summary>
/// <remarks>
/// This property is provided for advanced scenarios where you need to issue custom HTTP
/// calls that are not yet covered by the strongly-typed clients exposed by <see cref="ApiClient"/>.
/// Prefer using the typed clients (<see cref="Bots"/>, <see cref="Conversations"/>,
/// <see cref="Users"/>, <see cref="Teams"/>, <see cref="Meetings"/>) whenever possible.
/// Relying on this property may couple your code to the current HTTP implementation and
/// could limit future refactoring of the underlying client.
/// </remarks>
public IHttpClient Client { get => base._http; }

public ApiClient(string serviceUrl, CancellationToken cancellationToken = default) : base(cancellationToken)
{
ServiceUrl = serviceUrl;
Expand Down
5 changes: 5 additions & 0 deletions Libraries/Microsoft.Teams.Api/Clients/ConversationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,38 @@ public class ConversationClient : Client
public readonly string ServiceUrl;
public readonly ActivityClient Activities;
public readonly MemberClient Members;
public readonly ReactionClient Reactions;

public ConversationClient(string serviceUrl, CancellationToken cancellationToken = default) : base(cancellationToken)
{
ServiceUrl = serviceUrl;
Activities = new ActivityClient(serviceUrl, _http, cancellationToken);
Members = new MemberClient(serviceUrl, _http, cancellationToken);
Reactions = new ReactionClient(serviceUrl, _http, cancellationToken);
}

public ConversationClient(string serviceUrl, IHttpClient client, CancellationToken cancellationToken = default) : base(client, cancellationToken)
{
ServiceUrl = serviceUrl;
Activities = new ActivityClient(serviceUrl, _http, cancellationToken);
Members = new MemberClient(serviceUrl, _http, cancellationToken);
Reactions = new ReactionClient(serviceUrl, _http, cancellationToken);
}

public ConversationClient(string serviceUrl, IHttpClientOptions options, CancellationToken cancellationToken = default) : base(options, cancellationToken)
{
ServiceUrl = serviceUrl;
Activities = new ActivityClient(serviceUrl, _http, cancellationToken);
Members = new MemberClient(serviceUrl, _http, cancellationToken);
Reactions = new ReactionClient(serviceUrl, _http, cancellationToken);
}

public ConversationClient(string serviceUrl, IHttpClientFactory factory, CancellationToken cancellationToken = default) : base(factory, cancellationToken)
{
ServiceUrl = serviceUrl;
Activities = new ActivityClient(serviceUrl, _http, cancellationToken);
Members = new MemberClient(serviceUrl, _http, cancellationToken);
Reactions = new ReactionClient(serviceUrl, _http, cancellationToken);
}

public async Task<ConversationResource> CreateAsync(CreateRequest request)
Expand Down
94 changes: 94 additions & 0 deletions Libraries/Microsoft.Teams.Api/Clients/ReactionClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Teams.Api.Messages;
using Microsoft.Teams.Common.Http;

namespace Microsoft.Teams.Api.Clients;

/// <summary>
/// Client for working with app message reactions for a given conversation/activity.
/// </summary>
public class ReactionClient : Client
{
public readonly string ServiceUrl;

public ReactionClient(string serviceUrl, CancellationToken cancellationToken = default)
: base(cancellationToken)
{
ServiceUrl = serviceUrl;
}

public ReactionClient(string serviceUrl, IHttpClient client, CancellationToken cancellationToken = default)
: base(client, cancellationToken)
{
ServiceUrl = serviceUrl;
}

public ReactionClient(string serviceUrl, IHttpClientOptions options, CancellationToken cancellationToken = default)
: base(options, cancellationToken)
{
ServiceUrl = serviceUrl;
}

public ReactionClient(string serviceUrl, IHttpClientFactory factory, CancellationToken cancellationToken = default)
: base(factory, cancellationToken)
{
ServiceUrl = serviceUrl;
}

/// <summary>
/// Adds a reaction on an activity in a conversation.
/// </summary>
/// <param name="conversationId">The conversation id.</param>
/// <param name="activityId">The id of the activity to react to.</param>
/// <param name="reactionType">
/// The reaction type (for example: "like", "heart", "launch", etc.).
/// </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation.
/// </returns>
public async Task AddAsync(
string conversationId,
string activityId,
ReactionType reactionType,
CancellationToken cancellationToken = default
)
{
// Assumed route:
// PUT v3/conversations/{conversationId}/activities/{activityId}/reactions/{reactionType}
var url = $"{ServiceUrl}v3/conversations/{conversationId}/activities/{activityId}/reactions/{reactionType}";
var req = HttpRequest.Put(url);
await _http.SendAsync(req, cancellationToken != default ? cancellationToken : _cancellationToken);
}

/// <summary>
/// Removes a reaction from an activity in a conversation.
/// </summary>
/// <param name="conversationId">The conversation id.</param>
/// <param name="activityId">The id of the activity the reaction is on.</param>
/// <param name="reactionType">
/// The reaction type to remove (for example: "like", "heart", "launch", etc.).
/// </param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> to observe while waiting for the task to complete.</param>
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation.
/// </returns>
public async Task DeleteAsync(
string conversationId,
string activityId,
ReactionType reactionType,
CancellationToken cancellationToken = default
)
{
// Assumed route:
// DELETE v3/conversations/{conversationId}/activities/{activityId}/reactions/{reactionType}
var url =
$"{ServiceUrl}v3/conversations/{conversationId}/activities/{activityId}/reactions/{reactionType}";

var req = HttpRequest.Delete(url);

await _http.SendAsync(req, cancellationToken != default ? cancellationToken : _cancellationToken);
}
}
41 changes: 27 additions & 14 deletions Libraries/Microsoft.Teams.Api/Messages/Reaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,46 @@
namespace Microsoft.Teams.Api.Messages;

/// <summary>
/// The type of reaction given to the
/// message. Possible values include: 'like', 'heart', 'laugh', 'surprised',
/// 'sad', 'angry', 'plusOne'
/// The type of reaction given to the message.
/// </summary>
[JsonConverter(typeof(JsonConverter<ReactionType>))]
public class ReactionType(string value) : StringEnum(value)
{
/// <summary>
/// 👍
/// </summary>
public static readonly ReactionType Like = new("like");
public bool IsLike => Like.Equals(Value);

/// <summary>
/// ❤️
/// </summary>
public static readonly ReactionType Heart = new("heart");
public bool IsHeart => Heart.Equals(Value);

public static readonly ReactionType Laugh = new("laugh");
public bool IsLaugh => Laugh.Equals(Value);

public static readonly ReactionType Surprise = new("surprise");
public bool IsSurprise => Surprise.Equals(Value);
/// <summary>
/// 👀
/// </summary>
public static readonly ReactionType Eyes = new("1f440_eyes");
public bool IsEyes => Eyes.Equals(Value);

public static readonly ReactionType Sad = new("sad");
public bool IsSad => Sad.Equals(Value);
/// <summary>
/// ✅
/// </summary>
public static readonly ReactionType CheckMark = new("2705_whiteheavycheckmark");
public bool IsCheckMark => CheckMark.Equals(Value);

public static readonly ReactionType Angry = new("angry");
public bool IsAngry => Angry.Equals(Value);
/// <summary>
/// 🚀
/// </summary>
public static readonly ReactionType Launch = new("launch");
public bool IsLaunch => Launch.Equals(Value);

public static readonly ReactionType PlusOne = new("plusOne");
public bool IsPlusOne => PlusOne.Equals(Value);
/// <summary>
/// 📌
/// </summary>
public static readonly ReactionType Pushpin = new("1f4cc_pushpin");
public bool IsPushpin => Pushpin.Equals(Value);
}

/// <summary>
Expand Down
17 changes: 16 additions & 1 deletion Microsoft.Teams.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.2.11415.280 d18.0
VisualStudioVersion = 18.2.11415.280
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libraries", "Libraries", "{809F86A1-1C4C-B159-0CD4-DF9D33D876CE}"
EndProject
Expand Down Expand Up @@ -89,6 +89,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deprecated.Controllers", "S
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.TargetedMessages", "Samples\Samples.TargetedMessages\Samples.TargetedMessages.csproj", "{A5989DC4-B182-4218-A858-24FA5FE31251}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.Reactions", "Samples\Samples.Reactions\Samples.Reactions.csproj", "{1FCD6490-DD3A-4D76-A07F-A30655365CA8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -543,6 +545,18 @@ Global
{A5989DC4-B182-4218-A858-24FA5FE31251}.Release|x64.Build.0 = Release|Any CPU
{A5989DC4-B182-4218-A858-24FA5FE31251}.Release|x86.ActiveCfg = Release|Any CPU
{A5989DC4-B182-4218-A858-24FA5FE31251}.Release|x86.Build.0 = Release|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Debug|x64.ActiveCfg = Debug|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Debug|x64.Build.0 = Debug|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Debug|x86.ActiveCfg = Debug|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Debug|x86.Build.0 = Debug|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Release|Any CPU.Build.0 = Release|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Release|x64.ActiveCfg = Release|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Release|x64.Build.0 = Release|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Release|x86.ActiveCfg = Release|Any CPU
{1FCD6490-DD3A-4D76-A07F-A30655365CA8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -588,6 +602,7 @@ Global
{FCEEF7AD-1E78-4615-8BE4-7B46B034A514} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{1F0A8B29-3792-4E11-B6A7-43F03F9A591F} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{A5989DC4-B182-4218-A858-24FA5FE31251} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
{1FCD6490-DD3A-4D76-A07F-A30655365CA8} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {378263F2-C2B2-4DB1-83CF-CA228AF03ABF}
Expand Down
38 changes: 38 additions & 0 deletions Samples/Samples.Reactions/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Microsoft.Teams.Api.Clients;
using Microsoft.Teams.Api.Messages;
using Microsoft.Teams.Apps.Activities;
using Microsoft.Teams.Apps.Extensions;
using Microsoft.Teams.Plugins.AspNetCore.DevTools.Extensions;
using Microsoft.Teams.Plugins.AspNetCore.Extensions;

var builder = WebApplication.CreateBuilder(args);
builder.AddTeams().AddTeamsDevTools();
var app = builder.Build();
var teams = app.UseTeams();


teams.OnMessage(async (context, cancellationToken) =>
{
await context.Send($"you said '{context.Activity.Text}'", cancellationToken);

// replace with context.Api.Conversations.Reactions once Reactions client is available in PROD.
var api = new ApiClient(context.Activity.ServiceUrl!, context.Api.Client, cancellationToken);

await api.Conversations.Reactions.AddAsync(context.Activity.Conversation.Id, context.Activity.Id, new ReactionType("1f44b_wavinghand-tone4"));

await Task.Delay(2000, cancellationToken);
await api.Conversations.Reactions.AddAsync(context.Activity.Conversation.Id, context.Activity.Id, new ReactionType("1f601_beamingfacewithsmilingeyes"));

await Task.Delay(2000, cancellationToken);
await api.Conversations.Reactions.DeleteAsync(context.Activity.Conversation.Id, context.Activity.Id, new ReactionType("1f601_beamingfacewithsmilingeyes"));

});

teams.OnMessageReaction(async (context, cancellationToken) =>
{
context.Log.Info($"Reaction '{context.Activity.ReactionsAdded?.FirstOrDefault()?.Type}' added by {context.Activity.From?.Name}");
await context.Send($"you added '{context.Activity.ReactionsAdded?.FirstOrDefault()?.Type}' " +
$"and removed '{context.Activity.ReactionsRemoved?.FirstOrDefault()?.Type}'", cancellationToken);
});

app.Run();
16 changes: 16 additions & 0 deletions Samples/Samples.Reactions/Properties/launchSettings.TEMPLATE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"bot-config": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:3978",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"Teams__TenantId": "",
"Teams__ClientId": "",
"Teams__ClientSecret": ""
}
}
}
}
14 changes: 14 additions & 0 deletions Samples/Samples.Reactions/Samples.Reactions.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Libraries\Microsoft.Teams.Plugins\Microsoft.Teams.Plugins.AspNetCore.DevTools\Microsoft.Teams.Plugins.AspNetCore.DevTools.csproj" />
<ProjectReference Include="..\..\Libraries\Microsoft.Teams.Plugins\Microsoft.Teams.Plugins.AspNetCore\Microsoft.Teams.Plugins.AspNetCore.csproj" />
</ItemGroup>

</Project>
9 changes: 9 additions & 0 deletions Samples/Samples.Reactions/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Loading