Пример #1
0
        public async Task Telemetry_LogTeamsProperties()
        {
            // Arrange
            var mockTelemetryClient = new Mock <IBotTelemetryClient>();

            var adapter = new TestAdapter(Channels.Msteams)
                          .Use(new TelemetryLoggerMiddleware(mockTelemetryClient.Object));

            var teamInfo    = new TeamInfo("teamId", "teamName");
            var channelData = new TeamsChannelData(null, null, teamInfo, null, new TenantInfo("tenantId"));
            var activity    = MessageFactory.Text("test");

            activity.ChannelData = channelData;
            activity.From        = new ChannelAccount("userId", "userName", null, "aadId");

            // Act
            await new TestFlow(adapter)
            .Send(activity)
            .StartTestAsync();

            // Assert
            Assert.Equal("BotMessageReceived", mockTelemetryClient.Invocations[0].Arguments[0]);
            Assert.True(((Dictionary <string, string>)mockTelemetryClient.Invocations[0].Arguments[1])["TeamsUserAadObjectId"] == "aadId");
            Assert.True(((Dictionary <string, string>)mockTelemetryClient.Invocations[0].Arguments[1])["TeamsTenantId"] == "tenantId");
            Assert.True(((Dictionary <string, string>)mockTelemetryClient.Invocations[0].Arguments[1])["TeamsTeamInfo"] == JsonConvert.SerializeObject(teamInfo));
        }
Пример #2
0
        public void TeamsChannelDataInitsWithNoArgs()
        {
            var channelData = new TeamsChannelData();

            Assert.NotNull(channelData);
            Assert.IsType <TeamsChannelData>(channelData);
        }
Пример #3
0
        /// <summary>
        /// When implemented in middleware, processess an incoming activity.
        /// </summary>
        /// <param name="turnContext">The context object for this turn.</param>
        /// <param name="next">The delegate to call to continue the bot middleware pipeline.</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>
        /// <remarks>
        /// Middleware calls the <paramref name="next" /> delegate to pass control to
        /// the next middleware in the pipeline. If middleware doesn’t call the next delegate,
        /// the adapter does not call any of the subsequent middleware’s request handlers or the
        /// bot’s receive handler, and the pipeline short circuits.
        /// <para>The <paramref name="turnContext" /> provides information about the
        /// incoming activity, and other data needed to process the activity.</para>
        /// </remarks>
        /// <seealso cref="ITurnContext" />
        /// <seealso cref="Schema.IActivity" />
        public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (!turnContext.Activity.ChannelId.Equals(Channels.Msteams, StringComparison.OrdinalIgnoreCase))
            {
                // If the goal is to NOT process messages from other channels, comment out the following line
                // and message processing will be 'short circuited'.
                await next(cancellationToken).ConfigureAwait(false);

                return;
            }

            TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData <TeamsChannelData>();
            string           tenantId         = teamsChannelData?.Tenant?.Id;

            if (string.IsNullOrEmpty(tenantId))
            {
                throw new UnauthorizedAccessException("Tenant Id is missing.");
            }

            if (!this.tenantMap.Contains(tenantId))
            {
                throw new UnauthorizedAccessException("Tenant Id '" + tenantId + "' is not allowed access.");
            }

            await next(cancellationToken).ConfigureAwait(false);
        }
Пример #4
0
        private async Task <Activity> HandleTeamsMessageAsync(Activity payload)
        {
            TeamEventBase    eventData   = payload.GetConversationUpdateData();
            ChannelAccount   botAccount  = payload.Recipient;
            string           tenantId    = payload.GetTenantId();
            TeamsChannelData channelData = payload.GetChannelData <TeamsChannelData>();

            switch (eventData.EventType)
            {
            // send a welcome message
            case TeamEventType.MembersAdded:
                var connector = new ConnectorClient(new Uri(payload.ServiceUrl));
                IEnumerable <TeamsChannelAccount> newMembers = payload.MembersAdded.AsTeamsChannelAccounts();
                string welcomeMessage = "Welcome Teams, I am Jarvis. How can I help you?";
                foreach (TeamsChannelAccount member in newMembers)
                {
                    // send a 1:1 message to new members
                    await MessageHelpers.SendOneToOneMessage(connector, channelData, botAccount, member, tenantId, welcomeMessage);
                }

                break;
            }



            return(null);
        }
