diff --git a/Bot.Builder.Community.sln b/Bot.Builder.Community.sln index 0e3583bc..7450fdef 100644 --- a/Bot.Builder.Community.sln +++ b/Bot.Builder.Community.sln @@ -183,6 +183,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapt EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Facebook.Tests", "tests\Bot.Builder.Community.Adapters.Facebook.Tests\Bot.Builder.Community.Adapters.Facebook.Tests.csproj", "{8201DC48-763A-4534-9E51-466E15DF01D8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Components.Trigger.SessionAgent", "libraries\Bot.Builder.Community.Components.Trigger.SessionAgent\Bot.Builder.Community.Components.Trigger.SessionAgent.csproj", "{EA37FC80-A1FC-4B7F-B091-33128C55242E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU @@ -805,6 +807,14 @@ Global {8201DC48-763A-4534-9E51-466E15DF01D8}.Documentation|Any CPU.Build.0 = Debug|Any CPU {8201DC48-763A-4534-9E51-466E15DF01D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {8201DC48-763A-4534-9E51-466E15DF01D8}.Release|Any CPU.Build.0 = Release|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Documentation|Any CPU.Build.0 = Debug|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA37FC80-A1FC-4B7F-B091-33128C55242E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -893,6 +903,7 @@ Global {428AD1B4-DF58-4D21-9C19-AB4AB6001A90} = {840D4038-9AB8-4750-9FFE-365386CE47E2} {3348B9A5-E3CE-4AF8-B059-8B4D7971C25A} = {840D4038-9AB8-4750-9FFE-365386CE47E2} {8201DC48-763A-4534-9E51-466E15DF01D8} = {840D4038-9AB8-4750-9FFE-365386CE47E2} + {EA37FC80-A1FC-4B7F-B091-33128C55242E} = {1F7F4CAF-CF22-491F-840D-A63835726C12} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {9FE3B75E-BA2B-45BC-BBF0-DDA8BA10C4F0} diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/AgentSessionBotComponent.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/AgentSessionBotComponent.cs new file mode 100644 index 00000000..c778313a --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/AgentSessionBotComponent.cs @@ -0,0 +1,47 @@ +using System; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Helper; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Middleware; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Service; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Trigger; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs.Declarative; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent +{ + public class SessionAgentBotComponent : BotComponent + { + public override void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + if (services == null) + throw new ArgumentNullException(nameof(services)); + + if (configuration == null) + throw new ArgumentNullException(nameof(configuration)); + + ActivityHelper.IsEnable = string.IsNullOrEmpty(configuration["IsEnabled"]) + || Convert.ToBoolean(configuration["IsEnabled"]); + + if (!ActivityHelper.IsEnable) return; + + ActivityHelper.SleepInMilliseconds = string.IsNullOrEmpty(configuration["SleepTime"]) ? 1000 : Convert.ToInt32(configuration["SleepTime"]); + + if (ActivityHelper.SleepInMilliseconds <= 100) + ActivityHelper.SleepInMilliseconds = 1000; + + services.AddSingleton(); + + services.AddHostedService(); + + services.AddSingleton(); + + services.AddSingleton(sp => + new DeclarativeType(OnSessionExpireConversation.Kind)); + + services.AddSingleton(sp => + new DeclarativeType(OnReminderConversation.Kind)); + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/AgentJob.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/AgentJob.cs new file mode 100644 index 00000000..f5cd606e --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/AgentJob.cs @@ -0,0 +1,8 @@ +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer +{ + public enum AgentJob + { + Update, + Track + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/IServiceAgentAnnouncer.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/IServiceAgentAnnouncer.cs new file mode 100644 index 00000000..fd119b49 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/IServiceAgentAnnouncer.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer +{ + public interface IServiceAgentAnnouncer : IObservable + { + void SendAnnouncement(); + void UserLastAccessTime(string userId); + + void RegisterUser(ITurnContext turnContext); + + void SetController(IBot bot, IBotFrameworkHttpAdapter botHttpAdapter); + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/ServiceAgentAnnouncer.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/ServiceAgentAnnouncer.cs new file mode 100644 index 00000000..25c515a7 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/ServiceAgentAnnouncer.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Receiver; +using Bot.Builder.Community.Components.Trigger.SessionAgent.UserInformation; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer +{ + public partial class ServiceAgentAnnouncer : IServiceAgentAnnouncer + { + private readonly ConcurrentDictionary> _usersDictionary = + new ConcurrentDictionary>(); + + private IBot _bot; + private IBotFrameworkHttpAdapter _botAdapter; + + private IDisposable Subscribe(string userId, IObserver observer) + { + if (!_usersDictionary.ContainsKey(userId)) + { + _usersDictionary.TryAdd(userId, observer); + } + + return new Unsubscribe(_usersDictionary, userId); + } + + public IDisposable Subscribe(IObserver observer) + { + throw new NotImplementedException("Subscribe function handle by internally"); + } + + public void SendAnnouncement() + { + if (!_usersDictionary.IsEmpty) + { + Parallel.ForEach(_usersDictionary, number => + { + number.Value.OnNext(AgentJob.Track); + }); + } + } + + public void UserLastAccessTime(string userId) + { + if (_usersDictionary.TryGetValue(userId, out var subscribeTime)) + { + subscribeTime.OnNext(AgentJob.Update); + } + } + + public void RegisterUser(ITurnContext turnContext) + { + if (_bot != null && _botAdapter != null) + { + IUser user = new User(_bot, _botAdapter, turnContext); + + var watcher = new ServiceAgentReceiver(user); + + watcher.Disposable = Subscribe(user.UserId, watcher); + } + } + + public void SetController(IBot bot, IBotFrameworkHttpAdapter botHttpAdapter) + { + _bot = bot ?? throw new ArgumentNullException(nameof(bot)); + _botAdapter = botHttpAdapter ?? throw new ArgumentNullException(nameof(botHttpAdapter)); + } + + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/Unsubscribe.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/Unsubscribe.cs new file mode 100644 index 00000000..7d18cf5b --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Announcer/Unsubscribe.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Concurrent; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer +{ + public partial class ServiceAgentAnnouncer + { + private class Unsubscribe : IDisposable + { + private readonly ConcurrentDictionary> _observers; + private readonly string _userId; + + public Unsubscribe() + { + _observers = null; + _userId = string.Empty; + } + + public Unsubscribe(ConcurrentDictionary> observers, string userId) + { + _observers = observers; + _userId = userId; + } + + public void Dispose() + { + _observers?.TryRemove(_userId, out _); + } + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Bot.Builder.Community.Components.Trigger.SessionAgent.csproj b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Bot.Builder.Community.Components.Trigger.SessionAgent.csproj new file mode 100644 index 00000000..e51f277b --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Bot.Builder.Community.Components.Trigger.SessionAgent.csproj @@ -0,0 +1,55 @@ + + + + + netstandard2.0 + Action is performed when Automatically conversation has expired. + Bot Builder Community + Bot Builder Community + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/blob/master/LICENSE + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/master/libraries/Bot.Builder.Community.Components.Trigger.AutomaticallyExpireConversation + 1.0.0 + 1.0.0 + package-icon.png + + bots;botframework;botbuilder;msbot-component;msbot-middleware;msbot-trigger; + http://www.github.com/botbuildercommunity/botbuildercommunity-dotnet + + + LICENSE + Action is performed when Automatically conversation has expired. + https://github.com/BotBuilderCommunity/botbuilder-community-dotnet/tree/develop/libraries/Bot.Builder.Community.Components.Trigger.AutomaticallyExpireConversation + true + 1.0.10 + BotBuilderCommunity + $(AssemblyName) + + + + + + + + + + + + + + + + + + + + True + + + + True + + + + + + diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Helper/ActivityHelper.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Helper/ActivityHelper.cs new file mode 100644 index 00000000..7fa4de72 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Helper/ActivityHelper.cs @@ -0,0 +1,17 @@ +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Helper +{ + public static class ActivityHelper + { + public static double ExpireAfterSeconds { get; set; } = 0; + + public static int SleepInMilliseconds { get; set; } = 0; + + public static double ReminderSeconds { get; set; } = 0; + + public static string SessionExpireTrigger = "SessionExpireConversation"; + + public static string ReminderTrigger = "ReminderConversation"; + + public static bool IsEnable; + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Middleware/ConversationAgentMiddleware.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Middleware/ConversationAgentMiddleware.cs new file mode 100644 index 00000000..b169b068 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Middleware/ConversationAgentMiddleware.cs @@ -0,0 +1,40 @@ +using System.Threading; +using System.Threading.Tasks; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Helper; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Middleware +{ + public class ConversationAgentMiddleware : IMiddleware + { + private readonly IServiceAgentAnnouncer _serviceAgentAnnouncer; + + public ConversationAgentMiddleware(IServiceAgentAnnouncer serviceAgentAnnouncer) + { + _serviceAgentAnnouncer = serviceAgentAnnouncer; + } + + public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, + CancellationToken cancellationToken = new CancellationToken()) + { + if (ActivityHelper.IsEnable) + { + if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate && + turnContext.Activity.MembersAdded != null) + { + _serviceAgentAnnouncer.RegisterUser(turnContext); + } + else + { + var conversationReference = turnContext.Activity.GetConversationReference(); + _serviceAgentAnnouncer.UserLastAccessTime(conversationReference.User.Id); + } + } + + await next(cancellationToken); + + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/README.md b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/README.md new file mode 100644 index 00000000..60f58d8f --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/README.md @@ -0,0 +1,145 @@ +# Session Expire / Reminder Conversation - Trigger Component for Bot Framework Composer + +## Build status +| Branch | Status | Recommended NuGet package version | +| ------ | ------ | ------ | +| master | [![Build status](https://ci.appveyor.com/api/projects/status/b9123gl3kih8x9cb?svg=true)](https://ci.appveyor.com/project/garypretty/botbuilder-community) | [Available via NuGet](https://www.nuget.org/packages/Bot.Builder.Community.Components.Trigger.SessionAgent/) | + +## Description + +This is part of the [Bot Builder Community](https://github.com/botbuildercommunity) project which contains open source Bot Framework Composer components, along with other extensions, including middleware, recognizers and other components for use with the Bot Builder .NET SDK v4. + +**Session Expire Conversation** : If a user does not respond after a certain period 'Session Expire Conversation' activity gets triggered to restart a conversation. + +**Reminder Conversation** : If a user does not respond after a certain period 'Reminder Conversation' activity gets triggered to remind a conversation + + +* [Composer component installation](#composer-component-installation) +* [Add Reminder trigger](#Add-Reminder-trigger) +* [Result from Add Reminder trigger](#result-from-expire-conversation) +* [Add Session expire trigger](#Add-Session-Expire-trigger) +* [Result from Add Session expire trigger](#result-from-Session-expire-conversation) +* [Settings](#Settings) +* [Note](#Note) + + +### Composer component installation + +1. Navigate to the Bot Framework Composer **Package Manager**. +2. Search for 'Session' and install **Bot.Builder.Community.Components.Trigger.SessionAgent** + +![image](https://user-images.githubusercontent.com/16264167/178138577-0d81798a-2b55-4efa-a394-60221858a42a.png) + + +### Add Reminder trigger +1. Go to the create a trigger dialog and add the 'Reminder conversation'. +![image](https://user-images.githubusercontent.com/16264167/178139209-3a62181d-935c-4d16-9688-882d9c8e9e78.png) + + + +2. Set the Reminder in seconds in the right side +![image](https://user-images.githubusercontent.com/16264167/178138798-25aedf1f-434a-4b0a-ae91-84ec815b723e.png) + + +### Result from Reminder trigger +Once you've configured the 'Reminder trigger' and set the 'Reminder in seconds' then this component will run on and start to monitor the activity, In case activities are not received within the time limit 'reminder trigger' gets invoked. + +You can get the results use this syntax '${turn.activity.entities}' (entities contains user details) + +```json +{ + +“lgType”: “Activity”, + +“text”: [ + +{ + + "type": "ReminderConversation", + + "mentioned": { + + "id": "e765a676-bb70-412c-a351-3587745e52cb", + + "name": "User", + + "aadObjectId": null, + + "role": "user" + + }, + + "text": null + +} +] +} +``` + +### Add Session Expire trigger + +1. Go to the create a trigger dialog and add the 'Session expire conversation'. +![image](https://user-images.githubusercontent.com/16264167/178139106-bb1b104e-65af-47b3-9fe2-73f7689183cb.png) + + +2. Set the Expire in seconds in the right side +![image](https://user-images.githubusercontent.com/16264167/178139139-62ef98e3-f45c-4513-9909-f0b5ff32fef0.png) + + +### Result from Session Expire trigger +Once you've configured the 'Session Expire trigger' and set the 'Expire in seconds' then this component will run on and start to monitor the activity, In case activities are not received within the time limit Session Expire trigger' gets invoked. + +You can get the results use this syntax '${turn.activity.entities}' +entities contains user details + +```json + +{ + +“lgType”: “Activity”, + +“text”: [ + +{ + + "type": "SessionExpireConversation", + + "mentioned": { + + "id": "78ee4558-a015-4817-aa04-ed6e353e0943", + + "name": "User", + + "aadObjectId": null, + + "role": "user" + + }, + + "text": null + +} +] +} + +``` + +### Settings + +Both settings are options + +SleepTime : This option is used to change the scan service, default agent service scans the conversation every 1000 Milliseconds, you can change this scan option on the settings page. + +IsEnable : This option is use to "Enable or Disable" Agent Service triggers + +goto navigate to Project Settings and toggle the Advanced Settings View (json) add the following settings at the root of your settings JSON. + +```json +"Bot.Builder.Community.Components.Trigger.SessionAgent" : { + "SleepTime" : "2000", + "IsEnabled" : true + } +``` +### Note +1. Both the triggers work independently, in case adding two trigger's together, the Reminder trigger 'seconds' must be always less than the session expires trigger seconds +2. After receiving the 'Session Expire Conversation trigger' the developer should handle the end of the conversation, clear memory, etc. diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Receiver/IServiceAgentReceiver.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Receiver/IServiceAgentReceiver.cs new file mode 100644 index 00000000..ac41c9ed --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Receiver/IServiceAgentReceiver.cs @@ -0,0 +1,10 @@ +using System; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Receiver +{ + public interface IServiceAgentReceiver : IObserver + { + IDisposable Disposable { get; set; } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Receiver/ServiceAgentReceiver.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Receiver/ServiceAgentReceiver.cs new file mode 100644 index 00000000..c73753c8 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Receiver/ServiceAgentReceiver.cs @@ -0,0 +1,67 @@ +using System; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Helper; +using Bot.Builder.Community.Components.Trigger.SessionAgent.UserInformation; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Receiver +{ + public sealed class ServiceAgentReceiver : IServiceAgentReceiver + { + private readonly IUser _user; + public IDisposable Disposable { get; set; } + + public ServiceAgentReceiver(IUser user) + { + _user = user ?? throw new ArgumentNullException(nameof(user)); + } + + public void OnCompleted() + { + + } + + public void OnError(Exception error) + { + + } + + public void OnNext(AgentJob value) + { + switch (value) + { + case AgentJob.Track: + { + var timeInterval = DateTime.UtcNow - _user.LastAccessTime; + + if (ActivityHelper.ReminderSeconds > 0 && timeInterval >= + TimeSpan.FromSeconds(ActivityHelper.ReminderSeconds) + && _user.IsReminder) + { + if (ActivityHelper.ExpireAfterSeconds <= 0) + { + _user.SendTrigger(ActivityHelper.ReminderTrigger); + _user.IsReminder = false; + } + else if (ActivityHelper.ReminderSeconds <= ActivityHelper.ExpireAfterSeconds) + { + _user.SendTrigger(ActivityHelper.ReminderTrigger); + _user.IsReminder = false; + } + } + else if (ActivityHelper.ExpireAfterSeconds > 0 && + timeInterval >= TimeSpan.FromSeconds(ActivityHelper.ExpireAfterSeconds)) + { + _user.SendTrigger(ActivityHelper.SessionExpireTrigger); + Disposable.Dispose(); + } + + break; + } + case AgentJob.Update: + _user.LastAccessTime = DateTime.UtcNow; + _user.IsReminder = true; + break; + } + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Service/AgentBackgroundService.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Service/AgentBackgroundService.cs new file mode 100644 index 00000000..c3e0a4a9 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Service/AgentBackgroundService.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Announcer; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Helper; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Service +{ + public class AgentBackgroundService : BackgroundService + { + private readonly ILogger _logger; + + private readonly IServiceAgentAnnouncer _serviceAgentAnnouncer = null; + public AgentBackgroundService(IBot bot, IBotFrameworkHttpAdapter botHttpAdapter, ILogger logger, + IServiceAgentAnnouncer serviceAgentAnnouncer) + { + _logger = logger; + _serviceAgentAnnouncer = serviceAgentAnnouncer; + _serviceAgentAnnouncer.SetController(bot,botHttpAdapter); + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + _serviceAgentAnnouncer?.SendAnnouncement(); + await Task.Delay(TimeSpan.FromMilliseconds(ActivityHelper.SleepInMilliseconds), stoppingToken); + } + } + public override Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Background Tracker started : {dataTime}", DateTime.UtcNow); + return base.StartAsync(cancellationToken); + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Background Tracker stopped : {dataTime}", DateTime.UtcNow); + return base.StopAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Trigger/OnReminderConversation.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Trigger/OnReminderConversation.cs new file mode 100644 index 00000000..2b5fddb0 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Trigger/OnReminderConversation.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.CompilerServices; +using AdaptiveExpressions; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Helper; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Conditions; +using Newtonsoft.Json; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Trigger +{ + public class OnReminderConversation : OnActivity + { + [JsonProperty("$kind")] + public new const string Kind = nameof(OnReminderConversation); + + [JsonProperty("ReminderAfterSeconds")] + public int Reminder { get; set; } + + [JsonConstructor] + public OnReminderConversation([CallerFilePath] string callerPath = "", [CallerLineNumber] int callerLine = 0) + : base(type: ActivityHelper.ReminderTrigger, callerPath: callerPath, callerLine: callerLine) + { + + } + + protected override Expression CreateExpression() + { + Type = ActivityHelper.ReminderTrigger; + + if (Reminder > 0) + { + var reminder = Convert.ToDouble(Reminder); + ActivityHelper.ReminderSeconds = reminder; + } + + return Expression.AndExpression(Expression.Parse($"{TurnPath.Activity}.Type == '{this.Type}'"), + base.CreateExpression()); + } + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Trigger/OnSessionExpireConversation.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Trigger/OnSessionExpireConversation.cs new file mode 100644 index 00000000..0dfaf04a --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/Trigger/OnSessionExpireConversation.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.CompilerServices; +using AdaptiveExpressions; +using Bot.Builder.Community.Components.Trigger.SessionAgent.Helper; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Builder.Dialogs.Adaptive.Conditions; +using Newtonsoft.Json; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.Trigger +{ + public class OnSessionExpireConversation : OnActivity + { + [JsonProperty("$kind")] + public new const string Kind = nameof(OnSessionExpireConversation); + + [JsonProperty("ExpireAfterSeconds")] + public int ExpireAfterSeconds { get; set; } + + [JsonConstructor] + public OnSessionExpireConversation([CallerFilePath] string callerPath = "", [CallerLineNumber] int callerLine = 0) + : base(type: ActivityHelper.SessionExpireTrigger, callerPath: callerPath, callerLine: callerLine) + { + + } + + protected override Expression CreateExpression() + { + Type = ActivityHelper.SessionExpireTrigger; + + if (ExpireAfterSeconds > 0) + { + var expireSeconds = Convert.ToDouble(ExpireAfterSeconds); + ActivityHelper.ExpireAfterSeconds = expireSeconds; + } + + return Expression.AndExpression(Expression.Parse($"{TurnPath.Activity}.Type == '{this.Type}'"), base.CreateExpression()); + } + } +} diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/UserInformation/IUser.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/UserInformation/IUser.cs new file mode 100644 index 00000000..6481a549 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/UserInformation/IUser.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.UserInformation +{ + public interface IUser + { + Task SendTrigger(string triggerName); + + DateTime LastAccessTime { get; set; } + + string UserId { get; } + + bool IsReminder { get; set; } + + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/UserInformation/User.cs b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/UserInformation/User.cs new file mode 100644 index 00000000..b832a381 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/UserInformation/User.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector; +using Microsoft.Bot.Schema; + +namespace Bot.Builder.Community.Components.Trigger.SessionAgent.UserInformation +{ + public class User : IUser + { + public string UserId { get; } + + public DateTime LastAccessTime { get; set; } + + public ConversationReference ConversationReference { get; } + + public IConnectorClient ConnectorClient { get; } + + public IBot Bot { get; } + + public IBotFrameworkHttpAdapter BotAdapter { get; } + + public bool IsReminder { get; set; } + + public User(IBot bot, IBotFrameworkHttpAdapter botAdapter, ITurnContext turnContext) + { + if (turnContext == null) + throw new ArgumentNullException(nameof(turnContext)); + + Bot = bot ?? throw new ArgumentNullException(nameof(bot)); + + BotAdapter = botAdapter ?? throw new ArgumentNullException(nameof(botAdapter)); + + ConversationReference = turnContext.Activity.GetConversationReference(); + + var connectorClient = turnContext.TurnState.Get(); + + if (connectorClient == null) + throw new ArgumentNullException(nameof(connectorClient)); + + UserId = ConversationReference.User.Id; + + LastAccessTime = DateTime.UtcNow; + ConnectorClient = new ConnectorClient(connectorClient.BaseUri, connectorClient.Credentials); + + IsReminder = true; + } + + public async Task SendTrigger(string triggerName) + { + if (ConversationReference != null && ConnectorClient != null) + { + var activity = PrepareActivity(triggerName, ConversationReference); + + if (activity != null) + { + activity.Timestamp = DateTimeOffset.UtcNow; + activity.LocalTimestamp = DateTimeOffset.UtcNow; + activity.MembersAdded = null; + + var turnContext = new TurnContext((BotAdapter)BotAdapter, activity); + turnContext.TurnState.Add(ConnectorClient); + + await Bot.OnTurnAsync(turnContext); + } + } + } + + private static Activity PrepareActivity(string activityName,ConversationReference conversationReference) + { + + if (conversationReference == null) + return null; + + var createActivity = conversationReference.GetContinuationActivity(); + + createActivity.Type = activityName; + createActivity.ReplyToId = string.Empty; + + createActivity.Entities = new List(); + + var entity = new Entity + { + Type = activityName + }; + entity.SetAs(new Mention() + { + Mentioned = new ChannelAccount() + { + Id = conversationReference.User.Id, + Name = conversationReference.User.Name, + Role = conversationReference.User.Role, + AadObjectId = conversationReference.User.AadObjectId + + }, + Type = activityName + }); + + createActivity.Entities.Add(entity); + + return createActivity; + } + + } + +} diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnReminderConversation.schema b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnReminderConversation.schema new file mode 100644 index 00000000..bffa6206 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnReminderConversation.schema @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "$role": [ "implements(Microsoft.ITrigger)" ], + "title": "On Reminder Conversation", + "description": "Reminder Conversation when user is not interact with in seconds", + "properties": { + "ReminderAfterSeconds": { + "$ref": "schema:#/definitions/integerExpression", + "title": "Reminder in seconds", + "description": "Reminder the conversation after seconds", + "examples": [ + "24" + ] + } + }, + "required": [ + ] +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnReminderConversation.uischema b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnReminderConversation.uischema new file mode 100644 index 00000000..93035653 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnReminderConversation.uischema @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/ui/v1.0/ui.schema", + "form": { + "order": [ + "*" + ], + "hidden": [ + "condition", + "actions" + ], + "label": "Reminder conversation", + "subtitle": "Reminder conversation trigger", + "description": "Action is performed automatically when user is not interact with in time." + }, + "trigger": { + "label": "Reminder conversation" + } +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnSessionExpireConversation.schema b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnSessionExpireConversation.schema new file mode 100644 index 00000000..301865a8 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnSessionExpireConversation.schema @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/component/v1.0/component.schema", + "$role": [ "implements(Microsoft.ITrigger)" ], + "title": "On Session Expire Conversation", + "description": "Action is performed when the session conversation has expired.", + "properties": { + "ExpireAfterSeconds": { + "$ref": "schema:#/definitions/integerExpression", + "title": "Expire in seconds", + "description": "Set the conversation session timeout", + "examples": [ + "30" + ] + } + }, + "required": [ + ] +} \ No newline at end of file diff --git a/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnSessionExpireConversation.uischema b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnSessionExpireConversation.uischema new file mode 100644 index 00000000..df953bd1 --- /dev/null +++ b/libraries/Bot.Builder.Community.Components.Trigger.SessionAgent/schema/OnSessionExpireConversation.uischema @@ -0,0 +1,18 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/ui/v1.0/ui.schema", + "form": { + "order": [ + "*" + ], + "hidden": [ + "condition", + "actions" + ], + "label": "Session Expire conversation", + "subtitle": "Automatically session expire conversation trigger", + "description": "Action is performed automatically when the session has expired." + }, + "trigger": { + "label": "Session Expire conversation" + } +} \ No newline at end of file