diff --git a/Samples/Common/Sample.Common/Sample.Common.csproj b/Samples/Common/Sample.Common/Sample.Common.csproj index 09061e22..71d8c31f 100644 --- a/Samples/Common/Sample.Common/Sample.Common.csproj +++ b/Samples/Common/Sample.Common/Sample.Common.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj index a8776d59..083eae49 100644 --- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj +++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/FrontEnd/AVPFrontEnd.csproj @@ -55,14 +55,14 @@ False - ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.14.1.234-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll + ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.17.0.39-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll - + - + diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj index 1b9f603a..b02e7de1 100644 --- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj +++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/AVPWorkerRole.csproj @@ -34,7 +34,7 @@ - + @@ -48,6 +48,9 @@ + + Always + Designer PreserveNewest diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/VC_redist.x64.exe b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/VC_redist.x64.exe new file mode 100644 index 00000000..7476d75a Binary files /dev/null and b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/VC_redist.x64.exe differ diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config index 3488b1ab..cdf3a12c 100644 --- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config +++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/app.config @@ -56,7 +56,7 @@ - + @@ -64,7 +64,7 @@ - + diff --git a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd index 6aea8c43..fa65f8b7 100644 --- a/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd +++ b/Samples/V1.0Samples/LocalMediaSamples/AudioVideoPlaybackBot/WorkerRole/startup.cmd @@ -1,6 +1,9 @@ REM --- Move to this scripts location --- pushd "%~dp0" +REM --- Ensure the VC_redist is installed for the Microsoft.Skype.Bots.Media Library --- +.\VC_redist.x64.exe /quiet + REM --- Print out environment variables for debugging --- set diff --git a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj index de609922..c4b46c58 100644 --- a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj +++ b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/HueBot.csproj @@ -19,8 +19,8 @@ - - + + @@ -35,7 +35,10 @@ - PreserveNewest + Always + + + Always diff --git a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd index f698e077..95c2312b 100644 --- a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd +++ b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/Startup.cmd @@ -8,6 +8,12 @@ pushd "%~dp0" REM --- Print out environment variables for debugging --- REM set Fabric +REM --- Ensure the VC_redist is installed for the Microsoft.Skype.Bots.Media Library --- +@echo off +set logfile=.\InstallCppRuntime-HueBot.log +.\VC_redist.x64.exe /quiet +echo %date% %time% ErrorLevel=%errorlevel% >> %logfile% + REM --- Register media perf dlls --- powershell .\MediaPlatformStartupScript.bat diff --git a/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/VC_redist.x64.exe b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/VC_redist.x64.exe new file mode 100644 index 00000000..7476d75a Binary files /dev/null and b/Samples/V1.0Samples/LocalMediaSamples/HueBot/HueBot/VC_redist.x64.exe differ diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot.sln b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot.sln similarity index 96% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot.sln rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot.sln index c5573f99..69d88b88 100644 --- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot.sln +++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot.sln @@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\nuget.config = ..\..\nuget.config EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common.Beta", "..\..\Common\Sample.Common.Beta\Sample.Common.Beta.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRFrontEnd", "PolicyRecordingBot\FrontEnd\CRFrontEnd.csproj", "{739D09C4-47E8-42B7-9B89-94DCB890AC0F}" EndProject diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/Bot.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/BotMediaStream.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/CallHandler.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Bot/ExceptionExtensions.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj similarity index 92% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj index 0df9e7a4..3bc4e2dd 100644 --- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj +++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/CRFrontEnd.csproj @@ -53,21 +53,21 @@ False - ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.14.1.234-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll + ..\..\..\..\packages\Microsoft.Skype.Bots.Media\1.17.0.39-alpha\src\skype_media_lib\Microsoft.Skype.Internal.Media.H264.dll - + - + - + {88b67fbe-6de8-4dc3-83cf-9f6b4ea10138} - Sample.Common.Beta + Sample.Common diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/DemoController.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/HttpRouteConstants.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/Controllers/PlatformCallController.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/ExceptionLogger.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/HttpConfigurationInitializer.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Http/LoggingMessageHandler.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/IConfiguration.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/LRUCache.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Properties/AssemblyInfo.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/SampleConstants.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Service.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/FrontEnd/Utilities.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting1.png diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/Images/TestMeeting2.png diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/PolicyRecordingBot.ccproj diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/README.md b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/README.md similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/README.md rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/README.md diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Cloud.cscfg diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceConfiguration.Local.cscfg diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/ServiceDefinition.csdef diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/AzureConfiguration.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj similarity index 92% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj index 8e3ad32e..b262bce8 100644 --- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj +++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/CRWorkerRole.csproj @@ -35,7 +35,7 @@ - + @@ -60,14 +60,19 @@ - + {88b67fbe-6de8-4dc3-83cf-9f6b4ea10138} - Sample.Common.Beta + Sample.Common {739d09c4-47e8-42b7-9b89-94dcb890ac0f} CRFrontEnd + + + Always + + \ No newline at end of file diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/ConfigurationException.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/Properties/AssemblyInfo.cs diff --git a/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/VC_redist.x64.exe b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/VC_redist.x64.exe new file mode 100644 index 00000000..7476d75a Binary files /dev/null and b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/VC_redist.x64.exe differ diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/WorkerRole.cs diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config similarity index 95% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config index 44a9bae6..4721f1ce 100644 --- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config +++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/app.config @@ -56,7 +56,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -78,6 +78,6 @@ - + diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd similarity index 90% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd index 6aea8c43..d536f07e 100644 --- a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd +++ b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRole/startup.cmd @@ -4,6 +4,9 @@ pushd "%~dp0" REM --- Print out environment variables for debugging --- set +REM --- Ensure the VC_redist is installed for the Microsoft.Skype.Bots.Media Library --- +.\VC_redist.x64.exe /quiet /norestart + REM --- Delete existing certificate bindings and URL ACL registrations --- netsh http delete sslcert ipport=%InstanceIpAddress%:%PrivateDefaultCallControlPort% netsh http delete sslcert ipport=%InstanceIpAddress%:%PrivateInstanceCallControlPort% diff --git a/Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx b/Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx similarity index 100% rename from Samples/BetaSamples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx rename to Samples/V1.0Samples/LocalMediaSamples/PolicyRecordingBot/WorkerRoleContent/diagnostics.wadcfgx diff --git a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln index 5af3ebb2..2cad675c 100644 --- a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln +++ b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot.sln @@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IncidentBot", "IncidentBot\IncidentBot.csproj", "{FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common.Beta", "..\..\Common\Sample.Common.Beta\Sample.Common.Beta.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs index 800dbd80..7efcaf36 100644 --- a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs +++ b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/Bot/Bot.cs @@ -480,7 +480,7 @@ private void AddCallToHandlers(ICall call, IncidentCallContext incidentCallConte var statusData = this.IncidentStatusManager.GetIncident(incidentCallContext.IncidentId); CallHandler callHandler; - ParticipantInfo callee; + InvitationParticipantInfo callee; switch (incidentCallContext.CallType) { case IncidentCallType.BotMeeting: diff --git a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj index f195a11b..5b23ad1c 100644 --- a/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj +++ b/Samples/V1.0Samples/RemoteMediaSamples/IncidentBot/IncidentBot.csproj @@ -24,7 +24,7 @@ - + diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot.sln b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot.sln new file mode 100644 index 00000000..e3a705dd --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29911.84 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleIvrBot", "SimpleIvrBot\SimpleIvrBot.csproj", "{89B2D455-60B7-4701-B30C-9F6C21FBE705}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89B2D455-60B7-4701-B30C-9F6C21FBE705}.Release|Any CPU.Build.0 = Release|Any CPU + {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FCDBE79-54CC-43F0-A6FC-674B5CD980E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9076CB49-69D5-400A-9C2C-88AD1AC400A9} + EndGlobalSection +EndGlobal diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs index 8e709036..dc8ae3b0 100644 --- a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs +++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/Bot/Bot.cs @@ -37,15 +37,7 @@ namespace Sample.SimpleIvrBot.Bot /// public class Bot { - /// - /// The prompt audio name for transfering the call. - /// - /// - /// message: "Please press 1 for Sales, press 2 for Service, press 3 for other questions". - /// - private const string BotIncomingPromptName = "BotIncomingPrompt"; - - private readonly Uri botBaseUri; + private readonly Uri botBaseUri; /// /// Initializes a new instance of the class. @@ -84,16 +76,6 @@ public Bot(BotOptions options, IGraphLogger graphLogger) this.AuthenticationProvider, productInfo, defaultProperties); - - // setup media prompt - this.MediaMap[BotIncomingPromptName] = new MediaPrompt - { - MediaInfo = new MediaInfo - { - Uri = new Uri(options.BotBaseUrl, "audio/speech.wav").ToString(), - ResourceId = Guid.NewGuid().ToString(), - }, - }; } /// @@ -126,19 +108,13 @@ public Bot(BotOptions options, IGraphLogger graphLogger) /// public IGraphClient GraphApiClient { get; } - /// - /// Gets the prompts dictionary. - /// - public IDictionary MediaMap { get; } = new Dictionary(); - /// /// Processes the notification asynchronously. - /// Here we make sure we log the http request and - /// catch/log any errors. + /// Here we make sure we log the http request and catch/log any errors. /// /// The request. /// The response. - /// Returns after notification is processed. + /// The . public async Task ProcessNotificationAsync( HttpRequest request, HttpResponse response) @@ -305,69 +281,64 @@ private async Task NotificationProcessor_OnNotificationReceivedAsync(Notificatio { await this.BotAnswerIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } - else if (args.ChangeType == ChangeType.Updated && call.State == CallState.Established) + else if (args.ChangeType == ChangeType.Updated && (call.State == CallState.Established || call.State == CallState.Establishing)) { - this.GraphLogger.Log(TraceLevel.Info, "In Established"); + this.GraphLogger.Log(TraceLevel.Info, $"In {call.State}"); - if (call.ToneInfo == null && call.MediaState.Audio == MediaState.Active) + if (call.ToneInfo == null && call.MediaState?.Audio == MediaState.Active) { await this.BotPlayNotificationPromptAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } else if (call.ToneInfo?.SequenceId == 1) { - InvitationParticipantInfo transferTarget = null; - + InvitationParticipantInfo transferTarget = null; if (call.ToneInfo.Tone == Tone.Tone1) + { + transferTarget = new InvitationParticipantInfo { - transferTarget = new InvitationParticipantInfo + Identity = new IdentitySet { - Identity = new IdentitySet + User = new Identity { - User = new Identity - { - Id = ConfigurationManager.AppSetting["ObjectIds:First"], - }, + Id = ConfigurationManager.AppSetting["ObjectIds:First"], }, - }; - } + }, + }; + } else if (call.ToneInfo.Tone == Tone.Tone2) - { - transferTarget = new InvitationParticipantInfo + { + transferTarget = new InvitationParticipantInfo + { + Identity = new IdentitySet { - Identity = new IdentitySet + User = new Identity { - User = new Identity - { - Id = ConfigurationManager.AppSetting["ObjectIds:Second"], - }, + Id = ConfigurationManager.AppSetting["ObjectIds:Second"], }, - }; - } + }, + }; + } else + { + transferTarget = new InvitationParticipantInfo { - transferTarget = new InvitationParticipantInfo - { - Identity = new IdentitySet - { - User = new Identity - { - Id = ConfigurationManager.AppSetting["ObjectIds:Third"], - }, - }, - }; - } + Identity = new IdentitySet + { + User = new Identity + { + Id = ConfigurationManager.AppSetting["ObjectIds:Third"], + }, + }, + }; + } await this.BotTransferCallAsync(transferTarget, call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); } - - return; } else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated) { - this.GraphLogger.Log(TraceLevel.Info, "Call is Terminated now"); + this.GraphLogger.Log(TraceLevel.Info, $"Call State:{call.State}"); } - - return; } else if (args.ResourceData is PlayPromptOperation playPromptOperation) { @@ -379,7 +350,7 @@ private async Task NotificationProcessor_OnNotificationReceivedAsync(Notificatio Message = "No call id provided in PlayPromptOperation.ClientContext.", }); } - else if (playPromptOperation.CompletionReason == PlayPromptCompletionReason.CompletedSuccessfully && playPromptOperation.Status == OperationStatus.Completed) + else if (playPromptOperation.Status == OperationStatus.Completed) { await this.BotSubscribesToToneAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); } @@ -389,16 +360,13 @@ private async Task NotificationProcessor_OnNotificationReceivedAsync(Notificatio /// /// Subscribes to Tone. /// - /// Call Id. - /// Tenant Id. - /// ScenarioId. - /// - /// user subscribesToTone. - /// + /// The call identifier. + /// The tenant identifier. + /// The scenario identifier. + /// The . private async Task BotSubscribesToToneAsync(string callId, string tenantId, Guid scenarioId) { await this.GraphApiClient.SendAsync(this.RequestBuilder.Communications.Calls[callId].SubscribeToTone(callId).Request(), RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); - return; } /// @@ -407,20 +375,22 @@ private async Task BotSubscribesToToneAsync(string callId, string tenantId, Guid /// The identifier of the call to transfer. /// The tenant identifier. /// The scenario identifier. - /// - /// When the call has been answered. - /// + /// The . private async Task BotAnswerIncomingCallAsync(string callId, string tenantId, Guid scenarioId) { - var mediaToPrefetch = new List(); - foreach (var m in this.MediaMap) - { - mediaToPrefetch.Add(m.Value.MediaInfo); - } - var answerRequest = this.RequestBuilder.Communications.Calls[callId].Answer( callbackUri: new Uri(this.botBaseUri, ControllerConstants.CallbackPrefix).ToString(), - mediaConfig: new ServiceHostedMediaConfig { PreFetchMedia = mediaToPrefetch }, + mediaConfig: new ServiceHostedMediaConfig + { + PreFetchMedia = new List() + { + new MediaInfo() + { + Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(), + ResourceId = Guid.NewGuid().ToString(), + }, + }, + }, acceptedModalities: new List { Modality.Audio }).Request(); await this.GraphApiClient.SendAsync(answerRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); } @@ -432,9 +402,7 @@ private async Task BotAnswerIncomingCallAsync(string callId, string tenantId, Gu /// The call identifier. /// The tenant identifier. /// The scenario identifier. - /// - /// When the call has been answered. - /// + /// The . private async Task BotTransferCallAsync(InvitationParticipantInfo target, string callId, string tenantId, Guid scenarioId) { await this.GraphApiClient.SendAsync(this.RequestBuilder.Communications.Calls[callId].Transfer(target).Request(), RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); @@ -446,19 +414,26 @@ private async Task BotTransferCallAsync(InvitationParticipantInfo target, string /// The call identifier. /// The Tenant identifier. /// The scenario identifier. - /// - /// Returns when prompt is played. - /// + /// The . private async Task BotPlayNotificationPromptAsync(string callId, string tenantId, Guid scenarioId) { - var prompts = new Prompt[] { this.MediaMap[BotIncomingPromptName] }; + var prompts = new Prompt[] + { + new MediaPrompt + { + MediaInfo = new MediaInfo() + { + Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(), + ResourceId = Guid.NewGuid().ToString(), + }, + }, + }; var playPromptRequest = this.RequestBuilder.Communications.Calls[callId].PlayPrompt( prompts: prompts, clientContext: callId).Request(); await this.GraphApiClient.SendAsync(playPromptRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); - return; } } } \ No newline at end of file diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj index c6a19c0c..8d085a3e 100644 --- a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj +++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/SimpleIvrBot.csproj @@ -2,27 +2,17 @@ netcoreapp2.1 - 5dedc36d-1522-49fc-ae69-0e7e9440d29a - InProcess - AnyCPU - Sample.SimpleIvrBot - Sample.SimpleIvrBot - false - x64 + - - - + - - diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json index 72d017f4..79bb4fd3 100644 --- a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json +++ b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/appsettings.json @@ -11,7 +11,7 @@ "AppId": "%AppId%", "AppSecret": "%AppSecret%", "PlaceCallEndpointUrl": "https://graph.microsoft.com/v1.0", - "BotBaseUrl": "%BotBaseUrl%" + "BotBaseUrl": "%ServiceDns%" }, "objectIds": { "First": "%objectId-1%", diff --git a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav index e9eaa95d..5d498c89 100644 Binary files a/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav and b/Samples/V1.0Samples/StatelessSamples/SimpleIvrBot/wwwroot/audio/speech.wav differ diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot.sln b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot.sln new file mode 100644 index 00000000..9fe5f4ab --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29102.190 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DCC9792D-0B15-4167-8A9F-BB5DDDA6E7D6}" + ProjectSection(SolutionItems) = preProject + ..\..\configure_cloud.ps1 = ..\..\configure_cloud.ps1 + ..\Graph.props = ..\Graph.props + ..\nuget.config = ..\nuget.config + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoiceRecorderAndPlaybackBot", "VoiceRecorderAndPlaybackBot\VoiceRecorderAndPlaybackBot.csproj", "{FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Common", "..\..\Common\Sample.Common\Sample.Common.csproj", "{3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA70E97D-7018-4B08-A9D0-ABA9967AD7DC}.Release|Any CPU.Build.0 = Release|Any CPU + {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3268E59C-90DC-4D7B-97EA-A1DBB2716DF3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {49D3AAEB-2787-4CC0-876C-8F6C8E350FD7} + EndGlobalSection +EndGlobal diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/Bot.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/Bot.cs new file mode 100644 index 00000000..d9f257ee --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/Bot.cs @@ -0,0 +1,491 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Bot +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; + using Microsoft.Extensions.Primitives; + using Microsoft.Graph; + using Microsoft.Graph.Communications.Client.Authentication; + using Microsoft.Graph.Communications.Client.Transport; + using Microsoft.Graph.Communications.Common; + using Microsoft.Graph.Communications.Common.Telemetry; + using Microsoft.Graph.Communications.Common.Transport; + using Microsoft.Graph.Communications.Core.Notifications; + using Microsoft.Graph.Communications.Core.Serialization; + using Newtonsoft.Json; + using Sample.Common; + using Sample.Common.Authentication; + using Sample.Common.Transport; + using Sample.VoiceRecorderAndPlaybackBot.Controller; + using Sample.VoiceRecorderAndPlaybackBot.Extensions; + using File = System.IO.File; + using TraceLevel = System.Diagnostics.TraceLevel; + + /// + /// The core bot class. + /// + public class Bot + { + private readonly Uri botBaseUri; + + /// + /// Initializes a new instance of the class. + /// + /// The bot options. + /// The graph logger. + public Bot(BotOptions options, IGraphLogger graphLogger) + { + this.botBaseUri = options.BotBaseUrl; + + this.GraphLogger = graphLogger; + var name = this.GetType().Assembly.GetName().Name; + this.AuthenticationProvider = new AuthenticationProvider(name, options.AppId, options.AppSecret, graphLogger); + this.Serializer = new CommsSerializer(); + + var authenticationWrapper = new AuthenticationWrapper(this.AuthenticationProvider); + this.NotificationProcessor = new NotificationProcessor(authenticationWrapper, this.Serializer); + this.NotificationProcessor.OnNotificationReceived += this.NotificationProcessor_OnNotificationReceived; + this.RequestBuilder = new GraphServiceClient(options.PlaceCallEndpointUrl.AbsoluteUri, authenticationWrapper); + + // Add the default headers used by the graph client. + // This will include SdkVersion. + var defaultProperties = new List>>(); + using (HttpClient tempClient = GraphClientFactory.Create(authenticationWrapper)) + { + defaultProperties.AddRange(tempClient.DefaultRequestHeaders.Select(header => GraphProperty.RequestProperty(header.Key, header.Value))); + } + + // graph client + var productInfo = new ProductInfoHeaderValue( + typeof(Bot).Assembly.GetName().Name, + typeof(Bot).Assembly.GetName().Version.ToString()); + this.GraphApiClient = new GraphAuthClient( + this.GraphLogger, + this.Serializer.JsonSerializerSettings, + new HttpClient(), + this.AuthenticationProvider, + productInfo, + defaultProperties); + } + + /// + /// Gets graph logger. + /// + public IGraphLogger GraphLogger { get; } + + /// + /// Gets the authentication provider. + /// + public IRequestAuthenticationProvider AuthenticationProvider { get; } + + /// + /// Gets the notification processor. + /// + public INotificationProcessor NotificationProcessor { get; } + + /// + /// Gets the URI builder. + /// + public GraphServiceClient RequestBuilder { get; } + + /// + /// Gets the serializer. + /// + public CommsSerializer Serializer { get; } + + /// + /// Gets the stateless graph client. + /// + public IGraphClient GraphApiClient { get; } + + /// + /// Processes the notification asynchronously. + /// Here we make sure we log the http request and catch/log any errors. + /// + /// The request. + /// The response. + /// The . + public async Task ProcessNotificationAsync( + HttpRequest request, + HttpResponse response) + { + // TODO: Parse out the scenario id and request id headers. + var headers = request.Headers.Select( + pair => new KeyValuePair>(pair.Key, pair.Value)); + + // Don't log content since we can't PII scrub here (we don't know the type). + var stopwatch = new Stopwatch(); + stopwatch.Start(); + this.GraphLogger.LogHttpMessage( + TraceLevel.Verbose, + TransactionDirection.Incoming, + HttpTraceType.HttpRequest, + request.GetDisplayUrl(), + request.Method, + obfuscatedContent: null, + headers: headers); + + try + { + var httpRequest = request.CreateRequestMessage(); + var results = await this.AuthenticationProvider.ValidateInboundRequestAsync(httpRequest).ConfigureAwait(false); + if (results.IsValid) + { + var httpResponse = await this.NotificationProcessor.ProcessNotificationAsync(httpRequest).ConfigureAwait(false); + await httpResponse.CreateHttpResponseAsync(response).ConfigureAwait(false); + } + else + { + var httpResponse = httpRequest.CreateResponse(HttpStatusCode.Forbidden); + await httpResponse.CreateHttpResponseAsync(response).ConfigureAwait(false); + } + + headers = response.Headers.Select( + pair => new KeyValuePair>(pair.Key, pair.Value)); + + this.GraphLogger.LogHttpMessage( + TraceLevel.Verbose, + TransactionDirection.Incoming, + HttpTraceType.HttpResponse, + request.GetDisplayUrl(), + request.Method, + obfuscatedContent: null, + headers: headers, + responseCode: response.StatusCode, + responseTime: stopwatch.ElapsedMilliseconds); + } + catch (ServiceException e) + { + string obfuscatedContent = null; + if ((int)e.StatusCode >= 300) + { + response.StatusCode = (int)e.StatusCode; + await response.WriteAsync(e.ToString()).ConfigureAwait(false); + obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(e, Formatting.Indented); + } + else if ((int)e.StatusCode >= 200) + { + response.StatusCode = (int)e.StatusCode; + } + else + { + response.StatusCode = (int)e.StatusCode; + await response.WriteAsync(e.ToString()).ConfigureAwait(false); + obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(e, Formatting.Indented); + } + + headers = response.Headers.Select( + pair => new KeyValuePair>(pair.Key, pair.Value)); + + if (e.ResponseHeaders?.Any() == true) + { + foreach (var pair in e.ResponseHeaders) + { + response.Headers.Add(pair.Key, new StringValues(pair.Value.ToArray())); + } + + headers = headers.Concat(e.ResponseHeaders); + } + + this.GraphLogger.LogHttpMessage( + TraceLevel.Error, + TransactionDirection.Incoming, + HttpTraceType.HttpResponse, + request.GetDisplayUrl(), + request.Method, + obfuscatedContent, + headers, + response.StatusCode, + responseTime: stopwatch.ElapsedMilliseconds); + } + catch (Exception e) + { + response.StatusCode = (int)HttpStatusCode.InternalServerError; + await response.WriteAsync(e.ToString()).ConfigureAwait(false); + + var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(e, Formatting.Indented); + headers = response.Headers.Select( + pair => new KeyValuePair>(pair.Key, pair.Value)); + + this.GraphLogger.LogHttpMessage( + TraceLevel.Error, + TransactionDirection.Incoming, + HttpTraceType.HttpResponse, + request.GetDisplayUrl(), + request.Method, + obfuscatedContent, + headers, + response.StatusCode, + responseTime: stopwatch.ElapsedMilliseconds); + } + } + + /// + /// Raised when the has received a notification. + /// + /// The instance containing the event data. + private void NotificationProcessor_OnNotificationReceived(NotificationEventArgs args) + { +#pragma warning disable 4014 + // Processing notification in the background. + // This ensures we're not holding on to the request. + this.NotificationProcessor_OnNotificationReceivedAsync(args).ForgetAndLogExceptionAsync( + this.GraphLogger, + $"Error processing notification {args.Notification.ResourceUrl} with scenario {args.ScenarioId}"); +#pragma warning restore 4014 + } + + /// + /// Raised when the has received a notification asynchronously. + /// + /// The instance containing the event data. + /// The . + private async Task NotificationProcessor_OnNotificationReceivedAsync(NotificationEventArgs args) + { + this.GraphLogger.CorrelationId = args.ScenarioId; + var headers = new[] + { + new KeyValuePair>(HttpConstants.HeaderNames.ScenarioId, new[] { args.ScenarioId.ToString() }), + new KeyValuePair>(HttpConstants.HeaderNames.ClientRequestId, new[] { args.RequestId.ToString() }), + new KeyValuePair>(HttpConstants.HeaderNames.Tenant, new[] { args.TenantId }), + }; + + // Create obfuscation content to match what we + // would have gotten from the service, then log. + var notifications = new CommsNotifications { Value = new[] { args.Notification } }; + var obfuscatedContent = this.GraphLogger.SerializeAndObfuscate(notifications, Formatting.Indented); + this.GraphLogger.LogHttpMessage( + TraceLevel.Info, + TransactionDirection.Incoming, + HttpTraceType.HttpRequest, + args.CallbackUri.ToString(), + HttpMethods.Post, + obfuscatedContent, + headers, + correlationId: args.ScenarioId, + requestId: args.RequestId); + + if (args.ResourceData is Call call) + { + if (args.ChangeType == ChangeType.Created && call.State == CallState.Incoming) + { + await this.BotAnswerIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); + } + else if (args.ChangeType == ChangeType.Updated && call.State == CallState.Established && call.MediaState?.Audio == MediaState.Active) + { + // there can potentially be multiple established notifications for the same call, + // but since this is just sample code, it is not handled. + // your production code must handle such a case and not call record multiple times for the same call + await this.BotRecordsIncomingCallAsync(call.Id, args.TenantId, args.ScenarioId).ConfigureAwait(false); + } + else if (args.ChangeType == ChangeType.Deleted && call.State == CallState.Terminated) + { + this.CleanupCall(call.Id); + } + } + // Receiving updates for the play prompt operation. + else if (args.ResourceData is PlayPromptOperation playPromptOperation) + { + // checking for the call id sent in ClientContext. + if (string.IsNullOrWhiteSpace(playPromptOperation.ClientContext)) + { + throw new ServiceException(new Error() + { + Message = "No call id provided in PlayPromptOperation.ClientContext.", + }); + } + else if (playPromptOperation.Status == OperationStatus.Completed) + { + // The operation has been completed, hang up the call + await this.BotHangupCallAsync(playPromptOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); + this.GraphLogger.Log(TraceLevel.Info, $"Disconnecting the call."); + } + } + else if (args.ResourceData is RecordOperation recordOperation) + { + if (recordOperation.Status == OperationStatus.Completed && recordOperation.ResultInfo.Code == 200) + { + var recordingFileName = $"audio/recording-{recordOperation.ClientContext}.wav"; + + await this.DownloadRecording(recordingFileName, recordOperation).ConfigureAwait(false); + + var prompts = new Prompt[] { + new MediaPrompt + { + MediaInfo = new MediaInfo() + { + Uri = new Uri(this.botBaseUri, recordingFileName).ToString() + }, + }, + }; + + await this.BotPlayPromptAsync(prompts, recordOperation.ClientContext, args.TenantId, args.ScenarioId).ConfigureAwait(false); + } + } + } + + /// + /// Bot answers incoming call. + /// + /// The call identifier to answer. + /// The tenant identifier. + /// The scenario identifier. + /// The . + private async Task BotAnswerIncomingCallAsync(string callId, string tenantId, Guid scenarioId) + { + var answerRequest = this.RequestBuilder.Communications.Calls[callId].Answer( + callbackUri: new Uri(this.botBaseUri, ControllerConstants.CallbackPrefix).ToString(), + mediaConfig: new ServiceHostedMediaConfig { + PreFetchMedia = new List() + { + new MediaInfo() + { + Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(), + ResourceId = Guid.NewGuid().ToString(), + } + } + }, + acceptedModalities: new List { Modality.Audio }).Request(); + await this.GraphApiClient.SendAsync(answerRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); + } + + /// + /// Bot Hangs up the call. + /// + /// The call id to answer. + /// The tenant identifier. + /// The scenario identifier. + /// The . + private async Task BotHangupCallAsync(string callId, string tenantId, Guid scenarioId) + { + var hangupRequest = this.RequestBuilder.Communications.Calls[callId].Request(); + await this.GraphApiClient.SendAsync(hangupRequest, RequestType.Delete, tenantId, scenarioId).ConfigureAwait(false); + } + + /// + /// Bot records incoming call. + /// + /// The call identifier to answer. + /// The tenant identifier. + /// The scenario identifier. + /// The . + private async Task BotRecordsIncomingCallAsync(string callId, string tenantId, Guid scenarioId) + { + var prompts = new Prompt[] { + new MediaPrompt + { + MediaInfo = new MediaInfo() + { + Uri = new Uri(this.botBaseUri, "audio/speech.wav").ToString(), + ResourceId = Guid.NewGuid().ToString(), + }, + }, + }; + + IEnumerable stopTones = new List() { "#", "1", "*" }; + var recordRequest = this.RequestBuilder.Communications.Calls[callId].RecordResponse( + bargeInAllowed: true, + clientContext: callId, // set clientcontext as callid + prompts: prompts, + maxRecordDurationInSeconds: 10, + initialSilenceTimeoutInSeconds: 5, + maxSilenceTimeoutInSeconds: 2, + playBeep: true, + stopTones: stopTones).Request(); + + await this.GraphApiClient.SendAsync(recordRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); + } + + /// + /// Bot plays the given prompts. + /// + /// The prompts to play. + /// The call identifier. + /// The tenant identifier. + /// The scenario identifier. + /// A + private async Task BotPlayPromptAsync(IEnumerable prompts, string callId, string tenantId, Guid scenarioId) + { + var playPromptRequest = this.RequestBuilder.Communications.Calls[callId].PlayPrompt( + prompts: prompts, + clientContext: callId).Request(); + await this.GraphApiClient.SendAsync(playPromptRequest, RequestType.Create, tenantId, scenarioId).ConfigureAwait(false); + } + + /// + /// Cleans up the specified call. + /// + /// The id of the call being cleaned up. + private void CleanupCall(string callId) + { + this.GraphLogger.Log(TraceLevel.Info, $"Cleaning up call {callId}"); + + // Any media collected may not be persisted, so delete it. + // Make sure you are compliant with the laws and regulations of your area when it comes to call recording. + // Please consult with a legal counsel for more information. + + var fileInfo = new FileInfo($"wwwroot/audio/recording-{callId}.wav"); + if (File.Exists(fileInfo.FullName)) + { + this.GraphLogger.Log(TraceLevel.Info, $"Deleting {fileInfo.FullName}"); + File.Delete(fileInfo.FullName); + } + else + { + throw new ServiceException(new Error() + { + Message = "File does not exist to be deleted.", + }); + } + } + + /// + /// Downloads the recording. + /// + /// File name where recording is to be downloaded. + /// Record Operation. + /// The . + private async Task DownloadRecording(string recordingFileName, RecordOperation recordOperation) + { + using (var httpClient = new HttpClient()) + { + var requestMessage = new HttpRequestMessage( + HttpMethod.Get, + new Uri(recordOperation.RecordingLocation)); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", recordOperation.RecordingAccessToken); + + var httpResponse = await httpClient.SendAsync(requestMessage).ConfigureAwait(false); + + if (!httpResponse.IsSuccessStatusCode) + { + throw new ServiceException(new Error() + { + Message = "Unable to download the recording file.", + }); + } + using (var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + FileStream fileStream = null; + var fileInfo = new FileInfo($"wwwroot/{recordingFileName}"); + using (fileStream = new FileStream(fileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.None)) + { + await stream.CopyToAsync(fileStream).ConfigureAwait(false); + } + } + } + } + } +} \ No newline at end of file diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/BotOptions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/BotOptions.cs new file mode 100644 index 00000000..9c0c37b8 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Bot/BotOptions.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Bot +{ + using System; + + /// + /// The bot options class. + /// + public class BotOptions + { + /// + /// Gets or sets the application id. + /// + public string AppId { get; set; } + + /// + /// Gets or sets the application secret. + /// + public string AppSecret { get; set; } + + /// + /// Gets or sets the calls uri of the application. + /// + public Uri BotBaseUrl { get; set; } + + /// + /// Gets or sets the comms platform endpoint uri. + /// + public Uri PlaceCallEndpointUrl { get; set; } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/ControllerConstants.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/ControllerConstants.cs new file mode 100644 index 00000000..daf3d81d --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/ControllerConstants.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Controller +{ + /// + /// Http route constants for routing requests. + /// + public class ControllerConstants + { + /// + /// Route prefix for all incoming requests. + /// + public const string CallbackPrefix = "/callback"; + } +} \ No newline at end of file diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/HomeController.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/HomeController.cs new file mode 100644 index 00000000..aa7f8621 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/HomeController.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Controller +{ + using Microsoft.AspNetCore.Mvc; + using Sample.Common.Logging; + + /// + /// The home controller class. + /// + public class HomeController : Controller + { + private readonly SampleObserver observer; + + /// + /// Initializes a new instance of the class. + /// + /// The observer. + public HomeController(SampleObserver observer) + { + this.observer = observer; + } + + /// + /// Get the default content of home page. + /// + /// Default content. + [HttpGet("/")] + public string Get() + { + return "Home Page"; + } + + /// + /// Get the service logs. + /// + /// Skip specified lines. + /// Take specified lines. + /// The logs. + [HttpGet] + [Route("/logs")] + public IActionResult GetLogs( + [FromQuery] int skip = 0, + [FromQuery] int take = 1000) + { + this.AddRefreshHeader(3); + return this.Content( + this.observer.GetLogs(skip, take), + System.Net.Mime.MediaTypeNames.Text.Plain, + System.Text.Encoding.UTF8); + } + + /// + /// Get the service logs. + /// + /// The filter. + /// Skip specified lines. + /// Take specified lines. + /// + /// The logs. + /// + [HttpGet] + [Route("/logs/{filter}")] + public IActionResult GetLogs( + string filter, + [FromQuery] int skip = 0, + [FromQuery] int take = 1000) + { + this.AddRefreshHeader(3); + return this.Content( + this.observer.GetLogs(filter, skip, take), + System.Net.Mime.MediaTypeNames.Text.Plain, + System.Text.Encoding.UTF8); + } + + /// + /// Add refresh headers for browsers to download content. + /// + /// Refresh rate. + private void AddRefreshHeader(int seconds) + { + this.Response.Headers.Add("Cache-Control", "private,must-revalidate,post-check=1,pre-check=2,no-cache"); + this.Response.Headers.Add("Refresh", seconds.ToString()); + } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/PlatformCallController.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/PlatformCallController.cs new file mode 100644 index 00000000..d3497de7 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Controllers/PlatformCallController.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Controller +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Graph.Communications.Common.Telemetry; + using Sample.VoiceRecorderAndPlaybackBot.Bot; + + /// + /// Entry point for handling call-related web hook requests. + /// + public class PlatformCallController : Controller + { + private readonly IGraphLogger graphLogger; + private readonly Bot bot; + + /// + /// Initializes a new instance of the class. + /// + /// The bot. + public PlatformCallController(Bot bot) + { + this.bot = bot; + this.graphLogger = bot.GraphLogger.CreateShim(nameof(PlatformCallController)); + } + + /// + /// Handle call back for bot calls user case. + /// + /// returns when task is done. + [HttpPost] + [Route(ControllerConstants.CallbackPrefix)] + public async Task OnIncomingBotCallUserRequestAsync() + { + await this.bot.ProcessNotificationAsync(this.Request, this.Response).ConfigureAwait(false); + } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/BotBuilderExtensions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/BotBuilderExtensions.cs new file mode 100644 index 00000000..2bfcadfb --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/BotBuilderExtensions.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Microsoft.Extensions.DependencyInjection +{ + using System; + using Sample.VoiceRecorderAndPlaybackBot.Bot; + + /// + /// The bot builder extensions class. + /// + public static class BotBuilderExtensions + { + /// + /// Add bot feature. + /// + /// The service collection. + /// The updated service collection. + public static IServiceCollection AddBot(this IServiceCollection services) + => services.AddBot(_ => { }); + + /// + /// Add bot feature. + /// + /// The service collection. + /// The action for bot options. + /// The updated service collection. + public static IServiceCollection AddBot(this IServiceCollection services, Action botOptionsAction) + { + var options = new BotOptions(); + botOptionsAction(options); + services.AddSingleton(options); + + return services.AddSingleton(); + } + } +} \ No newline at end of file diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/ControllerExtentions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/ControllerExtentions.cs new file mode 100644 index 00000000..e2ae9d17 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/ControllerExtentions.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Extensions +{ + using System; + using System.Linq; + using System.Net; + using System.Net.Http.Headers; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Microsoft.Extensions.Primitives; + using Microsoft.Graph; + + /// + /// The controller exceptions. + /// + public static class ControllerExtentions + { + /// + /// Convert exception to action result. + /// + /// The controller. + /// The exception. + /// The action result. + public static IActionResult Exception(this Controller controller, Exception exception) + { + IActionResult result; + + if (exception is ServiceException e) + { + controller.HttpContext.Response.CopyHeaders(e.ResponseHeaders); + + int statusCode = (int)e.StatusCode; + + result = statusCode >= 300 + ? controller.StatusCode(statusCode, e.ToString()) + : controller.StatusCode((int)HttpStatusCode.InternalServerError, e.ToString()); + } + else + { + result = controller.StatusCode((int)HttpStatusCode.InternalServerError, exception.ToString()); + } + + return result; + } + + /// + /// Copy the response headers to controller.HttpContext.Response. + /// + /// The controller. + /// The headers. + private static void CopyHeaders(this HttpResponse response, HttpHeaders headers) + { + if (headers == null) + { + // do nothing as the source headers are null. + return; + } + + foreach (var header in headers) + { + var values = header.Value?.ToArray(); + if (values?.Any() == true) + { + response.Headers.Add(header.Key, new StringValues(values)); + } + } + } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/HttpExtensions.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/HttpExtensions.cs new file mode 100644 index 00000000..5191bab5 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Extensions/HttpExtensions.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// + +namespace Sample.VoiceRecorderAndPlaybackBot.Extensions +{ + using System; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; + + /// + /// Extensions for ASP.NET HTTP request and response. + /// + public static class HttpExtensions + { + /// + /// Creates the request message asynchronous. + /// + /// The request. + /// The . + public static HttpRequestMessage CreateRequestMessage(this HttpRequest request) + { + var displayUri = request.GetDisplayUrl(); + var httpRequest = new HttpRequestMessage + { + RequestUri = new Uri(displayUri), + Method = new HttpMethod(request.Method), + }; + + if (request.ContentLength.HasValue && request.ContentLength.Value > 0) + { + httpRequest.Content = new StreamContent(request.Body); + } + + // Copy headers + foreach (var header in request.Headers) + { + httpRequest.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + + return httpRequest; + } + + /// + /// Creates the HTTP response. + /// + /// The response. + /// The HTTP response. + /// The populated . + public static async Task CreateHttpResponseAsync(this HttpResponseMessage response, HttpResponse httpResponse) + { + httpResponse.StatusCode = (int)response.StatusCode; + + if (response.Content != null) + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + await httpResponse.WriteAsync(content).ConfigureAwait(false); + } + + // Copy headers + foreach (var header in response.Headers) + { + response.Headers.Add(header.Key, header.Value); + } + + return httpResponse; + } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Program.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Program.cs new file mode 100644 index 00000000..eb59080b --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Program.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Sample.VoiceRecorderAndPlaybackBot +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddDebug(); + logging.AddConsole(); + logging.AddAzureWebAppDiagnostics(); + }) + .UseStartup(); + } +} \ No newline at end of file diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Properties/launchSettings.json b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Properties/launchSettings.json new file mode 100644 index 00000000..75a7508e --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:2404", + "sslPort": 44390 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "VoiceRecorderAndPlaybackBot": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/README.md b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/README.md new file mode 100644 index 00000000..70945494 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/README.md @@ -0,0 +1,99 @@ +# Introduction +The sample demostrate a recording audio by user and playing back to user workflow. + +# Sample status +1. The user will call bot and Bot will answer the call. +2. Bot plays audio prompt message, indicating it is ready to record. +3. Bot records what the user speaks, with maximum duration allowed 10 seconds. +4. Bot plays back the recorded message. +5. Bot hangs up the call. + +# Getting Started +1. Installation process + * Enable an Azure subscription to host web sites and bot services. + * Install Visual Studio 2017 + * Launch CommsSamples.sln in \Samples with Visual Studio 2017 (VS2017) + * Click menu Build/"Build Solution" to build the whole solution + * Create an BotService in an Azure subscription with Azure Portal (https://portal.azure.com), then enable both Teams & Skype channels on the Azure portal, and configure the calling Uri of the bot. + - Go to "Bot Services" resource type page, click "Add", select "Bot Channels Registration", click "Create", then follow the instructions. + - Write down the application ID **\{ApplicationId}** and **\{ApplicationSecret}** for next steps. + - Click "VoiceRecorderAndPlaybackBot" in "Bot Services" resource type page, Click "Channels", Then select "Microsoft Teams" and "Skype" channels and enable both of them. + - Click "edit" button of "Skype" channel, click "Calling" tab, select "Enable calling" radio button, then select "IVR - 1:1 IVR audio calls", and fill the Webhook (for calling) edit box with value "\{BotBaseUrl}/callback". + * Configure permissions for the Bot. + - This Bot doesnt need any permissions + +# Getting Started (Azure Version) +1. Installation process + * Create web site in Azure + - Right click 1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/"Connected Services" and select "Add Connected Service" in project VoiceRecorderAndPlaybackBot in VS2017, then select Publish tab, and click "Create new profile" to lauch a dialog + - Select "App Service" then click "Create New" radio button, then click "Publish" button to create a App Service and publish the code on. + - Write down the web site root uri **\{BotBaseUrl}** for next steps. + + * Update the following elements in appsettings.json file in project VoiceRecorderAndPlaybackBot. + - Bot/AppId: "**\{ApplicationId}**" + - Bot/AppSecret: "**\{ApplicationSecret}**" + - Bot/BotBaseUrl: "**\{BotBaseUrl}**" + + * Publish the application again. + - Right click VoiceRecorderAndPlaybackBot/"Connected Services" and select "Add Connected Service" in project NotificationBot in VS2017, click "Publish" button. + +2. Update process + * Update code properly. + * Publish the application again. + +3. Software dependencies + * .NET Framework 471 + * Nuget packages list are in \\Dependencies\Nuget in Solution Explorer of Visual Studio 2017 + +4. Latest releases + * version 0.2 + +5. API references + +# Getting Started (Local Run Version) +1. Installation process + * Install Visual Studio 2017 + * Launch CommsSamples.sln in \Samples with Visual Studio 2017 (VS2017) + * Click menu Build/"Build Solution" to build the whole solution + * Setup ngrok. + - Sign up for a free ngrok account. Once signed up, go to the ngrok [dashboard](https://dashboard.ngrok.com/) and get your auth token. + - Create an ngrok configuration file `ngrok.yml` as follows: + ```yaml + authtoken: %replace_with_auth_token_from_dashboard% + tunnels: + signaling: + addr: 9442 + proto: http + media: + addr: 8445 + proto: tcp + ``` + - Start ngrok: `ngrok http https://localhost:44379 -host-header=localhost`. You will see an output like this: + ```ymal + Session Status online + Account YourName (Plan: Free) + Version x.x.xx + Region United States (us) + Web Interface http://127.0.0.1:4040 + Forwarding http://e6c2321a.ngrok.io -> https://localhost:44379 + Forwarding https://e6c2321a.ngrok.io -> https://localhost:44379 + ``` + - From **your** output, in line Forwarding (yours will be different) the first url`https://e6c2321a.ngrok.io` will be your bot base uri. Write down the bot base uri as **\{BotBaseUrl}** for next steps. + * Update the following elements in appsettings.json file in project NotificationBot. + - Bot/AppId: "**\{ApplicationId}**" + - Bot/AppSecret: "**\{ApplicationSecret}**" + - Bot/BotBaseUrl: "**\{BotBaseUrl}**" + +# Build and Test +1. Create a tenant in O365, with Teams enabled. + +2. Create two users in O365, with Teams enabled. + * Write down the users' object IDs as \{UserObjectId-1} for user1 and \{UserObjectId-2} for user2 (optional). + +3. Install Teams client. + +4. Login to Teams + +5. Install the VoiceRecorderAndPlaybackBot in the client using steps mentioned here: https://microsoftgraph.github.io/microsoft-graph-comms-samples/docs/articles/calls/register-calling-bot.html#register-bot-in-microsoft-teams + +6. user1 should call VoiceRecorderAndPlaybackBot using the Teams client, and bot should play prompt indicating it is ready to record the user's audio. diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Startup.cs b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Startup.cs new file mode 100644 index 00000000..7f1d2854 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/Startup.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Graph.Communications.Common.Telemetry; +using Sample.Common.Logging; + +namespace Sample.VoiceRecorderAndPlaybackBot +{ + public class Startup + { + private readonly GraphLogger logger; + private readonly SampleObserver observer; + + public Startup(IConfiguration configuration) + { + this.Configuration = configuration; + this.logger = new GraphLogger(typeof(Startup).Assembly.GetName().Name); + this.observer = new SampleObserver(this.logger); + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services + .AddSingleton(this.observer) + .AddSingleton(this.logger); + + services + .AddBot(options => this.Configuration.Bind("Bot", options)) + .AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); + } + + /// + /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + /// + /// App builder. + /// Hosting environment. + /// /// The logger of ILogger instance. + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) + { + this.logger.BindToILoggerFactory(loggerFactory); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + app.UseCookiePolicy(); + + app.UseMvc(); + } + } +} diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/VoiceRecorderAndPlaybackBot.csproj b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/VoiceRecorderAndPlaybackBot.csproj new file mode 100644 index 00000000..755625b5 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/VoiceRecorderAndPlaybackBot.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/appsettings.json b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/appsettings.json new file mode 100644 index 00000000..fa4119d8 --- /dev/null +++ b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Information", + "System": "Information", + "Microsoft": "Information" + } + }, + "Bot": { + "AppId": "%AppId%", + "AppSecret": "%AppSecret%", + "PlaceCallEndpointUrl": "https://graph.microsoft.com/v1.0", + "BotBaseUrl": "%ServiceDns%" + } +} \ No newline at end of file diff --git a/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/wwwroot/audio/speech.wav b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/wwwroot/audio/speech.wav new file mode 100644 index 00000000..fb060767 Binary files /dev/null and b/Samples/V1.0Samples/StatelessSamples/VoiceRecorderAndPlaybackBot/wwwroot/audio/speech.wav differ diff --git a/changelog.md b/changelog.md index a2f08e0d..88591aa8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,18 @@ # Changelog for Microsoft Graph Communications SDK and Samples This changelog covers what's changed in Microsoft Graph Communications SDK and its associated samples. +## Mar 2020 + +- Updated to latest /beta and /v1 contracts + - For a full list of changes refer to the [graph blog](https://developer.microsoft.com/en-us/graph/blogs/microsoft-graph-net-sdk-updates/). +- Changed name of Compliance Recording to Policy Recording +- Migrated the Policy Recording sample to the https://graph.microsoft.com/v1.0 endpoint. +- Updated Communications libraries in v1 bots to use the 1.2.0.791 SDK. + - Exposed the Policy Recording API's in v1. +- Updated Media library in v1 bots to 1.17.0.39-alpha ## Feb 2020 + - Updated Compliance Recording sample docs. ## Jan 2020