Пример #5
0
        public static async Task SendOneToOneWelcomeMessage(
            ConnectorClient connector,
            TeamsChannelData channelData,
            ChannelAccount botAccount, ChannelAccount userAccount,
            string tenantId)
        {
            string welcomeMessage = CreateHelpMessage($"The team {channelData.Team.Name} has the Invnetory Management bot- helping your team to order inventories and find suppliers.");

            // create or get existing chat conversation with user
            var response = connector.Conversations.CreateOrGetDirectConversation(botAccount, userAccount, tenantId);

            // Construct the message to post to conversation
            Activity newActivity = new Activity()
            {
                Text         = welcomeMessage,
                Type         = ActivityTypes.Message,
                Conversation = new ConversationAccount
                {
                    Id = response.Id
                },
            };

            // Post the message to chat conversation with user
            await connector.Conversations.SendToConversationAsync(newActivity);
        }
        private async Task SetModerators(IDialogContext context, Activity activity, TeamsChannelData channelData)
        {
            // TODO: Updated DB
            var details = JsonConvert.DeserializeObject <ModeratorActionDetails>(activity.Value.ToString());

            if (details == null || details.Moderators == null)
            {
                details = JsonConvert.DeserializeObject <TaskModule.BotFrameworkCardValue <ModeratorActionDetails> >
                              (activity.Value.ToString()).Data;
            }
            var moderatorList = details.Moderators.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(m => m.ToLower().Trim()).ToList();

            if (moderatorList.Count == 0)
            {
                await context.PostAsync("Please set at least one moderator.");

                return;
            }
            Tenant tenantData = await Common.CheckAndAddTenantDetails(channelData.Tenant.Id);

            tenantData.Moderators = moderatorList;

            await Cache.Tenants.AddOrUpdateItemAsync(tenantData.Id, tenantData);

            await context.PostAsync("Moderators are set successfully. These users can now create message and post.");
        }
Пример #7
0
        // Handle team members removed event (in team scope)
        private async Task HandleTeamMembersRemovedAsync(Activity activity, TeamsChannelData channelData)
        {
            var teamId = channelData.Team.Id;

            this.logProvider.LogInfo($"Handling team members removed event in team {teamId}");

            var isBotRemoved = activity.MembersRemoved.Any(member => member.Id == activity.Recipient.Id);

            if (isBotRemoved)
            {
                this.logProvider.LogInfo($"Bot removed from team {teamId}");

                // Delete team details and membership records
                await this.userManagementHelper.DeleteTeamDetailsAsync(teamId);

                await this.userManagementHelper.DeleteUserTeamMembershipByTeamIdAsync(teamId);
            }
            else
            {
                foreach (var member in activity.MembersRemoved)
                {
                    this.logProvider.LogInfo($"User {member.Id} removed from team {teamId}.");

                    // Delete membership records for the users who were removed
                    await this.userManagementHelper.DeleteUserTeamMembershipAsync(member.Id, teamId);
                }
            }
        }
        internal async static Task <User> CheckAndAddUserDetails(Activity activity, TeamsChannelData channelData)
        {
            var currentUser = await GetCurrentUser(activity);

            // User not present in cache
            var userDetails = await Cache.Users.GetItemAsync(currentUser.UserPrincipalName.ToLower());

            if (userDetails == null && currentUser != null)
            {
                userDetails = new User()
                {
                    BotConversationId = activity.From.Id,
                    Id   = currentUser.UserPrincipalName.ToLower(),
                    Name = currentUser.Name ?? currentUser.GivenName
                };
                await Cache.Users.AddOrUpdateItemAsync(userDetails.Id, userDetails);

                Tenant tenantData = await Common.CheckAndAddTenantDetails(channelData.Tenant.Id);

                if (!tenantData.Users.Contains(userDetails.Id))
                {
                    tenantData.Users.Add(userDetails.Id);
                    await Cache.Tenants.AddOrUpdateItemAsync(tenantData.Id, tenantData);
                }
            }

            return(userDetails);
        }
