Skip to content
Open
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
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35222.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FetchGroupChatMessages", "FetchGroupChatMessages\FetchGroupChatMessages.csproj", "{BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}"
EndProject
Project("{A9E3F50B-275E-4AF7-ADCE-8BE12D41E305}") = "M365Agent", "M365Agent\M365Agent.ttkproj", "{462434D8-DC29-4E0E-AF21-3ECED0A27602}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{81AE0E51-0749-438A-837B-6E2C1B5EB523}"
ProjectSection(SolutionItems) = preProject
FetchGroupChatMessages.slnLaunch.user = FetchGroupChatMessages.slnLaunch.user
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEE3FB6B-5F07-491F-9669-23B0C1CA4A30}.Release|Any CPU.Build.0 = Release|Any CPU
{462434D8-DC29-4E0E-AF21-3ECED0A27602}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{462434D8-DC29-4E0E-AF21-3ECED0A27602}.Debug|Any CPU.Build.0 = Debug|Any CPU
{462434D8-DC29-4E0E-AF21-3ECED0A27602}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{462434D8-DC29-4E0E-AF21-3ECED0A27602}.Release|Any CPU.ActiveCfg = Release|Any CPU
{462434D8-DC29-4E0E-AF21-3ECED0A27602}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4D7C6DCC-3A5C-42E6-B6E8-2E80E5342C91}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# TeamsFx files
build
appPackage/build
env/.env.*.user
env/.env.local
appsettings.Development.json
.deployment

# User-specific files
*.user

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Notification local store
.notification.localstore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Generated with Bot Builder V4 SDK Template for Visual Studio CoreBot v4.14.0

using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Builder.TraceExtensions;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace FetchGroupChatMessagesWithRSC
{
public class AdapterWithErrorHandler : CloudAdapter
{
public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger<CloudAdapter> logger)
: base(auth, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
// NOTE: In production environment, you should consider logging this to
// Azure Application Insights. Visit https://aka.ms/bottelemetry to see how
// to add telemetry capture to your bot.
logger.LogError($"Exception caught : {exception.Message}");

// Uncomment below commented line for local debugging.
// await turnContext.SendActivityAsync($"Sorry, it looks like something went wrong. Exception Caught: {exception.Message}");

// Send a trace activity, which will be displayed in the Bot Framework Emulator
await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError");
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Teams;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Schema.Teams;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace FetchGroupChatMessagesWithRSC.Bots
{
/// <summary>
/// Activity bot for handling user interactions and file uploads.
/// </summary>
public class ActivityBot<T> : TeamsActivityHandler where T : Dialog
{
private readonly IConfiguration _configuration;
private readonly IWebHostEnvironment _env;
private readonly IHttpClientFactory _clientFactory;
private readonly BotState _conversationState;
private readonly Dialog _dialog;

/// <summary>
/// Initializes a new instance of the <see cref="ActivityBot{T}"/> class.
/// </summary>
/// <param name="configuration">The application configuration.</param>
/// <param name="env">The web host environment.</param>
/// <param name="clientFactory">The HTTP client factory.</param>
/// <param name="conversationState">The conversation state.</param>
/// <param name="dialog">The dialog instance.</param>
public ActivityBot(IConfiguration configuration, IWebHostEnvironment env, IHttpClientFactory clientFactory, ConversationState conversationState, T dialog)
{
_clientFactory = clientFactory;
_configuration = configuration;
_env = env;
_conversationState = conversationState;
_dialog = dialog;
}

/// <summary>
/// Handle request from bot.
/// </summary>
/// <param name="turnContext">The turn context.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that represents the work queued to execute.</returns>
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)
{
await base.OnTurnAsync(turnContext, cancellationToken);

// Save any state changes that might have occurred during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}

/// <summary>
/// Handle when a message is addressed to the bot.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</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 override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var activity = StripAtMentionText((Activity)turnContext.Activity);
var userCommand = activity.Text.ToLower().Trim();

if (userCommand == "getchat" || userCommand == "logout" || userCommand == "login")
{
// Run the Dialog with the new message Activity.
await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Type 'getchat' to get archived messages."), cancellationToken);
}
}

/// <summary>
/// Invoked when a file consent card activity is received.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
/// <param name="fileConsentCardResponse">The response representing the value of the invoke activity sent when the user acts on a file consent card.</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 override async Task OnTeamsFileConsentAcceptAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
try
{
string filePath = Path.Combine(_env.ContentRootPath, "wwwroot", "chat.txt");
long fileSize = new FileInfo(filePath).Length;
var client = _clientFactory.CreateClient();

using (var fileStream = File.OpenRead(filePath))
{
var fileContent = new StreamContent(fileStream)
{
Headers =
{
ContentLength = fileSize,
ContentRange = new ContentRangeHeaderValue(0, fileSize - 1, fileSize)
}
};
await client.PutAsync(fileConsentCardResponse.UploadInfo.UploadUrl, fileContent, cancellationToken);
}

await FileUploadCompletedAsync(turnContext, fileConsentCardResponse, cancellationToken);
}
catch (Exception e)
{
await FileUploadFailedAsync(turnContext, e.ToString(), cancellationToken);
}
}

/// <summary>
/// Invoked when a file consent card is declined by the user.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
/// <param name="fileConsentCardResponse">The response representing the value of the invoke activity sent when the user declines a file consent card.</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 override async Task OnTeamsFileConsentDeclineAsync(ITurnContext<IInvokeActivity> turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
JToken context = JObject.FromObject(fileConsentCardResponse.Context);

var reply = MessageFactory.Text($"Declined. We won't upload file <b>{context["filename"]}</b>.");
reply.TextFormat = "xml";
await turnContext.SendActivityAsync(reply, cancellationToken);
}

/// <summary>
/// Handle file upload completion.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
/// <param name="fileConsentCardResponse">The response representing the value of the invoke activity sent when the user accepts a file consent card.</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>
private async Task FileUploadCompletedAsync(ITurnContext turnContext, FileConsentCardResponse fileConsentCardResponse, CancellationToken cancellationToken)
{
var downloadCard = new FileInfoCard
{
UniqueId = fileConsentCardResponse.UploadInfo.UniqueId,
FileType = fileConsentCardResponse.UploadInfo.FileType,
};

var asAttachment = new Attachment
{
Content = downloadCard,
ContentType = FileInfoCard.ContentType,
Name = fileConsentCardResponse.UploadInfo.Name,
ContentUrl = fileConsentCardResponse.UploadInfo.ContentUrl,
};

var reply = MessageFactory.Text($"<b>File uploaded.</b> Your file <b>{fileConsentCardResponse.UploadInfo.Name}</b> is ready to download.");
reply.TextFormat = "xml";
reply.Attachments = new List<Attachment> { asAttachment };

await turnContext.SendActivityAsync(reply, cancellationToken);
}

/// <summary>
/// Handle file upload failure.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</param>
/// <param name="error">Error while uploading the file.</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>
private async Task FileUploadFailedAsync(ITurnContext turnContext, string error, CancellationToken cancellationToken)
{
var reply = MessageFactory.Text($"<b>File upload failed.</b> Error: <pre>{error}</pre>");
reply.TextFormat = "xml";
await turnContext.SendActivityAsync(reply, cancellationToken);
}

/// <summary>
/// Remove mention text from the activity.
/// </summary>
/// <param name="activity">The activity containing the mention text.</param>
/// <returns>The activity with the mention text removed.</returns>
private Activity StripAtMentionText(Activity activity)
{
if (activity == null)
{
throw new ArgumentNullException(nameof(activity));
}

foreach (var mention in activity.GetMentions())
{
if (mention.Mentioned.Id == activity.Recipient.Id)
{
// Bot is in the @mention list.
// The below example will strip the bot name out of the message, so you can parse it as if it wasn't included.
// Note that the Text object will contain the full bot name, if applicable.
if (mention.Text != null)
{
activity.Text = activity.Text.Replace(mention.Text, "").Trim();
}
}
}
return activity;
}
}
}
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 System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Configuration;

