diff --git a/Assembly.cs b/Assembly.cs
new file mode 100644
index 00000000..a8f3fba1
--- /dev/null
+++ b/Assembly.cs
@@ -0,0 +1,13 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by GitVersion.
+//
+// You can modify this code as we will not overwrite it when re-executing GitVersion
+//
+//------------------------------------------------------------------------------
+
+using System.Reflection;
+
+[assembly: AssemblyFileVersion("4.6.9.0")]
+[assembly: AssemblyVersion("4.6.9.0")]
+[assembly: AssemblyInformationalVersion("4.6.9-Adaptive-Rest.11+Branch.Adaptive-Rest.Sha.33bef312cbe591ec20a605b542c227530fd6b9de")]
\ No newline at end of file
diff --git a/Bot.Builder.Community.Samples.sln b/Bot.Builder.Community.Samples.sln
index 4a60181f..57ca6bf9 100644
--- a/Bot.Builder.Community.Samples.sln
+++ b/Bot.Builder.Community.Samples.sln
@@ -4,8 +4,6 @@ VisualStudioVersion = 16.0.29318.209
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Location Dialog Sample", "samples\Location Dialog Sample\Location Dialog Sample.csproj", "{10BEDE11-C9DE-4B67-9467-4E48272229C6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Google Adapter Sample", "samples\Google Adapter Sample\Google Adapter Sample.csproj", "{3312C17E-01FA-4390-9B8F-6E7733E0B1E7}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Form Flow Sample", "samples\Form Flow Sample\Form Flow Sample.csproj", "{B982923B-163B-49FD-960B-0DF5BB6E6562}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cortana Assistant Alexa Sample", "samples\Cortana Assistant Alexa Sample\Cortana Assistant Alexa Sample.csproj", "{2FD06287-25A1-47A6-BB85-2E508E543E66}"
@@ -24,6 +22,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RingCentral Adapter Sample"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Alexa Adapter Sample", "samples\Alexa Adapter Sample\Alexa Adapter Sample.csproj", "{53BCE929-C57D-4AF9-91C9-FCA5C7E2CE41}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Google Adapter Sample", "samples\Google Adapter Sample\Google Adapter Sample.csproj", "{89A6903F-2911-4A27-B178-EA28D697D46B}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Zoom Adapter Sample", "samples\Zoom Adapter Sample\Zoom Adapter Sample.csproj", "{D5F5BFB0-CDEC-4980-9EBB-864724B6B2B6}"
EndProject
Global
@@ -32,14 +32,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7927A7EE-C327-4403-A5F4-B5E2C0BF4D47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7927A7EE-C327-4403-A5F4-B5E2C0BF4D47}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7927A7EE-C327-4403-A5F4-B5E2C0BF4D47}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7927A7EE-C327-4403-A5F4-B5E2C0BF4D47}.Release|Any CPU.Build.0 = Release|Any CPU
{10BEDE11-C9DE-4B67-9467-4E48272229C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10BEDE11-C9DE-4B67-9467-4E48272229C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10BEDE11-C9DE-4B67-9467-4E48272229C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10BEDE11-C9DE-4B67-9467-4E48272229C6}.Release|Any CPU.Build.0 = Release|Any CPU
- {3312C17E-01FA-4390-9B8F-6E7733E0B1E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3312C17E-01FA-4390-9B8F-6E7733E0B1E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3312C17E-01FA-4390-9B8F-6E7733E0B1E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3312C17E-01FA-4390-9B8F-6E7733E0B1E7}.Release|Any CPU.Build.0 = Release|Any CPU
{B982923B-163B-49FD-960B-0DF5BB6E6562}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B982923B-163B-49FD-960B-0DF5BB6E6562}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B982923B-163B-49FD-960B-0DF5BB6E6562}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -60,6 +60,14 @@ Global
{55F863F1-9EC8-420C-9099-BD8378D72F18}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55F863F1-9EC8-420C-9099-BD8378D72F18}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55F863F1-9EC8-420C-9099-BD8378D72F18}.Release|Any CPU.Build.0 = Release|Any CPU
+ {37ED6FBD-12B5-40CF-837F-6F75ACC65484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {37ED6FBD-12B5-40CF-837F-6F75ACC65484}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37ED6FBD-12B5-40CF-837F-6F75ACC65484}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {37ED6FBD-12B5-40CF-837F-6F75ACC65484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {89A6903F-2911-4A27-B178-EA28D697D46B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {89A6903F-2911-4A27-B178-EA28D697D46B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {89A6903F-2911-4A27-B178-EA28D697D46B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {89A6903F-2911-4A27-B178-EA28D697D46B}.Release|Any CPU.Build.0 = Release|Any CPU
{D1C3490D-477A-4D79-A41C-4D15CA9DC183}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D1C3490D-477A-4D79-A41C-4D15CA9DC183}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1C3490D-477A-4D79-A41C-4D15CA9DC183}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Bot.Builder.Community.sln b/Bot.Builder.Community.sln
index a1bdb2da..5efb76de 100644
--- a/Bot.Builder.Community.sln
+++ b/Bot.Builder.Community.sln
@@ -71,12 +71,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Twitter.Tests", "tests\Bot.Builder.Community.Adapters.Tests\Bot.Builder.Community.Adapters.Twitter.Tests.csproj", "{873C9B8C-2677-4586-89D3-C80FB1263596}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.RingCentral", "libraries\Bot.Builder.Community.Adapters.RingCentral\Bot.Builder.Community.Adapters.RingCentral.csproj", "{F0C1BE0F-D1F1-4104-8617-4E87291C1416}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.RingCentral.Tests", "tests\Bot.Builder.Community.Adapters.RingCentral.Tests\Bot.Builder.Community.Adapters.RingCentral.Tests.csproj", "{441E05D3-43A9-4C4B-AB5B-06AF27742885}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Dialogs.Adaptive.Rest", "libraries\Bot.Builder.Community.Dialogs.Adaptive.Rest\Bot.Builder.Community.Dialogs.Adaptive.Rest.csproj", "{63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Dialogs.Adaptive.Rest.Tests", "tests\Bot.Builder.Community.Dialogs.Adaptive.Rest.Tests\Bot.Builder.Community.Dialogs.Adaptive.Rest.Tests.csproj", "{E14564ED-C929-4F43-BD1B-46D20DFAAF3A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.RingCentral", "libraries\Bot.Builder.Community.Adapters.RingCentral\Bot.Builder.Community.Adapters.RingCentral.csproj", "{042403D4-2D7B-482E-87DD-51B1CFDEC210}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Zoom", "libraries\Bot.Builder.Community.Adapters.Zoom\Bot.Builder.Community.Adapters.Zoom.csproj", "{DA1A84A1-D921-412F-A855-93FC9A525D27}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Infobip", "libraries\Bot.Builder.Community.Adapters.Infobip\Bot.Builder.Community.Adapters.Infobip.csproj", "{0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Infobip.Tests", "tests\Bot.Builder.Community.Adapters.Infobip.Tests\Bot.Builder.Community.Adapters.Infobip.Tests.csproj", "{6356EF6A-406B-4B2E-A229-C1D042C2FCF6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Google.Core", "libraries\Bot.Builder.Community.Adapters.Google.Core\Bot.Builder.Community.Adapters.Google.Core.csproj", "{7856219A-3537-4F81-86A8-4CB020BD2B9A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
@@ -275,14 +285,6 @@ Global
{873C9B8C-2677-4586-89D3-C80FB1263596}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{873C9B8C-2677-4586-89D3-C80FB1263596}.Release|Any CPU.ActiveCfg = Release|Any CPU
{873C9B8C-2677-4586-89D3-C80FB1263596}.Release|Any CPU.Build.0 = Release|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Documentation|Any CPU.Build.0 = Debug|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416}.Release|Any CPU.Build.0 = Release|Any CPU
{441E05D3-43A9-4C4B-AB5B-06AF27742885}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{441E05D3-43A9-4C4B-AB5B-06AF27742885}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{441E05D3-43A9-4C4B-AB5B-06AF27742885}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -291,6 +293,30 @@ Global
{441E05D3-43A9-4C4B-AB5B-06AF27742885}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{441E05D3-43A9-4C4B-AB5B-06AF27742885}.Release|Any CPU.ActiveCfg = Release|Any CPU
{441E05D3-43A9-4C4B-AB5B-06AF27742885}.Release|Any CPU.Build.0 = Release|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Documentation|Any CPU.Build.0 = Debug|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Documentation|Any CPU.Build.0 = Debug|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Documentation|Any CPU.Build.0 = Debug|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210}.Release|Any CPU.Build.0 = Release|Any CPU
{DA1A84A1-D921-412F-A855-93FC9A525D27}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{DA1A84A1-D921-412F-A855-93FC9A525D27}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{DA1A84A1-D921-412F-A855-93FC9A525D27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -299,6 +325,30 @@ Global
{DA1A84A1-D921-412F-A855-93FC9A525D27}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{DA1A84A1-D921-412F-A855-93FC9A525D27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DA1A84A1-D921-412F-A855-93FC9A525D27}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Documentation|Any CPU.Build.0 = Debug|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Documentation|Any CPU.Build.0 = Debug|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Documentation|Any CPU.Build.0 = Debug|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -333,9 +383,14 @@ Global
{0311DB07-D853-4649-9DA1-F1CA2C20B406} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
{ACEAA5FA-1DE3-4E3A-B435-A988A4276231} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
{873C9B8C-2677-4586-89D3-C80FB1263596} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
- {F0C1BE0F-D1F1-4104-8617-4E87291C1416} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
{441E05D3-43A9-4C4B-AB5B-06AF27742885} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
+ {63AFED39-D4AC-4B1E-94B9-EF58EE687B3D} = {08759E28-8592-4EBA-9A07-19A5BED3FB0C}
+ {E14564ED-C929-4F43-BD1B-46D20DFAAF3A} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
+ {042403D4-2D7B-482E-87DD-51B1CFDEC210} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
{DA1A84A1-D921-412F-A855-93FC9A525D27} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
+ {7856219A-3537-4F81-86A8-4CB020BD2B9A} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
+ {0F8ADFCE-3E91-4F85-BC0B-4B0A5C98797C} = {BF310E8A-8DA1-441F-90E9-DE0E66553048}
+ {6356EF6A-406B-4B2E-A229-C1D042C2FCF6} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9FE3B75E-BA2B-45BC-BBF0-DDA8BA10C4F0}
diff --git a/assistant-sdk-getting-started.jpg b/assistant-sdk-getting-started.jpg
new file mode 100644
index 00000000..c6f93f3f
Binary files /dev/null and b/assistant-sdk-getting-started.jpg differ
diff --git a/libraries/Bot.Builder.Community.Adapters.Alexa.Core/AlexaRequestMapper.cs b/libraries/Bot.Builder.Community.Adapters.Alexa.Core/AlexaRequestMapper.cs
index d3f9b4ec..eed33213 100644
--- a/libraries/Bot.Builder.Community.Adapters.Alexa.Core/AlexaRequestMapper.cs
+++ b/libraries/Bot.Builder.Community.Adapters.Alexa.Core/AlexaRequestMapper.cs
@@ -1,412 +1,412 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Security;
-using System.Xml;
-using System.Xml.Linq;
-using Alexa.NET.Request;
-using Alexa.NET.Request.Type;
-using Alexa.NET.Response;
-using Bot.Builder.Community.Adapters.Alexa.Core.Attachments;
-using Bot.Builder.Community.Adapters.Alexa.Core.Helpers;
-using Microsoft.Bot.Schema;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using Microsoft.Rest;
-using AlexaResponse = Alexa.NET.Response;
-
-namespace Bot.Builder.Community.Adapters.Alexa.Core
-{
- public class AlexaRequestMapper
- {
- private AlexaRequestMapperOptions _options;
- private ILogger _logger;
-
- public AlexaRequestMapper(AlexaRequestMapperOptions options = null, ILogger logger = null)
- {
- _options = options ?? new AlexaRequestMapperOptions();
- _logger = logger ?? NullLogger.Instance;
- }
-
- ///
- /// Returns an Activity object created by using properties on a SkillRequest.
- /// A base set of properties based on the SkillRequest are applied to a new Activity object
- /// for all request types with the activity type, and additional properties, set depending
- /// on the specific request type.
- ///
- /// The SkillRequest to be used to create an Activity object.
- /// Activity
- public Activity RequestToActivity(SkillRequest skillRequest)
- {
- if (skillRequest.Request == null)
- {
- throw new ValidationException("Bad Request. Skill request missing Request property.");
- }
-
- switch (skillRequest.Request)
- {
- case IntentRequest intentRequest:
- if (intentRequest.Intent.Slots != null && intentRequest.Intent.Slots.ContainsKey(_options.DefaultIntentSlotName))
- {
- return RequestToMessageActivity(skillRequest, intentRequest);
- }
- else
- {
- if (intentRequest.Intent.Name == "AMAZON.StopIntent")
- {
- return RequestToEndOfConversationActivity(skillRequest);
- }
-
- return RequestToEventActivity(skillRequest);
- }
- case LaunchRequest launchRequest:
- return RequestToConversationUpdateActivity(skillRequest);
- case SessionEndedRequest sessionEndedRequest:
- return RequestToEndOfConversationActivity(skillRequest);
- default:
- return RequestToEventActivity(skillRequest);
- }
- }
-
- ///
- /// Creates a SkillResponse based on an Activity and original SkillRequest.
- ///
- /// The Activity to use to create the SkillResponse
- /// Original SkillRequest received from Alexa Skills service. This is used
- /// to check if the original request was a SessionEndedRequest which should not return a response.
- /// SkillResponse
- public SkillResponse ActivityToResponse(Activity activity, SkillRequest alexaRequest)
- {
- var response = new SkillResponse()
- {
- Version = "1.0",
- Response = new ResponseBody()
- };
-
- if (activity == null || activity.Type != ActivityTypes.Message || alexaRequest.Request is SessionEndedRequest)
- {
- response.Response.ShouldEndSession = true;
- response.Response.OutputSpeech = new PlainTextOutputSpeech
- {
- Text = string.Empty
- };
- return response;
- }
-
- if (!string.IsNullOrEmpty(activity.Speak))
- {
- response.Response.OutputSpeech = new SsmlOutputSpeech(activity.Speak);
- }
- else
- {
- response.Response.OutputSpeech = new PlainTextOutputSpeech(activity.Text);
- }
-
- ProcessActivityAttachments(activity, response);
-
- if (ShouldSetEndSession(response))
- {
- switch (activity.InputHint)
- {
- case InputHints.IgnoringInput:
- response.Response.ShouldEndSession = true;
- break;
- case InputHints.ExpectingInput:
- response.Response.ShouldEndSession = false;
- response.Response.Reprompt = new Reprompt(activity.Text);
- break;
- default:
- response.Response.ShouldEndSession = _options.ShouldEndSessionByDefault;
- break;
- }
- }
-
- return response;
- }
-
- ///
- /// Concatenates activities into a single activity. Uses the last activity in the list as the base activity.
- /// If any of the activities being process contain an outer SSML speak tag within the value of the Speak property,
- /// these are removed from the individual activities and a tag is wrapped around the resulting
- /// concatenated string. An SSML strong break tag is added between activity content. For more infomation
- /// about the supported SSML for Alexa see
- /// https://developer.amazon.com/en-US/docs/alexa/custom-skills/speech-synthesis-markup-language-ssml-reference.html#break
- ///
- /// The list of one or more outgoing activities
- /// Activity
- public Activity MergeActivities(IList activities)
- {
- var messageActivities = activities?.Where(a => a.Type == ActivityTypes.Message).ToList();
-
- if (messageActivities == null || messageActivities.Count == 0)
- {
- return null;
- }
-
- var activity = messageActivities.Last();
-
- if (messageActivities.Any(a => !string.IsNullOrEmpty(a.Speak)))
- {
- var speakText = string.Join("", messageActivities
- .Select(a => !string.IsNullOrEmpty(a.Speak) ? StripSpeakTag(a.Speak) : NormalizeActivityText(a.TextFormat, a.Text, forSsml: true))
- .Where(s => !string.IsNullOrEmpty(s))
- .Select(s => s));
-
- activity.Speak = $"{speakText}";
- }
-
- activity.Text = string.Join(". ", messageActivities
- .Select(a => NormalizeActivityText(a.TextFormat, a.Text, forSsml: false))
- .Where(s => !string.IsNullOrEmpty(s))
- .Select(s => s.Trim(new char[] { ' ', '.' })));
-
- activity.Attachments = messageActivities.Where(x => x.Attachments != null).SelectMany(x => x.Attachments).ToList();
-
- return activity;
- }
-
- private Activity RequestToEndOfConversationActivity(SkillRequest skillRequest)
- {
- var activity = Activity.CreateEndOfConversationActivity() as Activity;
- activity = SetGeneralActivityProperties(activity, skillRequest);
- return activity;
- }
-
- private Activity RequestToConversationUpdateActivity(SkillRequest skillRequest)
- {
- var activity = Activity.CreateConversationUpdateActivity() as Activity;
- activity = SetGeneralActivityProperties(activity, skillRequest);
- activity.MembersAdded.Add(new ChannelAccount(id: skillRequest.Session.User.UserId));
- return activity;
- }
-
- private Activity RequestToMessageActivity(SkillRequest skillRequest, IntentRequest intentRequest)
- {
- var activity = Activity.CreateMessageActivity() as Activity;
- activity = SetGeneralActivityProperties(activity, skillRequest);
- activity.Text = intentRequest.Intent.Slots[_options.DefaultIntentSlotName].Value;
- activity.Locale = intentRequest.Locale;
- return activity;
- }
-
- private Activity RequestToEventActivity(SkillRequest skillRequest)
- {
- var activity = Activity.CreateEventActivity() as Activity;
- activity = SetGeneralActivityProperties(activity, skillRequest);
- activity.Name = skillRequest.Request.Type;
-
- switch (skillRequest.Request)
- {
- case IntentRequest skillIntentRequest:
- activity.Value = skillIntentRequest;
- break;
- case AccountLinkSkillEventRequest accountLinkSkillEventRequest:
- activity.Value = accountLinkSkillEventRequest;
- break;
- case AudioPlayerRequest audioPlayerRequest:
- activity.Value = audioPlayerRequest;
- break;
- case DisplayElementSelectedRequest displayElementSelectedRequest:
- activity.Value = displayElementSelectedRequest;
- break;
- case PermissionSkillEventRequest permissionSkillEventRequest:
- activity.Value = permissionSkillEventRequest;
- break;
- case PlaybackControllerRequest playbackControllerRequest:
- activity.Value = playbackControllerRequest;
- break;
- case SkillEventRequest skillEventRequest:
- activity.Value = skillEventRequest;
- break;
- case SystemExceptionRequest systemExceptionRequest:
- activity.Value = systemExceptionRequest;
- break;
- default:
- activity.Value = skillRequest.Request;
- break;
- }
-
- return activity;
- }
-
- ///
- /// Set the general properties, based on an incoming SkillRequest that can be applied
- /// irresepective of the resulting Activity type.
- ///
- /// The Activity on which to set the properties on.
- /// The incoming SkillRequest
- /// Activity
- private Activity SetGeneralActivityProperties(Activity activity, SkillRequest skillRequest)
- {
- var alexaSystem = skillRequest.Context.System;
-
- activity.ChannelId = _options.ChannelId;
- activity.Id = skillRequest.Request.RequestId;
- activity.DeliveryMode = DeliveryModes.ExpectReplies;
- activity.ServiceUrl = _options.ServiceUrl ?? $"{alexaSystem.ApiEndpoint}?token={alexaSystem.ApiAccessToken}";
- activity.Recipient = new ChannelAccount(alexaSystem.Application.ApplicationId);
- activity.From = new ChannelAccount(alexaSystem.Person?.PersonId ?? alexaSystem.User.UserId);
- activity.Conversation = new ConversationAccount(isGroup: false, id: skillRequest.Session?.SessionId ?? skillRequest.Request.RequestId);
- activity.Timestamp = skillRequest.Request.Timestamp.ToUniversalTime();
- activity.ChannelData = skillRequest;
-
- return activity;
- }
-
- ///
- /// Checks a string to see if it is XML and if the outer tag is a speak tag
- /// indicating it is SSML. If an outer speak tag is found, the inner XML is
- /// returned, otherwise the original string is returned
- ///
- /// String to be checked for an outer speak XML tag and stripped if found
- private string StripSpeakTag(string speakText)
- {
- try
- {
- var speakSsmlDoc = XDocument.Parse(speakText);
- if (speakSsmlDoc != null && speakSsmlDoc.Root.Name.ToString().ToLowerInvariant() == "speak")
- {
- using (var reader = speakSsmlDoc.Root.CreateReader())
- {
- reader.MoveToContent();
- return reader.ReadInnerXml();
- }
- }
-
- return speakText;
- }
- catch (XmlException)
- {
- return speakText;
- }
- }
-
- private string NormalizeActivityText(string textFormat, string text, bool forSsml)
- {
- if (string.IsNullOrWhiteSpace(text))
- {
- return string.Empty;
- }
-
- // Default to markdown if it isn't specified.
- if (textFormat == null)
- {
- textFormat = TextFormatTypes.Markdown;
- }
-
- string plainText;
- if (textFormat.Equals(TextFormatTypes.Plain, StringComparison.Ordinal))
- {
- plainText = text;
- }
- else if (textFormat.Equals(TextFormatTypes.Markdown, StringComparison.Ordinal))
- {
- plainText = AlexaMarkdownToPlaintextRenderer.Render(text);
- }
- else // xml format or other unknown and unsupported format.
- {
- plainText = string.Empty;
- }
-
- if (forSsml && !SecurityElement.IsValidText(plainText))
- {
- plainText = SecurityElement.Escape(plainText);
- }
- return plainText;
- }
-
- ///
- /// Under certain circumstances, such as the inclusion of certain types of directives
- /// on a response, should force the 'ShouldEndSession' property not be included on
- /// an outgoing response. This method determines if this property is allowed to have
- /// a value assigned.
- ///
- /// Boolean indicating if the 'ShouldEndSession' property can be populated on the response.'
- /// bool
- private bool ShouldSetEndSession(SkillResponse response)
- {
- if (response.Response.Directives.Any(d => d is IEndSessionDirective))
- {
- return false;
- }
-
- return true;
- }
-
- ///
- /// Processes any attachments on the Activity in order to amend the SkillResponse appropriately.
- /// The current process for processing activity attachments is;
- /// 1. Check for an instance of a SigninCard. Set the Card property on the SkillResponse to a LinkAccountCard.
- /// 2. If no SigninCard is found, check for an instance of a HeroCard. Transform the first instance of a HeroCard
- /// into an Alexa Card and set the Card property on the response.
- /// 3. If no Signin or HeroCard instances were found, check for Alexa specific CardAttachment and
- /// set the content of the Card property on the response.
- /// 4. Look for any instances of DirectiveAttachments and add the appropriate directives to the response.
- ///
- /// The Activity for which to process activities.
- /// The SkillResponse to be modified based on the attachments on the Activity object.
- private void ProcessActivityAttachments(Activity activity, SkillResponse response)
- {
- activity.ConvertAttachmentContent();
-
- var bfCard = activity.Attachments?.FirstOrDefault(a => a.ContentType == HeroCard.ContentType || a.ContentType == SigninCard.ContentType);
-
- if (bfCard != null)
- {
- switch (bfCard.Content)
- {
- case SigninCard signinCard:
- response.Response.Card = new LinkAccountCard();
- break;
- case HeroCard heroCard:
- response.Response.Card = CreateAlexaCardFromHeroCard(heroCard);
- break;
- }
- }
- else
- {
- var cardAttachment = activity.Attachments?.FirstOrDefault(a => a.ContentType == AlexaAttachmentContentTypes.Card);
- if (cardAttachment != null)
- {
- response.Response.Card = cardAttachment.Content as ICard;
- }
- }
-
- var directiveAttachments = activity.Attachments?.Where(a => a.ContentType == AlexaAttachmentContentTypes.Directive).ToList();
- if (directiveAttachments != null && directiveAttachments.Any())
- {
- response.Response.Directives = directiveAttachments.Select(d => d.Content as IDirective).ToList();
- }
- }
-
- ///
- /// Uses a HeroCard to create an instance of ICard (either StandardCard or SimpleCard).
- ///
- /// The HeroCard to be transformed.
- /// An instance of ICard - either SimpleCard or StandardCard.
- private ICard CreateAlexaCardFromHeroCard(HeroCard heroCard)
- {
- if (heroCard.Images != null && heroCard.Images.Any())
- {
- return new StandardCard()
- {
- Content = heroCard.Text,
- Image = new AlexaResponse.CardImage()
- {
- SmallImageUrl = heroCard.Images[0].Url,
- LargeImageUrl = heroCard.Images.Count > 1 ? heroCard.Images[1].Url : null
- },
- Title = heroCard.Title
- };
- }
- else
- {
- return new SimpleCard()
- {
- Content = heroCard.Text,
- Title = heroCard.Title
- };
- }
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security;
+using System.Xml;
+using System.Xml.Linq;
+using Alexa.NET.Request;
+using Alexa.NET.Request.Type;
+using Alexa.NET.Response;
+using Bot.Builder.Community.Adapters.Alexa.Core.Attachments;
+using Bot.Builder.Community.Adapters.Alexa.Core.Helpers;
+using Microsoft.Bot.Schema;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Rest;
+using AlexaResponse = Alexa.NET.Response;
+
+namespace Bot.Builder.Community.Adapters.Alexa.Core
+{
+ public class AlexaRequestMapper
+ {
+ private AlexaRequestMapperOptions _options;
+ private ILogger _logger;
+
+ public AlexaRequestMapper(AlexaRequestMapperOptions options = null, ILogger logger = null)
+ {
+ _options = options ?? new AlexaRequestMapperOptions();
+ _logger = logger ?? NullLogger.Instance;
+ }
+
+ ///
+ /// Returns an Activity object created by using properties on a SkillRequest.
+ /// A base set of properties based on the SkillRequest are applied to a new Activity object
+ /// for all request types with the activity type, and additional properties, set depending
+ /// on the specific request type.
+ ///
+ /// The SkillRequest to be used to create an Activity object.
+ /// Activity
+ public Activity RequestToActivity(SkillRequest skillRequest)
+ {
+ if (skillRequest.Request == null)
+ {
+ throw new ValidationException("Bad Request. Skill request missing Request property.");
+ }
+
+ switch (skillRequest.Request)
+ {
+ case IntentRequest intentRequest:
+ if (intentRequest.Intent.Slots != null && intentRequest.Intent.Slots.ContainsKey(_options.DefaultIntentSlotName))
+ {
+ return RequestToMessageActivity(skillRequest, intentRequest);
+ }
+ else
+ {
+ if (intentRequest.Intent.Name == "AMAZON.StopIntent")
+ {
+ return RequestToEndOfConversationActivity(skillRequest);
+ }
+
+ return RequestToEventActivity(skillRequest);
+ }
+ case LaunchRequest launchRequest:
+ return RequestToConversationUpdateActivity(skillRequest);
+ case SessionEndedRequest sessionEndedRequest:
+ return RequestToEndOfConversationActivity(skillRequest);
+ default:
+ return RequestToEventActivity(skillRequest);
+ }
+ }
+
+ ///
+ /// Creates a SkillResponse based on an Activity and original SkillRequest.
+ ///
+ /// The Activity to use to create the SkillResponse
+ /// Original SkillRequest received from Alexa Skills service. This is used
+ /// to check if the original request was a SessionEndedRequest which should not return a response.
+ /// SkillResponse
+ public SkillResponse ActivityToResponse(Activity activity, SkillRequest alexaRequest)
+ {
+ var response = new SkillResponse()
+ {
+ Version = "1.0",
+ Response = new ResponseBody()
+ };
+
+ if (activity == null || activity.Type != ActivityTypes.Message || alexaRequest.Request is SessionEndedRequest)
+ {
+ response.Response.ShouldEndSession = true;
+ response.Response.OutputSpeech = new PlainTextOutputSpeech
+ {
+ Text = string.Empty
+ };
+ return response;
+ }
+
+ if (!string.IsNullOrEmpty(activity.Speak))
+ {
+ response.Response.OutputSpeech = new SsmlOutputSpeech(activity.Speak);
+ }
+ else
+ {
+ response.Response.OutputSpeech = new PlainTextOutputSpeech(activity.Text);
+ }
+
+ ProcessActivityAttachments(activity, response);
+
+ if (ShouldSetEndSession(response))
+ {
+ switch (activity.InputHint)
+ {
+ case InputHints.IgnoringInput:
+ response.Response.ShouldEndSession = true;
+ break;
+ case InputHints.ExpectingInput:
+ response.Response.ShouldEndSession = false;
+ response.Response.Reprompt = new Reprompt(activity.Text);
+ break;
+ default:
+ response.Response.ShouldEndSession = _options.ShouldEndSessionByDefault;
+ break;
+ }
+ }
+
+ return response;
+ }
+
+ ///
+ /// Concatenates activities into a single activity. Uses the last activity in the list as the base activity.
+ /// If any of the activities being process contain an outer SSML speak tag within the value of the Speak property,
+ /// these are removed from the individual activities and a tag is wrapped around the resulting
+ /// concatenated string. An SSML strong break tag is added between activity content. For more infomation
+ /// about the supported SSML for Alexa see
+ /// https://developer.amazon.com/en-US/docs/alexa/custom-skills/speech-synthesis-markup-language-ssml-reference.html#break
+ ///
+ /// The list of one or more outgoing activities
+ /// Activity
+ public Activity MergeActivities(IList activities)
+ {
+ var messageActivities = activities?.Where(a => a.Type == ActivityTypes.Message).ToList();
+
+ if (messageActivities == null || messageActivities.Count == 0)
+ {
+ return null;
+ }
+
+ var activity = messageActivities.Last();
+
+ if (messageActivities.Any(a => !string.IsNullOrEmpty(a.Speak)))
+ {
+ var speakText = string.Join("", messageActivities
+ .Select(a => !string.IsNullOrEmpty(a.Speak) ? StripSpeakTag(a.Speak) : NormalizeActivityText(a.TextFormat, a.Text, forSsml: true))
+ .Where(s => !string.IsNullOrEmpty(s))
+ .Select(s => s));
+
+ activity.Speak = $"{speakText}";
+ }
+
+ activity.Text = string.Join(". ", messageActivities
+ .Select(a => NormalizeActivityText(a.TextFormat, a.Text, forSsml: false))
+ .Where(s => !string.IsNullOrEmpty(s))
+ .Select(s => s.Trim(new char[] { ' ', '.' })));
+
+ activity.Attachments = messageActivities.Where(x => x.Attachments != null).SelectMany(x => x.Attachments).ToList();
+
+ return activity;
+ }
+
+ private Activity RequestToEndOfConversationActivity(SkillRequest skillRequest)
+ {
+ var activity = Activity.CreateEndOfConversationActivity() as Activity;
+ activity = SetGeneralActivityProperties(activity, skillRequest);
+ return activity;
+ }
+
+ private Activity RequestToConversationUpdateActivity(SkillRequest skillRequest)
+ {
+ var activity = Activity.CreateConversationUpdateActivity() as Activity;
+ activity = SetGeneralActivityProperties(activity, skillRequest);
+ activity.MembersAdded.Add(new ChannelAccount(id: skillRequest.Session.User.UserId));
+ return activity;
+ }
+
+ private Activity RequestToMessageActivity(SkillRequest skillRequest, IntentRequest intentRequest)
+ {
+ var activity = Activity.CreateMessageActivity() as Activity;
+ activity = SetGeneralActivityProperties(activity, skillRequest);
+ activity.Text = intentRequest.Intent.Slots[_options.DefaultIntentSlotName].Value;
+ activity.Locale = intentRequest.Locale;
+ return activity;
+ }
+
+ private Activity RequestToEventActivity(SkillRequest skillRequest)
+ {
+ var activity = Activity.CreateEventActivity() as Activity;
+ activity = SetGeneralActivityProperties(activity, skillRequest);
+ activity.Name = skillRequest.Request.Type;
+
+ switch (skillRequest.Request)
+ {
+ case IntentRequest skillIntentRequest:
+ activity.Value = skillIntentRequest;
+ break;
+ case AccountLinkSkillEventRequest accountLinkSkillEventRequest:
+ activity.Value = accountLinkSkillEventRequest;
+ break;
+ case AudioPlayerRequest audioPlayerRequest:
+ activity.Value = audioPlayerRequest;
+ break;
+ case DisplayElementSelectedRequest displayElementSelectedRequest:
+ activity.Value = displayElementSelectedRequest;
+ break;
+ case PermissionSkillEventRequest permissionSkillEventRequest:
+ activity.Value = permissionSkillEventRequest;
+ break;
+ case PlaybackControllerRequest playbackControllerRequest:
+ activity.Value = playbackControllerRequest;
+ break;
+ case SkillEventRequest skillEventRequest:
+ activity.Value = skillEventRequest;
+ break;
+ case SystemExceptionRequest systemExceptionRequest:
+ activity.Value = systemExceptionRequest;
+ break;
+ default:
+ activity.Value = skillRequest.Request;
+ break;
+ }
+
+ return activity;
+ }
+
+ ///
+ /// Set the general properties, based on an incoming SkillRequest that can be applied
+ /// irresepective of the resulting Activity type.
+ ///
+ /// The Activity on which to set the properties on.
+ /// The incoming SkillRequest
+ /// Activity
+ private Activity SetGeneralActivityProperties(Activity activity, SkillRequest skillRequest)
+ {
+ var alexaSystem = skillRequest.Context.System;
+
+ activity.ChannelId = _options.ChannelId;
+ activity.Id = skillRequest.Request.RequestId;
+ activity.DeliveryMode = DeliveryModes.ExpectReplies;
+ activity.ServiceUrl = _options.ServiceUrl ?? $"{alexaSystem.ApiEndpoint}?token={alexaSystem.ApiAccessToken}";
+ activity.Recipient = new ChannelAccount(alexaSystem.Application.ApplicationId);
+ activity.From = new ChannelAccount(alexaSystem.Person?.PersonId ?? alexaSystem.User.UserId);
+ activity.Conversation = new ConversationAccount(isGroup: false, id: skillRequest.Session?.SessionId ?? skillRequest.Request.RequestId);
+ activity.Timestamp = skillRequest.Request.Timestamp.ToUniversalTime();
+ activity.ChannelData = skillRequest;
+
+ return activity;
+ }
+
+ ///
+ /// Checks a string to see if it is XML and if the outer tag is a speak tag
+ /// indicating it is SSML. If an outer speak tag is found, the inner XML is
+ /// returned, otherwise the original string is returned
+ ///
+ /// String to be checked for an outer speak XML tag and stripped if found
+ private string StripSpeakTag(string speakText)
+ {
+ try
+ {
+ var speakSsmlDoc = XDocument.Parse(speakText);
+ if (speakSsmlDoc != null && speakSsmlDoc.Root.Name.ToString().ToLowerInvariant() == "speak")
+ {
+ using (var reader = speakSsmlDoc.Root.CreateReader())
+ {
+ reader.MoveToContent();
+ return reader.ReadInnerXml();
+ }
+ }
+
+ return speakText;
+ }
+ catch (XmlException)
+ {
+ return speakText;
+ }
+ }
+
+ private string NormalizeActivityText(string textFormat, string text, bool forSsml)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ {
+ return string.Empty;
+ }
+
+ // Default to markdown if it isn't specified.
+ if (textFormat == null)
+ {
+ textFormat = TextFormatTypes.Markdown;
+ }
+
+ string plainText;
+ if (textFormat.Equals(TextFormatTypes.Plain, StringComparison.Ordinal))
+ {
+ plainText = text;
+ }
+ else if (textFormat.Equals(TextFormatTypes.Markdown, StringComparison.Ordinal))
+ {
+ plainText = AlexaMarkdownToPlaintextRenderer.Render(text);
+ }
+ else // xml format or other unknown and unsupported format.
+ {
+ plainText = string.Empty;
+ }
+
+ if (forSsml && !SecurityElement.IsValidText(plainText))
+ {
+ plainText = SecurityElement.Escape(plainText);
+ }
+ return plainText;
+ }
+
+ ///
+ /// Under certain circumstances, such as the inclusion of certain types of directives
+ /// on a response, should force the 'ShouldEndSession' property not be included on
+ /// an outgoing response. This method determines if this property is allowed to have
+ /// a value assigned.
+ ///
+ /// Boolean indicating if the 'ShouldEndSession' property can be populated on the response.'
+ /// bool
+ private bool ShouldSetEndSession(SkillResponse response)
+ {
+ if (response.Response.Directives.Any(d => d is IEndSessionDirective))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Processes any attachments on the Activity in order to amend the SkillResponse appropriately.
+ /// The current process for processing activity attachments is;
+ /// 1. Check for an instance of a SigninCard. Set the Card property on the SkillResponse to a LinkAccountCard.
+ /// 2. If no SigninCard is found, check for an instance of a HeroCard. Transform the first instance of a HeroCard
+ /// into an Alexa Card and set the Card property on the response.
+ /// 3. If no Signin or HeroCard instances were found, check for Alexa specific CardAttachment and
+ /// set the content of the Card property on the response.
+ /// 4. Look for any instances of DirectiveAttachments and add the appropriate directives to the response.
+ ///
+ /// The Activity for which to process activities.
+ /// The SkillResponse to be modified based on the attachments on the Activity object.
+ private void ProcessActivityAttachments(Activity activity, SkillResponse response)
+ {
+ activity.ConvertAttachmentContent();
+
+ var bfCard = activity.Attachments?.FirstOrDefault(a => a.ContentType == HeroCard.ContentType || a.ContentType == SigninCard.ContentType);
+
+ if (bfCard != null)
+ {
+ switch (bfCard.Content)
+ {
+ case SigninCard signinCard:
+ response.Response.Card = new LinkAccountCard();
+ break;
+ case HeroCard heroCard:
+ response.Response.Card = CreateAlexaCardFromHeroCard(heroCard);
+ break;
+ }
+ }
+ else
+ {
+ var cardAttachment = activity.Attachments?.FirstOrDefault(a => a.ContentType == AlexaAttachmentContentTypes.Card);
+ if (cardAttachment != null)
+ {
+ response.Response.Card = cardAttachment.Content as ICard;
+ }
+ }
+
+ var directiveAttachments = activity.Attachments?.Where(a => a.ContentType == AlexaAttachmentContentTypes.Directive).ToList();
+ if (directiveAttachments != null && directiveAttachments.Any())
+ {
+ response.Response.Directives = directiveAttachments.Select(d => d.Content as IDirective).ToList();
+ }
+ }
+
+ ///
+ /// Uses a HeroCard to create an instance of ICard (either StandardCard or SimpleCard).
+ ///
+ /// The HeroCard to be transformed.
+ /// An instance of ICard - either SimpleCard or StandardCard.
+ private ICard CreateAlexaCardFromHeroCard(HeroCard heroCard)
+ {
+ if (heroCard.Images != null && heroCard.Images.Any())
+ {
+ return new StandardCard()
+ {
+ Content = heroCard.Text,
+ Image = new AlexaResponse.CardImage()
+ {
+ SmallImageUrl = heroCard.Images[0].Url,
+ LargeImageUrl = heroCard.Images.Count > 1 ? heroCard.Images[1].Url : null
+ },
+ Title = heroCard.Title
+ };
+ }
+ else
+ {
+ return new SimpleCard()
+ {
+ Content = heroCard.Text,
+ Title = heroCard.Title
+ };
+ }
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Alexa.Core/Helpers/AlexaMarkdownToPlaintextRenderer.cs b/libraries/Bot.Builder.Community.Adapters.Alexa.Core/Helpers/AlexaMarkdownToPlaintextRenderer.cs
index c439de72..bfc8fa88 100644
--- a/libraries/Bot.Builder.Community.Adapters.Alexa.Core/Helpers/AlexaMarkdownToPlaintextRenderer.cs
+++ b/libraries/Bot.Builder.Community.Adapters.Alexa.Core/Helpers/AlexaMarkdownToPlaintextRenderer.cs
@@ -1,70 +1,70 @@
-using Microsoft.MarkedNet;
-
-namespace Bot.Builder.Community.Adapters.Alexa.Core.Helpers
-{
- ///
- /// Simple Markdown renderer to turn markdown into plain text for Alexa.
- ///
- public static class AlexaMarkdownToPlaintextRenderer
- {
- private static readonly Marked _marked = new Marked(new Options { EscapeHtml = false, Sanitize = false, Mangle = false, Renderer = new RemoveMarkupRenderer() });
-
- public static string Render(string source) => _marked.Parse(source);
-
- private class RemoveMarkupRenderer : MarkdownRenderer
- {
- private const string ListItemMarker = "$$ListItemMarker$$";
-
- public RemoveMarkupRenderer() : base() { }
- public RemoveMarkupRenderer(Options options) : base(options) { }
-
- public override string Blockquote(string quote) => string.Concat(quote, ". ");
- public override string Br() => ". ";
- public override string Code(string code, string lang, bool escaped) => code;
- public override string Codespan(string text) => text;
- public override string Del(string text) => text;
- public override string Em(string text) => text;
- public override string Heading(string text, int level, string raw) => string.Concat(text, ". ");
- public override string Hr() => ". ";
- public override string Html(string html) => string.Empty;
- public override string Image(string href, string title, string text) => title ?? text;
- public override string Link(string href, string title, string text)
- {
- if (title != null)
- {
- return $"{title} {href}";
- }
- else if (text == href)
- {
- // For standard links the title and href will be the same.
- return href;
- }
- return $"{text} {href}";
- }
- public override string List(string body, bool ordered, int start)
- {
- if (ordered)
- {
- for (int marker = start, markerIndex = body.IndexOf(ListItemMarker); markerIndex >= 0; markerIndex = body.IndexOf(ListItemMarker), ++marker)
- {
- body = body.Substring(0, markerIndex) + marker + ". " + body.Substring(markerIndex + ListItemMarker.Length);
- }
- }
- else
- {
- body = body.Replace(ListItemMarker, string.Empty);
- }
- return $"{body.Trim().TrimEnd(',')}. ";
- }
- public override string ListItem(string text) => $"{ListItemMarker}{text}, ";
- public override string Paragraph(string text) => string.Concat(text.Replace("\n", " ").TrimEnd('.'), ". ");
- public override string Strong(string text) => text;
- public override string Table(string header, string body) => string.Empty;
- public override string TableCell(string content, TableCellFlags flags) => string.Empty;
- public override string TableRow(string content) => string.Empty;
- public override string Text(string text) => text.TrimEnd('.');
- public override string Preprocess(string text) => text.Trim();
- public override string Postprocess(string text) => text.Trim();
- }
- }
-}
+using Microsoft.MarkedNet;
+
+namespace Bot.Builder.Community.Adapters.Alexa.Core.Helpers
+{
+ ///
+ /// Simple Markdown renderer to turn markdown into plain text for Alexa.
+ ///
+ public static class AlexaMarkdownToPlaintextRenderer
+ {
+ private static readonly Marked _marked = new Marked(new Options { EscapeHtml = false, Sanitize = false, Mangle = false, Renderer = new RemoveMarkupRenderer() });
+
+ public static string Render(string source) => _marked.Parse(source);
+
+ private class RemoveMarkupRenderer : MarkdownRenderer
+ {
+ private const string ListItemMarker = "$$ListItemMarker$$";
+
+ public RemoveMarkupRenderer() : base() { }
+ public RemoveMarkupRenderer(Options options) : base(options) { }
+
+ public override string Blockquote(string quote) => string.Concat(quote, ". ");
+ public override string Br() => ". ";
+ public override string Code(string code, string lang, bool escaped) => code;
+ public override string Codespan(string text) => text;
+ public override string Del(string text) => text;
+ public override string Em(string text) => text;
+ public override string Heading(string text, int level, string raw) => string.Concat(text, ". ");
+ public override string Hr() => ". ";
+ public override string Html(string html) => string.Empty;
+ public override string Image(string href, string title, string text) => title ?? text;
+ public override string Link(string href, string title, string text)
+ {
+ if (title != null)
+ {
+ return $"{title} {href}";
+ }
+ else if (text == href)
+ {
+ // For standard links the title and href will be the same.
+ return href;
+ }
+ return $"{text} {href}";
+ }
+ public override string List(string body, bool ordered, int start)
+ {
+ if (ordered)
+ {
+ for (int marker = start, markerIndex = body.IndexOf(ListItemMarker); markerIndex >= 0; markerIndex = body.IndexOf(ListItemMarker), ++marker)
+ {
+ body = body.Substring(0, markerIndex) + marker + ". " + body.Substring(markerIndex + ListItemMarker.Length);
+ }
+ }
+ else
+ {
+ body = body.Replace(ListItemMarker, string.Empty);
+ }
+ return $"{body.Trim().TrimEnd(',')}. ";
+ }
+ public override string ListItem(string text) => $"{ListItemMarker}{text}, ";
+ public override string Paragraph(string text) => string.Concat(text.Replace("\n", " ").TrimEnd('.'), ". ");
+ public override string Strong(string text) => text;
+ public override string Table(string header, string body) => string.Empty;
+ public override string TableCell(string content, TableCellFlags flags) => string.Empty;
+ public override string TableRow(string content) => string.Empty;
+ public override string Text(string text) => text.TrimEnd('.');
+ public override string Preprocess(string text) => text.Trim();
+ public override string Postprocess(string text) => text.Trim();
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/AttachmentHelper.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/AttachmentHelper.cs
new file mode 100644
index 00000000..fe30745b
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/AttachmentHelper.cs
@@ -0,0 +1,112 @@
+using System;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Response;
+using Bot.Builder.Community.Adapters.Google.Core.Model.SystemIntents;
+using Microsoft.Bot.Schema;
+using Microsoft.Rest;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using BasicCard = Bot.Builder.Community.Adapters.Google.Core.Model.Response.BasicCard;
+
+namespace Bot.Builder.Community.Adapters.Google.Core.Attachments
+{
+ public static class AttachmentHelper
+ {
+ ///
+ /// Convert all Google specific attachments to their correct type.
+ ///
+ ///
+ public static void ConvertAttachmentContent(this Activity activity)
+ {
+ if (activity == null || activity.Attachments == null)
+ {
+ return;
+ }
+
+ foreach (var attachment in activity.Attachments)
+ {
+ switch (attachment.ContentType)
+ {
+ case HeroCard.ContentType:
+ Convert(attachment);
+ break;
+ case SigninCard.ContentType:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.BasicCard:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.MediaResponse:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.TableCard:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.CarouselIntent:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.ListIntent:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.DateTimeIntent:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.PermissionsIntent:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.PlaceLocationIntent:
+ Convert(attachment);
+ break;
+ case GoogleAttachmentContentTypes.ConfirmationIntent:
+ Convert(attachment);
+ break;
+ }
+ }
+ }
+
+ private static void Convert(Attachment attachment)
+ {
+ try
+ {
+ attachment.Content = attachment.ContentAs();
+ }
+ catch (JsonException ex)
+ {
+ throw new ValidationException($"Failed to convert Google Attachment with ContentType {attachment?.ContentType} to {typeof(T).Name}", ex);
+ }
+ catch
+ {
+ }
+ }
+
+ ///
+ /// Convert the Attachment Content field to the given type. An exception is thrown if the conversion fails.
+ ///
+ public static T ContentAs(this Attachment attachment)
+ {
+ if (attachment == null)
+ throw new ArgumentNullException(nameof(attachment));
+
+ if (attachment.Content == null)
+ {
+ return default;
+ }
+ if (typeof(T).IsValueType)
+ {
+ return (T)System.Convert.ChangeType(attachment.Content, typeof(T));
+ }
+ if (attachment.Content is T)
+ {
+ return (T)attachment.Content;
+ }
+ if (typeof(T) == typeof(byte[]))
+ {
+ return (T)(object)System.Convert.FromBase64String(attachment.Content.ToString());
+ }
+ if (attachment.Content is string)
+ {
+ return JsonConvert.DeserializeObject((string)attachment.Content);
+ }
+ return (T)((JObject)attachment.Content).ToObject();
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/Extensions.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/Extensions.cs
new file mode 100644
index 00000000..f65c0182
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/Extensions.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Response;
+using Bot.Builder.Community.Adapters.Google.Core.Model.SystemIntents;
+using Microsoft.Bot.Schema;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using BasicCard = Bot.Builder.Community.Adapters.Google.Core.Model.Response.BasicCard;
+
+namespace Bot.Builder.Community.Adapters.Google.Core.Attachments
+{
+ public static class Extensions
+ {
+ public static Attachment ToAttachment(this ResponseItem responseItem)
+ {
+ switch (responseItem)
+ {
+ case TableCard tableCard:
+ return CreateAttachment(tableCard, GoogleAttachmentContentTypes.TableCard);
+ case MediaResponse mediaResponse:
+ return CreateAttachment(mediaResponse, GoogleAttachmentContentTypes.MediaResponse);
+ case BasicCard basicCard:
+ return CreateAttachment(basicCard, GoogleAttachmentContentTypes.BasicCard);
+ default:
+ return null;
+ }
+ }
+
+ public static Attachment ToAttachment(this SystemIntent intent)
+ {
+ switch (intent)
+ {
+ case CarouselIntent carouselIntent:
+ return CreateAttachment(carouselIntent, GoogleAttachmentContentTypes.CarouselIntent);
+ case ListIntent listIntent:
+ return CreateAttachment(listIntent, GoogleAttachmentContentTypes.ListIntent);
+ case ConfirmationIntent confirmationIntent:
+ return CreateAttachment(confirmationIntent, GoogleAttachmentContentTypes.ConfirmationIntent);
+ case DateTimeIntent dateTimeIntent:
+ return CreateAttachment(dateTimeIntent, GoogleAttachmentContentTypes.DateTimeIntent);
+ case PermissionsIntent permissionsIntent:
+ return CreateAttachment(permissionsIntent, GoogleAttachmentContentTypes.PermissionsIntent);
+ case PlaceLocationIntent placeLocationIntent:
+ return CreateAttachment(placeLocationIntent, GoogleAttachmentContentTypes.PlaceLocationIntent);
+ default:
+ return null;
+ }
+ }
+
+ private static Attachment CreateAttachment(T card, string contentType)
+ {
+ return new Attachment
+ {
+ Content = JObject.FromObject(card, new JsonSerializer() { NullValueHandling = NullValueHandling.Ignore }),
+ ContentType = contentType,
+ };
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/GoogleAttachmentContentTypes.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/GoogleAttachmentContentTypes.cs
new file mode 100644
index 00000000..1a6c674a
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/Attachments/GoogleAttachmentContentTypes.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Bot.Builder.Community.Adapters.Google.Core.Attachments
+{
+ public static class GoogleAttachmentContentTypes
+ {
+ public const string BasicCard = "application/vhd.bfgoogle.basicCard";
+ public const string TableCard = "application/vhd.bfgoogle.tableCard";
+ public const string MediaResponse = "application/vhd.bfgoogle.media";
+ public const string PermissionsIntent = "application/vhd.bfgoogle.permissions";
+ public const string SigninIntent = "application/vhd.bfgoogle.signin";
+ public const string PlaceLocationIntent = "application/vhd.bfgoogle.placelocation";
+ public const string ListIntent = "application/vhd.bfgoogle.list";
+ public const string CarouselIntent = "application/vhd.bfgoogle.carousel";
+ public const string DateTimeIntent = "application/vhd.bfgoogle.datetime";
+ public const string ConfirmationIntent = "application/vhd.bfgoogle.confirm";
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/Bot.Builder.Community.Adapters.Google.Core.csproj b/libraries/Bot.Builder.Community.Adapters.Google.Core/Bot.Builder.Community.Adapters.Google.Core.csproj
new file mode 100644
index 00000000..43bf3360
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/Bot.Builder.Community.Adapters.Google.Core.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/ConversationRequestMapper.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/ConversationRequestMapper.cs
new file mode 100644
index 00000000..158e47de
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/ConversationRequestMapper.cs
@@ -0,0 +1,170 @@
+using System.Collections.Generic;
+using System.Linq;
+using Bot.Builder.Community.Adapters.Google.Core.Attachments;
+using Bot.Builder.Community.Adapters.Google.Core.Helpers;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Request;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Response;
+using Bot.Builder.Community.Adapters.Google.Core.Model.SystemIntents;
+using Microsoft.Bot.Schema;
+using Microsoft.Extensions.Logging;
+
+namespace Bot.Builder.Community.Adapters.Google.Core
+{
+ public class ConversationRequestMapper : GoogleRequestMapperBase
+ {
+ public ConversationRequestMapper(GoogleRequestMapperOptions options = null, ILogger logger = null) : base(options, logger)
+ {
+ }
+
+ public Activity RequestToActivity(ConversationRequest request)
+ {
+ var activity = new Activity();
+
+ var actionIntent =
+ request.Inputs.FirstOrDefault(i => i.Intent.ToLowerInvariant().StartsWith("actions.intent"))?.Intent;
+
+ switch (actionIntent?.ToLowerInvariant())
+ {
+ case "actions.intent.permission":
+ case "actions.intent.datetime":
+ case "ask_for_sign_in_confirmation":
+ case "actions.intent.place":
+ case "actions.intent.confirmation":
+ activity.Type = ActivityTypes.Event;
+ activity = SetGeneralActivityProperties(activity, request);
+ activity.Name = actionIntent;
+ activity.Value = request;
+ return activity;
+ case "actions.intent.sign_in":
+ activity.Type = ActivityTypes.Event;
+ activity = SetGeneralActivityProperties(activity, request);
+ activity.Name = actionIntent;
+ var signinStatusArgument = request.Inputs.First()?.Arguments?.Where(a => a.Name == "SIGN_IN").FirstOrDefault();
+ var argumentExtension = signinStatusArgument?.Extension;
+ activity.Value = argumentExtension?["status"];
+ return activity;
+ case "actions.intent.option":
+ activity.Type = ActivityTypes.Message;
+ activity = SetGeneralActivityProperties(activity, request);
+ activity.Text = StripInvocation(request.Inputs[0]?.RawInputs[0]?.Query,
+ Options.ActionInvocationName);
+ return activity;
+ }
+
+ var text = StripInvocation(request.Inputs[0]?.RawInputs[0]?.Query, Options.ActionInvocationName);
+
+ if (string.IsNullOrEmpty(text))
+ {
+ activity.Type = ActivityTypes.ConversationUpdate;
+ activity = SetGeneralActivityProperties(activity, request);
+ activity.MembersAdded = new List() { new ChannelAccount() { Id = activity.From.Id } };
+ return activity;
+ }
+
+ activity.Type = ActivityTypes.Message;
+ activity = SetGeneralActivityProperties(activity, request);
+ activity.Text = text;
+ return activity;
+ }
+
+ public ConversationWebhookResponse ActivityToResponse(Activity activity, ConversationRequest request)
+ {
+ var response = new ConversationWebhookResponse
+ {
+ UserStorage = request.User.UserStorage
+ };
+
+ // Send default empty response if no activity or invalid activity type sent
+ if (activity == null || activity.Type != ActivityTypes.Message)
+ {
+ response.ExpectUserResponse = false;
+ return response;
+ }
+
+ activity.ConvertAttachmentContent();
+
+ var simpleResponse = new SimpleResponse
+ {
+ Content = new SimpleResponseContent
+ {
+ DisplayText = activity.Text,
+ Ssml = activity.Speak,
+ TextToSpeech = activity.Text
+ }
+ };
+
+ var processedIntentStatus = ProcessHelperIntentAttachments(activity);
+
+ // If we have a system intent to send - send it - with or without additional simple prompt
+ if (processedIntentStatus.Intent != null)
+ {
+ response.ExpectUserResponse = true;
+ response.ExpectedInputs = new ExpectedInput[]
+ {
+ new ExpectedInput()
+ {
+ PossibleIntents = new SystemIntent[]
+ {
+ processedIntentStatus.Intent
+ },
+ InputPrompt = processedIntentStatus.AllowAdditionalInputPrompt
+ ? new InputPrompt()
+ {
+ RichInitialPrompt = new RichResponse()
+ {
+ Items = new ResponseItem[] { simpleResponse }
+ }
+ }
+ : null
+ }
+ };
+
+ return response;
+ };
+
+ // We haven't sent a response using a SystemIntent, so send simple response
+ // plus any card activities
+ var responseItems = new List { simpleResponse };
+ responseItems.AddRange(GetResponseItemsFromActivityAttachments(activity));
+
+ // ensure InputHint is set as required for response
+ if (activity.InputHint == null || activity.InputHint == InputHints.AcceptingInput)
+ {
+ activity.InputHint =
+ Options.ShouldEndSessionByDefault ? InputHints.IgnoringInput : InputHints.ExpectingInput;
+ }
+
+ // check if we should be listening for more input from the user
+ switch (activity.InputHint)
+ {
+ case InputHints.IgnoringInput:
+ response.ExpectUserResponse = false;
+ response.FinalResponse = new FinalResponse()
+ {
+ RichResponse = new RichResponse() { Items = responseItems.ToArray() }
+ };
+ break;
+ case InputHints.ExpectingInput:
+ response.ExpectUserResponse = true;
+ response.ExpectedInputs = new ExpectedInput[]
+ {
+ new ExpectedInput()
+ {
+ PossibleIntents = new SystemIntent[]
+ {
+ new TextIntent(),
+ },
+ InputPrompt = new InputPrompt()
+ {
+ RichInitialPrompt = new RichResponse() {Items = responseItems.ToArray()}
+ }
+ }
+ };
+ response.ExpectedInputs.First().InputPrompt.RichInitialPrompt.Suggestions = ConvertSuggestedActionsToSuggestionChips(activity)?.ToArray();
+ break;
+ }
+
+ return response;
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/DialogFlowRequestMapper.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/DialogFlowRequestMapper.cs
new file mode 100644
index 00000000..b307dffa
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/DialogFlowRequestMapper.cs
@@ -0,0 +1,194 @@
+using System.Collections.Generic;
+using System.Linq;
+using Bot.Builder.Community.Adapters.Google.Core.Attachments;
+using Bot.Builder.Community.Adapters.Google.Core.Model;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Request;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Response;
+using Bot.Builder.Community.Adapters.Google.Core.Model.SystemIntents;
+using Microsoft.Bot.Schema;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Bot.Builder.Community.Adapters.Google.Core
+{
+ public class DialogFlowRequestMapper : GoogleRequestMapperBase
+ {
+ public DialogFlowRequestMapper(GoogleRequestMapperOptions options = null, ILogger logger = null) : base(options, logger)
+ {
+ Options = options ?? new GoogleRequestMapperOptions();
+ Logger = logger ?? NullLogger.Instance;
+ }
+
+ public Activity RequestToActivity(DialogFlowRequest request)
+ {
+ var payload = request.OriginalDetectIntentRequest.Payload;
+
+ var activity = new Activity();
+ activity = SetGeneralActivityProperties(activity, payload);
+ var actionIntent = payload.Inputs.FirstOrDefault(i => i.Intent.ToLowerInvariant().StartsWith("actions.intent"))?.Intent;
+ var queryText = StripInvocation(payload.Inputs[0]?.RawInputs[0]?.Query, Options.ActionInvocationName);
+
+ if (request.QueryResult.Intent.IsFallback || request.QueryResult.Intent.DisplayName.ToLowerInvariant() == "launch")
+ {
+ if (string.IsNullOrEmpty(queryText) || request.QueryResult.Intent.DisplayName.ToLowerInvariant() == "launch")
+ {
+ activity.Type = ActivityTypes.ConversationUpdate;
+ activity.MembersAdded = new List() { new ChannelAccount() { Id = activity.From.Id } };
+ return activity;
+ }
+ activity.Type = ActivityTypes.Message;
+ activity.Text = queryText;
+ return activity;
+ }
+
+ switch (actionIntent?.ToLowerInvariant())
+ {
+ case "actions.intent.sign_in":
+ activity.Type = ActivityTypes.Event;
+ activity.Name = request.QueryResult.Intent.DisplayName;
+ var signinStatusArgument = request.OriginalDetectIntentRequest.Payload.Inputs.First()?.Arguments?.Where(a => a.Name == "SIGN_IN").FirstOrDefault();
+ var argumentExtension = signinStatusArgument?.Extension;
+ activity.Value = argumentExtension?["status"];
+ return activity;
+ case "actions.intent.option":
+ case "actions.intent.text":
+ activity.Type = ActivityTypes.Message;
+ activity.Text = queryText;
+ return activity;
+ case "actions.intent.permission":
+ case "actions.intent.datetime":
+ case "ask_for_sign_in_confirmation":
+ case "actions.intent.place":
+ case "actions.intent.confirmation":
+ default:
+ activity.Type = ActivityTypes.Event;
+ activity.Name = request.QueryResult.Intent.DisplayName;
+ activity.Value = request;
+ return activity;
+ }
+ }
+
+ public DialogFlowResponse ActivityToResponse(Activity activity, DialogFlowRequest dialogFlowRequest)
+ {
+ var response = new DialogFlowResponse()
+ {
+ Payload = new ResponsePayload()
+ {
+ Google = new PayloadContent()
+ {
+ ExpectUserResponse = !Options.ShouldEndSessionByDefault,
+ UserStorage = dialogFlowRequest.OriginalDetectIntentRequest.Payload.User.UserStorage
+ }
+ }
+ };
+
+ // Send default empty response if no activity or invalid activity type sent
+ if (activity == null || activity.Type != ActivityTypes.Message)
+ {
+ response.Payload.Google.ExpectUserResponse = false;
+ return response;
+ }
+
+ activity.ConvertAttachmentContent();
+
+ var simpleResponse = new SimpleResponse
+ {
+ Content = new SimpleResponseContent
+ {
+ DisplayText = activity.Text,
+ Ssml = activity.Speak,
+ TextToSpeech = activity.Text
+ }
+ };
+
+ var processedIntentStatus = ProcessHelperIntentAttachments(activity);
+
+ // If we have a system intent to send - send it - with or without additional simple prompt
+ if (processedIntentStatus.Intent != null)
+ {
+ response.Payload.Google.ExpectUserResponse = true;
+ response.Payload.Google.SystemIntent = GetDialogFlowSystemIntentFromSystemIntent(processedIntentStatus);
+
+ if (processedIntentStatus.AllowAdditionalInputPrompt)
+ {
+ response.Payload.Google.RichResponse = new RichResponse()
+ {
+ Items = new ResponseItem[] {simpleResponse}
+ };
+ }
+
+ return response;
+ }
+
+ var responseItems = new List { simpleResponse };
+ responseItems.AddRange(GetResponseItemsFromActivityAttachments(activity));
+
+ response.Payload.Google.RichResponse = new RichResponse()
+ {
+ Items = responseItems.ToArray()
+ };
+
+ // ensure InputHint is set as required for response
+ if (activity.InputHint == null || activity.InputHint == InputHints.AcceptingInput)
+ {
+ activity.InputHint =
+ Options.ShouldEndSessionByDefault ? InputHints.IgnoringInput : InputHints.ExpectingInput;
+ }
+
+ // check if we should be listening for more input from the user
+ switch (activity.InputHint)
+ {
+ case InputHints.IgnoringInput:
+ response.Payload.Google.ExpectUserResponse = false;
+ break;
+ case InputHints.ExpectingInput:
+ response.Payload.Google.ExpectUserResponse = true;
+
+ var suggestionChips = ConvertSuggestedActionsToSuggestionChips(activity);
+ if (suggestionChips.Any())
+ {
+ response.Payload.Google.RichResponse.Suggestions = suggestionChips.ToArray();
+ }
+ break;
+ }
+
+ return response;
+ }
+
+ private DialogFlowSystemIntent GetDialogFlowSystemIntentFromSystemIntent(
+ ProcessHelperIntentAttachmentsResult processedIntentStatus)
+ {
+ var dialogFlowSystemIntent = new DialogFlowSystemIntent()
+ {
+ Intent = processedIntentStatus.Intent.Intent
+ };
+
+ switch (processedIntentStatus.Intent)
+ {
+ case SigninIntent signinIntent:
+ dialogFlowSystemIntent.Data = signinIntent.InputValueData;
+ break;
+ case ListIntent listIntent:
+ dialogFlowSystemIntent.Data = listIntent.InputValueData;
+ break;
+ case CarouselIntent carouselIntent:
+ dialogFlowSystemIntent.Data = carouselIntent.InputValueData;
+ break;
+ case PermissionsIntent permissionsIntent:
+ dialogFlowSystemIntent.Data = permissionsIntent.InputValueData;
+ break;
+ case DateTimeIntent dateTimeIntent:
+ dialogFlowSystemIntent.Data = dateTimeIntent.InputValueData;
+ break;
+ case PlaceLocationIntent placeLocationIntent:
+ dialogFlowSystemIntent.Data = placeLocationIntent.InputValueData;
+ break;
+ case ConfirmationIntent confirmationIntent:
+ dialogFlowSystemIntent.Data = confirmationIntent.InputValueData;
+ break;
+ }
+
+ return dialogFlowSystemIntent;
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleAuthorizationHandler.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleAuthorizationHandler.cs
new file mode 100644
index 00000000..8e4d7d4b
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleAuthorizationHandler.cs
@@ -0,0 +1,29 @@
+using System;
+using JWT.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
+
+namespace Bot.Builder.Community.Adapters.Google.Core
+{
+ ///
+ /// Google Authorization Handler.
+ ///
+ public class GoogleAuthorizationHandler
+ {
+ private readonly ILogger _logger;
+
+ public GoogleAuthorizationHandler(ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ public static bool ValidateActionProjectId(string authorizationHeader, string actionProjectId)
+ {
+ var payload = new JwtBuilder().Decode(authorizationHeader);
+ var payloadJObj = JObject.Parse(payload);
+ var aud = (string)payloadJObj["aud"];
+ return aud.ToLowerInvariant() == actionProjectId.ToLowerInvariant();
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleRequestMapperBase.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleRequestMapperBase.cs
new file mode 100644
index 00000000..8705a4d9
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleRequestMapperBase.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Bot.Builder.Community.Adapters.Google.Core.Attachments;
+using Bot.Builder.Community.Adapters.Google.Core.Helpers;
+using Bot.Builder.Community.Adapters.Google.Core.Model;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Request;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Response;
+using Bot.Builder.Community.Adapters.Google.Core.Model.SystemIntents;
+using Microsoft.Bot.Schema;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using BasicCard = Bot.Builder.Community.Adapters.Google.Core.Model.Response.BasicCard;
+
+namespace Bot.Builder.Community.Adapters.Google.Core
+{
+ public abstract class GoogleRequestMapperBase
+ {
+ public GoogleRequestMapperOptions Options { get; set; }
+ public ILogger Logger;
+
+ public GoogleRequestMapperBase(GoogleRequestMapperOptions options = null, ILogger logger = null)
+ {
+ Options = options ?? new GoogleRequestMapperOptions();
+ Logger = logger ?? NullLogger.Instance;
+ }
+
+ public Activity SetGeneralActivityProperties(Activity activity, ConversationRequest request)
+ {
+ activity.DeliveryMode = DeliveryModes.ExpectReplies;
+ activity.ChannelId = Options.ChannelId;
+ activity.ServiceUrl = Options.ServiceUrl;
+ activity.Recipient = new ChannelAccount("", "action");
+ activity.Conversation = new ConversationAccount(false, id: $"{request.Conversation.ConversationId}");
+ activity.From = new ChannelAccount(request.GetUserIdFromUserStorage());
+ activity.Id = Guid.NewGuid().ToString();
+ activity.Timestamp = DateTime.UtcNow;
+ activity.Locale = request.User.Locale;
+ activity.ChannelData = request;
+
+ return activity;
+ }
+
+ public static Activity MergeActivities(IList activities)
+ {
+ return MappingHelper.MergeActivities(activities);
+ }
+
+ public ProcessHelperIntentAttachmentsResult ProcessHelperIntentAttachments(Activity activity)
+ {
+ if (activity?.Attachments != null
+ && activity.Attachments.FirstOrDefault(a => a.ContentType == SigninCard.ContentType) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = new SigninIntent(),
+ AllowAdditionalInputPrompt = false
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.ConfirmationIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.ConfirmationIntent,
+ activity),
+ AllowAdditionalInputPrompt = false
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.DateTimeIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.DateTimeIntent,
+ activity),
+ AllowAdditionalInputPrompt = false
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.PermissionsIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.PermissionsIntent,
+ activity),
+ AllowAdditionalInputPrompt = false
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.PlaceLocationIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.PlaceLocationIntent,
+ activity),
+ AllowAdditionalInputPrompt = false
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.SigninIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.SigninIntent,
+ activity),
+ AllowAdditionalInputPrompt = false
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.CarouselIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.CarouselIntent,
+ activity),
+ AllowAdditionalInputPrompt = true
+ };
+ }
+
+ if (activity?.Attachments?.FirstOrDefault(a =>
+ a.ContentType == GoogleAttachmentContentTypes.ListIntent) != null)
+ {
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = ProcessSystemIntentAttachment(
+ GoogleAttachmentContentTypes.ListIntent,
+ activity),
+ AllowAdditionalInputPrompt = true
+ };
+ }
+
+ return new ProcessHelperIntentAttachmentsResult()
+ {
+ Intent = null
+ };
+ }
+
+ public ResponseItem ProcessResponseItemAttachment(string contentType, Activity activity) where T : ResponseItem
+ {
+ return activity.Attachments?
+ .Where(a => a.ContentType == contentType)
+ .Select(a => (T)a.Content).FirstOrDefault();
+ }
+
+ public SystemIntent ProcessSystemIntentAttachment(string contentType, Activity activity) where T : SystemIntent
+ {
+ return activity.Attachments?
+ .Where(a => a.ContentType == contentType)
+ .Select(a => (T)a.Content).FirstOrDefault();
+ }
+
+ public List GetResponseItemsFromActivityAttachments(Activity activity)
+ {
+ var responseItems = new List();
+
+ activity.ConvertAttachmentContent();
+
+ var basicCardItem = ProcessResponseItemAttachment(GoogleAttachmentContentTypes.BasicCard, activity);
+ if (basicCardItem != null)
+ responseItems.Add(basicCardItem);
+
+ var tableCardItem = ProcessResponseItemAttachment(GoogleAttachmentContentTypes.TableCard, activity);
+ if (tableCardItem != null)
+ responseItems.Add(tableCardItem);
+
+ var mediaItem = ProcessResponseItemAttachment(GoogleAttachmentContentTypes.MediaResponse, activity);
+ if (mediaItem != null)
+ responseItems.Add(mediaItem);
+
+ return responseItems;
+ }
+
+ public string StripInvocation(string query, string invocationName)
+ {
+ if (!string.IsNullOrEmpty(query) && !string.IsNullOrEmpty(invocationName))
+ {
+ invocationName = invocationName.ToLowerInvariant();
+ query = query.ToLowerInvariant();
+
+ if (query.Contains(invocationName))
+ {
+ var newStartPosition = query.IndexOf(invocationName, StringComparison.Ordinal);
+ query = query.Substring(newStartPosition + invocationName.Length);
+ }
+ }
+
+ return query;
+ }
+
+ public static List ConvertSuggestedActionsToSuggestionChips(Activity activity)
+ {
+ var suggestions = new List();
+
+ if (activity.SuggestedActions != null && activity.SuggestedActions.Actions != null && activity.SuggestedActions.Actions.Any())
+ {
+ foreach (var suggestion in activity.SuggestedActions.Actions)
+ {
+ suggestions.Add(new Suggestion { Title = suggestion.Title });
+ }
+ }
+
+ return suggestions;
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleRequestMapperOptions.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleRequestMapperOptions.cs
new file mode 100644
index 00000000..5cc36d79
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/GoogleRequestMapperOptions.cs
@@ -0,0 +1,12 @@
+using Bot.Builder.Community.Adapters.Google.Core.Model;
+
+namespace Bot.Builder.Community.Adapters.Google.Core
+{
+ public class GoogleRequestMapperOptions
+ {
+ public string ChannelId { get; set; } = "google";
+ public string ServiceUrl { get; set; } = null;
+ public bool ShouldEndSessionByDefault { get; set; } = true;
+ public string ActionInvocationName { get; set; } = null;
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/Helpers/Extensions.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/Helpers/Extensions.cs
new file mode 100644
index 00000000..514dff82
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/Helpers/Extensions.cs
@@ -0,0 +1,33 @@
+using System;
+using Bot.Builder.Community.Adapters.Google.Core.Model;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Request;
+using Newtonsoft.Json.Linq;
+
+namespace Bot.Builder.Community.Adapters.Google.Core.Helpers
+{
+ public static class Extensions
+ {
+ public static void EnsureUniqueUserIdInUserStorage(this ConversationRequest conversationRequest)
+ {
+ if (conversationRequest.User.UserStorage == null || !conversationRequest.User.UserStorage.ContainsKey("UserId"))
+ {
+ if (conversationRequest.User.UserStorage == null)
+ {
+ conversationRequest.User.UserStorage = new JObject();
+ }
+
+ conversationRequest.User.UserStorage.Add("UserId", Guid.NewGuid().ToString());
+ }
+ }
+
+ public static string GetUserIdFromUserStorage(this ConversationRequest payload)
+ {
+ if (payload.User.UserStorage != null && payload.User.UserStorage.ContainsKey("UserId"))
+ {
+ return payload.User.UserStorage["UserId"].ToString();
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/libraries/Bot.Builder.Community.Adapters.Google.Core/Helpers/GoogleCardFactory.cs b/libraries/Bot.Builder.Community.Adapters.Google.Core/Helpers/GoogleCardFactory.cs
new file mode 100644
index 00000000..47a3527a
--- /dev/null
+++ b/libraries/Bot.Builder.Community.Adapters.Google.Core/Helpers/GoogleCardFactory.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Bot.Builder.Community.Adapters.Google.Core.Model.Response;
+using Bot.Builder.Community.Adapters.Google.Core.Model.SystemIntents;
+
+namespace Bot.Builder.Community.Adapters.Google.Core.Helpers
+{
+ public static class GoogleCardFactory
+ {
+ public static BasicCard CreateBasicCard(string title, string subtitle, string formattedText)
+ {
+ return new BasicCard()
+ {
+ Content = new BasicCardContent()
+ {
+
+ Title = title,
+ Subtitle = subtitle,
+ FormattedText = formattedText
+ }
+ };
+ }
+
+ public static TableCard CreateTableCard(List columns, List rows, string title = null,
+ string subtitle = null, List