Пример #9
0
        public static async Task SendOneToOneWelcomeMessage(ConnectorClient connector,
                                                            TeamsChannelData channelData,
                                                            ChannelAccount botAccount,
                                                            ChannelAccount userAccount,
                                                            string tenantId)
        {
            // Construct the message here
            string welcomeMessage = CreateHelpMessage($"The team {channelData.Team.Name} has added the Calculator Chat Bot - helping with some basic math stuff.");

            // Create or get existing chat conversation with the user
            var response = connector.Conversations.CreateOrGetDirectConversation(botAccount, userAccount, tenantId);

            // Construct the message to post to the conversation
            Activity newActivity = new Activity
            {
                Text         = welcomeMessage,
                Type         = ActivityTypes.Message,
                Conversation = new ConversationAccount
                {
                    Id = response.Id
                }
            };

            // Finally post the message
            await connector.Conversations.SendToConversationAsync(newActivity);
        }
Пример #10
0
 public IceBreakerBotTests()
 {
     this.botAdapter = new TestAdapter(Channels.Msteams)
     {
         Conversation =
         {
             Conversation         = new ConversationAccount
             {
                 ConversationType = "channel",
             },
         },
     };
     ConfigurationManager.AppSettings[DisableTenantFilterKey] = true.ToString().ToLower();
     this.telemetryClient    = new TelemetryClient();
     this.conversationHelper = new ConversationHelperMock();
     this.dataProvider       = new Mock <IBotDataProvider>();
     this.dataProvider.Setup(x => x.GetInstalledTeamAsync(It.IsAny <string>()))
     .Returns(() => Task.FromResult(new TeamInstallInfo()));
     this.sut         = new IcebreakerBot(this.dataProvider.Object, this.conversationHelper, MicrosoftAppCredentials.Empty, this.telemetryClient);
     this.userAccount = new TeamsChannelAccount {
         Id = Guid.NewGuid().ToString(), Properties = JObject.FromObject(new { Id = Guid.NewGuid().ToString() })
     };
     this.botAccount = new TeamsChannelAccount {
         Id = "bot", Properties = JObject.FromObject(new { Id = "bot" })
     };
     this.teamsChannelData = new TeamsChannelData
     {
         Team = new TeamInfo
         {
             Id = "TeamId",
         },
         Tenant = new TenantInfo(),
     };
 }
        /// <summary>
        /// Save member details in database
        /// </summary>
        /// <param name="member">ChannelAccount information of team member</param>
        /// <param name="channelData">TeamsChannelData</param>
        /// <param name="conversationId">Conversation id</param>
        /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
        private async Task SaveMemberDetailsAsync(ChannelAccount member, TeamsChannelData channelData, string conversationId)
        {
            // Add UserTeamMembership
            await this.AddUserTeamMembershipAsync(channelData.Team.Id, member);

            // Add user if it does not exist.
            await this.AddUserDetailsAsync(channelData, member, conversationId);
        }
        private async Task ShowAllDrafts(IDialogContext context, Activity activity, TeamsChannelData channelData)
        {
            var tenatInfo = await Cache.Tenants.GetItemAsync(channelData.Tenant.Id);

            var myTenantAnnouncements = new List <Campaign>();

            var listCard = new ListCard();

            listCard.content       = new Content();
            listCard.content.title = "Here are all draft and scheduled announcements:";;
            var list = new List <Item>();

            foreach (var announcementId in tenatInfo.Announcements)
            {
                var announcement = await Cache.Announcements.GetItemAsync(announcementId);

                if (announcement != null && (announcement.Status == Status.Draft || announcement.Status == Status.Scheduled))
                {
                    var item = new Item
                    {
                        icon     = announcement.Author.ProfilePhoto,
                        type     = "resultItem",
                        id       = announcement.Id,
                        title    = announcement.Title,
                        subtitle = "Author: " + announcement.Author?.Name
                                   + $" | Created Date: {announcement.CreatedTime.ToShortDateString()} | { (announcement.Status == Status.Scheduled ? "Scheduled" : "Draft") }",
                        tap = new Tap()
                        {
                            type  = ActionTypes.MessageBack,
                            title = "Id",
                            value = JsonConvert.SerializeObject(new AnnouncementActionDetails()
                            {
                                Id = announcement.Id, ActionType = Constants.ShowAnnouncement
                            })                                                                 //  "Show Announcement " + announcement.Title + " (" + announcement.Id + ")"
                        }
                    };
                    list.Add(item);
                }
            }

            if (list.Count > 0)
            {
                listCard.content.items = list.ToArray();
                var attachment = new Attachment
                {
                    ContentType = listCard.contentType,
                    Content     = listCard.content
                };
                var reply = activity.CreateReply();
                reply.Attachments.Add(attachment);
                await context.PostAsync(reply);
            }
            else
            {
                await context.PostAsync("Thre are no drafts. Please go ahead and create new announcement.");
            }
        }