namespace FetchGroupChatMessagesWithRSC.Bots
{
/// <summary>
/// Auth bot for handling user authentication and interactions.
/// </summary>
public class AuthBot<T> : ActivityBot<T> where T : Dialog
{
private readonly Dialog _dialog;
private readonly ConversationState _conversationState;

/// <summary>
/// Initializes a new instance of the <see cref="AuthBot{T}"/> class.
/// </summary>
/// <param name="configuration">The application configuration.</param>
/// <param name="env">The web host environment.</param>
/// <param name="clientFactory">The HTTP client factory.</param>
/// <param name="conversationState">The conversation state.</param>
/// <param name="dialog">The dialog instance.</param>
public AuthBot(IConfiguration configuration, IWebHostEnvironment env, IHttpClientFactory clientFactory, ConversationState conversationState, T dialog)
: base(configuration, env, clientFactory, conversationState, dialog)
{
_dialog = dialog;
_conversationState = conversationState;
}

/// <summary>
/// Invoked when the bot is added to the conversation.
/// </summary>
/// <param name="membersAdded">A list of all the members added to the conversation.</param>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</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 override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hello and welcome! Please type 'login' to initiate the authentication flow."), cancellationToken);
}
}
}

/// <summary>
/// Invoked when a token response event is received.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</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 override async Task OnTokenResponseEventAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
// Run the Dialog with the new Token Response Event Activity.
await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}

/// <summary>
/// Invoked when a sign-in verify state activity is received from the connector.
/// </summary>
/// <param name="turnContext">Context object containing information cached for a single turn of conversation with a user.</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 override async Task OnTeamsSigninVerifyStateAsync(ITurnContext<IInvokeActivity> turnContext, CancellationToken cancellationToken)
{
// Run the Dialog with the new Teams Sign-in Verify State Activity.
await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
}
Loading