Пример #13
0
        /// <summary>
        /// Called in the activity processing pipeline to process incoming activity.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="next">The next delegate to execute.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Task tracking operation.</returns>
        public async Task OnTurnAsync(ITurnContext context, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
        {
            TeamsChannelData teamsChannelData = context.Activity.GetChannelData <TeamsChannelData>();

            if (teamsChannelData.Team == null)
            {
                await next(cancellationToken).ConfigureAwait(false);
            }
        }
        private static async Task RenameTeam(TeamsChannelData channelData, Tenant tenant)
        {
            var team = await GetTeam(channelData, tenant);

            if (team != null)
            {
                team.Name = channelData.Team.Name;
                await Cache.Teams.AddOrUpdateItemAsync(team.Id, team);
            }
        }
        private static async Task <Team> GetTeam(TeamsChannelData channelData, Tenant tenant)
        {
            var teamId = channelData.Team.Id;

            if (tenant.Teams.Contains(channelData.Team.Id))
            {
                return(await Cache.Teams.GetItemAsync(channelData.Team.Id));
            }
            return(null);
        }
        private static async Task RemoveTeamDetails(TeamsChannelData channelData, Tenant tenant)
        {
            var team = await GetTeam(channelData, tenant);

            if (team != null)
            {
                await Cache.Teams.DeleteItemAsync(team.Id);
            }
            tenant.Teams.Remove(channelData.Team.Id);
            await Cache.Tenants.AddOrUpdateItemAsync(tenant.Id, tenant);
        }
        public void ChannelData_GetGeneralChannel()
        {
            Activity    sampleActivity = JsonConvert.DeserializeObject <Activity>(File.ReadAllText(@"Jsons\SampleActivityAtMention.json"));
            ChannelInfo generalChannel = sampleActivity.GetGeneralChannel();

            TeamsChannelData channelData = sampleActivity.GetChannelData <TeamsChannelData>();

            Assert.IsNotNull(generalChannel);
            Assert.IsNotNull(generalChannel.Id);
            Assert.IsTrue(generalChannel.Id == channelData.Team.Id);
        }
        /// <summary>
        /// Gets the Teams channel data associated with the current activity.
        /// </summary>
        /// <returns>Teams channel data <see cref="TeamsChannelData"/>Teams channel data for current activity.</returns>
        /// <exception cref="ArgumentNullException">ChannelData missing in Activity.</exception>
        public TeamsChannelData GetTeamsChannelData()
        {
            if (this.turnContext.Activity.ChannelData != null)
            {
                TeamsChannelData channelData = this.turnContext.Activity.GetChannelData <TeamsChannelData>();

                return(channelData);
            }
            else
            {
                throw new ArgumentNullException("ChannelData missing in Activity");
            }
        }
        private static async Task UpdateTeamCount(Activity message, TeamsChannelData channelData, Tenant tenant)
        {
            if (tenant.Teams.Contains(channelData.Team.Id))
            {
                ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
                var             members   = await connector.Conversations.GetConversationMembersAsync(channelData.Team.Id);

                var team = await Cache.Teams.GetItemAsync(channelData.Team.Id);

                team.Members = members.Select(m => m.AsTeamsChannelAccount().UserPrincipalName.ToLower()).ToList();
                await Cache.Teams.AddOrUpdateItemAsync(team.Id, team);
            }
        }
Пример #20
0
        public void ChannelData_PropertyCheck()
        {
            Activity         sampleActivity = JsonConvert.DeserializeObject <Activity>(File.ReadAllText(@"Jsons\SampleActivityAtMention.json"));
            TeamsChannelData channelData    = JsonConvert.DeserializeObject <TeamsChannelData>(sampleActivity.ChannelData.ToString());

            Assert.IsNotNull(channelData);
            Assert.IsNotNull(channelData.Channel);
            Assert.IsNotNull(channelData.Channel.Id);
            Assert.IsNotNull(channelData.Team);
            Assert.IsNotNull(channelData.Team.Id);
            Assert.IsNotNull(channelData.Tenant);
            Assert.IsNotNull(channelData.Tenant.Id);
        }
        private static async Task AddNewChannelDetails(TeamsChannelData channelData, Tenant tenant)
        {
            var team = await GetTeam(channelData, tenant);

            if (team != null)
            {
                team.Channels.Add(new Channel()
                {
                    Id = channelData.Channel.Id, Name = channelData.Channel.Name
                });
                await Cache.Teams.AddOrUpdateItemAsync(team.Id, team);
            }
        }
Пример #22
0
        /// <summary>
        /// Gets the key to use when reading and writing state to and from storage.
        /// </summary>
        /// <param name="turnContext">The context object for this turn.</param>
        /// <returns>The storage key.</returns>
        protected override string GetStorageKey(ITurnContext turnContext)
        {
            TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData <TeamsChannelData>();

            if (string.IsNullOrEmpty(teamsChannelData.Team?.Id))
            {
                return($"chat/{turnContext.Activity.ChannelId}/{turnContext.Activity.Conversation.Id}");
            }
            else
            {
                return($"team/{turnContext.Activity.ChannelId}/{teamsChannelData.Team.Id}");
            }
        }
        /// <summary>
        /// Creates a reply for the General channel of the team.
        /// </summary>
        /// <param name="text">Reply text.</param>
        /// <param name="locale">Locale information.</param>
        /// <returns>New reply activity with General channel channel data.</returns>
        public Activity CreateReplyToGeneralChannel(string text = null, string locale = null)
        {
            TeamsChannelData channelData   = this.turnContext.Activity.GetChannelData <TeamsChannelData>();
            Activity         replyActivity = this.turnContext.Activity.CreateReply(text, locale);

            replyActivity.ChannelData = new TeamsChannelData
            {
                Channel = this.GetGeneralChannel(),
                Team    = channelData.Team,
                Tenant  = channelData.Tenant,
            }.AsJObject();

            return(replyActivity);
        }
        private static async Task RenameChannel(TeamsChannelData channelData, Tenant tenant)
        {
            var team = await GetTeam(channelData, tenant);

            if (team != null)
            {
                var channel = team.Channels.FirstOrDefault(c => c.Id == channelData.Channel.Id);
                if (channel != null)
                {
                    channel.Name = channelData.Channel.Name;
                    await Cache.Teams.AddOrUpdateItemAsync(team.Id, team);
                }
            }
        }
Пример #25
0
        /// <summary>
        /// Creates a reply for the General channel of the team.
        /// </summary>
        /// <param name="activity">Incoming activity.</param>
        /// <param name="text">Reply text.</param>
        /// <param name="locale">Locale information.</param>
        /// <returns>New reply activity with General channel channel data.</returns>
        public static Activity CreateReplyToGeneralChannel(this Activity activity, string text = null, string locale = null)
        {
            TeamsChannelData channelData   = activity.GetChannelData <TeamsChannelData>();
            Activity         replyActivity = activity.CreateReply(text, locale);

            replyActivity.ChannelData = JObject.FromObject(new TeamsChannelData
            {
                Channel = activity.GetGeneralChannel(),
                Team    = channelData.Team,
                Tenant  = channelData.Tenant
            });

            return(replyActivity);
        }
Пример #26
0
        /// <summary>
        /// Notifies the user in direct conversation.
        /// </summary>
        /// <typeparam name="T">Type of message activity.</typeparam>
        /// <param name="replyActivity">The reply activity.</param>
        /// <returns>Modified activity.</returns>
        public static T NotifyUser <T>(this T replyActivity)
            where T : IMessageActivity
        {
            TeamsChannelData channelData = replyActivity.ChannelData == null ? new TeamsChannelData() : replyActivity.GetChannelData <TeamsChannelData>();

            channelData.Notification = new NotificationInfo
            {
                Alert = true
            };

            replyActivity.ChannelData = JObject.FromObject(channelData);

            return(replyActivity);
        }
Пример #27
0
        public void ConnectorExtensions_Create1on1()
        {
            JsonSerializerSettings serializerSettings = new JsonSerializerSettings();

            serializerSettings.NullValueHandling = NullValueHandling.Ignore;

            var botAccount = new ChannelAccount
            {
                Id   = "BotId",
                Name = "BotName"
            };

            var userAccount = new ChannelAccount
            {
                Id   = "UserId",
                Name = "UserName"
            };

            TestDelegatingHandler testDelegatingHandler = new TestDelegatingHandler((request) =>
            {
                string data = (request.Content as StringContent).ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
                ConversationParameters receivedRequest = JsonConvert.DeserializeObject <ConversationParameters>(data, serializerSettings);

                Assert.AreEqual(receivedRequest.Bot.Id, botAccount.Id);
                Assert.IsNotNull(receivedRequest.Members);
                Assert.IsTrue(receivedRequest.Members.Count == 1);
                Assert.AreEqual(receivedRequest.Members[0].Id, userAccount.Id);

                TeamsChannelData channelData = JsonConvert.DeserializeObject <TeamsChannelData>(receivedRequest.ChannelData.ToString());

                Assert.IsNotNull(channelData);
                Assert.IsNotNull(channelData.Tenant);
                Assert.IsNotNull(channelData.Tenant.Id);
                Assert.AreEqual(channelData.Tenant.Id, "TestTenantId");

                ConversationResourceResponse resourceResponse = new ConversationResourceResponse()
                {
                    Id = "TestId"
                };
                StringContent responseContent = new StringContent(JsonConvert.SerializeObject(resourceResponse));
                var response     = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = responseContent;
                return(Task.FromResult(response));
            });

            ConnectorClient conClient = new ConnectorClient(new Uri("https://testservice.com"), "Test", "Test", testDelegatingHandler);

            Assert.IsTrue(conClient.Conversations.CreateOrGetDirectConversation(botAccount, userAccount, "TestTenantId").Id == "TestId");
        }
Пример #28
0
        /// <summary>
        /// Configures the current activity to generate a notification within Teams.
        /// </summary>
        /// <param name="activity"> The current activity. </param>
        public static void TeamsNotifyUser(this IActivity activity)
        {
            var teamsChannelData = activity.ChannelData as TeamsChannelData;

            if (teamsChannelData == null)
            {
                teamsChannelData     = new TeamsChannelData();
                activity.ChannelData = teamsChannelData;
            }

            teamsChannelData.Notification = new NotificationInfo
            {
                Alert = true,
            };
        }
Пример #29
0
        public async Task ChannelData_GetGeneralChannelInvalidChannelDataAsync()
        {
            Activity         sampleActivity = JsonConvert.DeserializeObject <Activity>(File.ReadAllText(@"Jsons\SampleActivityAtMention.json"));
            TeamsChannelData channelData    = JsonConvert.DeserializeObject <TeamsChannelData>(sampleActivity.ChannelData.ToString());

            channelData.Team           = null;
            sampleActivity.ChannelData = JObject.FromObject(channelData);
            await TestHelpers.RunTestPipelineWithActivityAsync(
                sampleActivity,
                (teamsContext) =>
            {
                ChannelInfo generalChannel = teamsContext.GetGeneralChannel();
                return(Task.CompletedTask);
            }).ConfigureAwait(false);
        }
        public async Task SendMessageToChannel(string title, BotSubscription subscription)
        {
            var subscriptionFacade = new SubscriptionFacade();
            var channelData        = new TeamsChannelData
            {
                Channel = new ChannelInfo(subscription.ChannelId),
                Team    = new TeamInfo(subscription.TeamId),
                Tenant  = new TenantInfo(subscription.TenantId)
            };

            var newMessageText = await Build();

            var newMessage = new Activity
            {
                Type = ActivityTypes.Message,
                Text = newMessageText.FixNewLines(),
            };
            var conversationParams = new ConversationParameters(
                isGroup: true,
                bot: null,
                members: null,
                topicName: title,
                activity: (Activity)newMessage,
                channelData: channelData);

            var connector = new ConnectorClient(new Uri(subscription.ServiceUrl), Environment.GetEnvironmentVariable("MicrosoftAppId"), Environment.GetEnvironmentVariable("MicrosoftAppPassword"));

            MicrosoftAppCredentials.TrustServiceUrl(subscription.ServiceUrl, DateTime.MaxValue);

            if (subscription.LastActivity == null)
            {
                var result = await connector.Conversations.CreateConversationAsync(conversationParams);

                subscription.LastActivity = new LastActivity
                {
                    ConversationId = result.Id,
                    ActitityId     = result.ActivityId
                };
            }
            else
            {
                var result = await connector.Conversations.UpdateActivityAsync(subscription.LastActivity.ConversationId, subscription.LastActivity.ActitityId, newMessage);

                subscription.LastActivity.ActitityId = result.Id;
            }

            await subscriptionFacade.UpdateBotSubscription(subscription);
        }