diff --git a/Alexa.NET/Request/RequestBundle.cs b/Alexa.NET/Request/RequestBundle.cs deleted file mode 100644 index 16b4796..0000000 --- a/Alexa.NET/Request/RequestBundle.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Alexa.NET.Request.Type; -using Newtonsoft.Json; -using System; - -namespace Alexa.NET.Request -{ - public class RequestBundle : IIntentRequest, ILaunchRequest, ISessionEndedRequest - { - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("requestId")] - public string RequestId { get; set; } - - [JsonProperty("timestamp")] - public DateTime Timestamp { get; set; } - - [JsonProperty("intent")] - public Intent Intent { get; set; } - - [JsonProperty("reason")] - public string Reason { get; set; } - - public System.Type GetRequestType() - { - switch (Type) - { - case "IntentRequest": - return typeof(IIntentRequest); - case "LaunchRequest": - return typeof(ILaunchRequest); - case "SessionEndedRequest": - return typeof(ISessionEndedRequest); - default: - throw new ArgumentOutOfRangeException(nameof(Type), $"Unknown request type: {Type}."); - } - } - } -} \ No newline at end of file diff --git a/Alexa.NET/Request/SkillRequest.cs b/Alexa.NET/Request/SkillRequest.cs index e2c4dfa..e1b6fe0 100644 --- a/Alexa.NET/Request/SkillRequest.cs +++ b/Alexa.NET/Request/SkillRequest.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Alexa.NET.Request.Type; +using Newtonsoft.Json; namespace Alexa.NET.Request { @@ -11,11 +12,12 @@ public class SkillRequest public Session Session { get; set; } [JsonProperty("request")] - public RequestBundle Request { get; set; } + [JsonConverter(typeof(RequestConverter))] + public Type.Request Request { get; set; } public System.Type GetRequestType() { - return Request?.GetRequestType(); + return Request?.GetType(); } } } \ No newline at end of file diff --git a/Alexa.NET/Request/Type/AudioPlayerRequest.cs b/Alexa.NET/Request/Type/AudioPlayerRequest.cs new file mode 100644 index 0000000..98f69da --- /dev/null +++ b/Alexa.NET/Request/Type/AudioPlayerRequest.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Alexa.NET.Request.Type +{ + public class AudioPlayerRequest: Request + { + [JsonProperty("token")] + public string Token { get; set; } + + [JsonProperty("locale")] + public string Locale { get; set; } + + [JsonProperty("offsetInMilliseconds")] + public string OffsetInMilliseconds { get; set; } + + [JsonProperty("error")] + public Error Error { get; set; } + + [JsonProperty("currentPlaybackState")] + public PlaybackState CurrentPlaybackState { get; set; } + } +} diff --git a/Alexa.NET/Request/Type/Error.cs b/Alexa.NET/Request/Type/Error.cs new file mode 100644 index 0000000..64ee8c5 --- /dev/null +++ b/Alexa.NET/Request/Type/Error.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Alexa.NET.Request.Type +{ + public class Error + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + } +} diff --git a/Alexa.NET/Request/Type/IIntentRequest.cs b/Alexa.NET/Request/Type/IIntentRequest.cs deleted file mode 100644 index 1d9a543..0000000 --- a/Alexa.NET/Request/Type/IIntentRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Alexa.NET.Request.Type -{ - public interface IIntentRequest : IRequest - { - Intent Intent { get; set; } - } -} \ No newline at end of file diff --git a/Alexa.NET/Request/Type/IntentRequest.cs b/Alexa.NET/Request/Type/IntentRequest.cs new file mode 100644 index 0000000..c51f8a9 --- /dev/null +++ b/Alexa.NET/Request/Type/IntentRequest.cs @@ -0,0 +1,7 @@ +namespace Alexa.NET.Request.Type +{ + public class IntentRequest : Request + { + public Intent Intent { get; set; } + } +} \ No newline at end of file diff --git a/Alexa.NET/Request/Type/ILaunchRequest.cs b/Alexa.NET/Request/Type/LaunchRequest.cs similarity index 52% rename from Alexa.NET/Request/Type/ILaunchRequest.cs rename to Alexa.NET/Request/Type/LaunchRequest.cs index d73517a..e31c999 100644 --- a/Alexa.NET/Request/Type/ILaunchRequest.cs +++ b/Alexa.NET/Request/Type/LaunchRequest.cs @@ -1,6 +1,6 @@ namespace Alexa.NET.Request.Type { - public interface ILaunchRequest : IRequest + public class LaunchRequest : Request { } } \ No newline at end of file diff --git a/Alexa.NET/Request/Type/PlaybackState.cs b/Alexa.NET/Request/Type/PlaybackState.cs new file mode 100644 index 0000000..9183e8c --- /dev/null +++ b/Alexa.NET/Request/Type/PlaybackState.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Alexa.NET.Request.Type +{ + public class PlaybackState + { + [JsonProperty("token")] + public string Token { get; set; } + + [JsonProperty("offsetInMilliseconds")] + public string OffsetInMilliseconds { get; set; } + + [JsonProperty("playerActivity")] + public string PlayerActivity { get; set; } + } +} diff --git a/Alexa.NET/Request/Type/IRequest.cs b/Alexa.NET/Request/Type/Request.cs similarity index 53% rename from Alexa.NET/Request/Type/IRequest.cs rename to Alexa.NET/Request/Type/Request.cs index f1e4fd9..db9e67d 100644 --- a/Alexa.NET/Request/Type/IRequest.cs +++ b/Alexa.NET/Request/Type/Request.cs @@ -3,15 +3,15 @@ namespace Alexa.NET.Request.Type { - public interface IRequest + public abstract class Request { [JsonProperty("type")] - string Type { get; set; } + public string Type { get; set; } [JsonProperty("requestId")] - string RequestId { get; set; } + public string RequestId { get; set; } [JsonProperty("timestamp")] - DateTime Timestamp { get; set; } + public DateTime Timestamp { get; set; } } } \ No newline at end of file diff --git a/Alexa.NET/Request/Type/RequestBundle.cs b/Alexa.NET/Request/Type/RequestBundle.cs deleted file mode 100644 index 46f30f9..0000000 --- a/Alexa.NET/Request/Type/RequestBundle.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Alexa.NET.Request.Type -{ - public class RequestBundle : IIntentRequest, ILaunchRequest, ISessionEndedRequest - { - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("requestId")] - public string RequestId { get; set; } - - [JsonProperty("timestamp")] - public DateTime Timestamp { get; set; } - - [JsonProperty("intent")] - public Intent Intent { get; set; } - - [JsonProperty("reason")] - public string Reason { get; set; } - - public System.Type GetRequestType() - { - switch (Type) - { - case "IntentRequest": - return typeof(IIntentRequest); - case "LaunchRequest": - return typeof(ILaunchRequest); - case "SessionEndedRequest": - return typeof(ISessionEndedRequest); - default: - throw new ArgumentOutOfRangeException(nameof(Type), $"Unknown request type: {Type}."); - } - } - } -} \ No newline at end of file diff --git a/Alexa.NET/Request/Type/RequestConverter.cs b/Alexa.NET/Request/Type/RequestConverter.cs new file mode 100644 index 0000000..3bc8c2f --- /dev/null +++ b/Alexa.NET/Request/Type/RequestConverter.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Alexa.NET.Request.Type +{ + public class RequestConverter : JsonConverter + { + public override bool CanWrite => false; + + public override bool CanConvert(System.Type objectType) + { + return objectType == typeof(Request); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override object ReadJson(JsonReader reader, System.Type objectType, object existingValue, JsonSerializer serializer) + { + // Load JObject from stream + var jObject = JObject.Load(reader); + + // Create target request object based on "type" property + var target = Create(jObject["type"].Value()); + + // Populate the object properties + serializer.Populate(jObject.CreateReader(), target); + + return target; + } + + public Request Create(string requestType) + { + //AudioPlayer requests are very similar, map to single type + if (requestType.StartsWith("AudioPlayer")) + requestType = "AudioPlayer"; + + switch (requestType) + { + case "IntentRequest": + return new IntentRequest(); + case "LaunchRequest": + return new LaunchRequest(); + case "SessionEndedRequest": + return new SessionEndedRequest(); + case "AudioPlayer": + return new AudioPlayerRequest(); + default: + throw new ArgumentOutOfRangeException(nameof(Type), $"Unknown request type: {requestType}."); + } + } + } +} \ No newline at end of file diff --git a/Alexa.NET/Request/Type/ISessionEndedRequest.cs b/Alexa.NET/Request/Type/SessionEndedRequest.cs similarity index 53% rename from Alexa.NET/Request/Type/ISessionEndedRequest.cs rename to Alexa.NET/Request/Type/SessionEndedRequest.cs index bd8b40a..4ccee6c 100644 --- a/Alexa.NET/Request/Type/ISessionEndedRequest.cs +++ b/Alexa.NET/Request/Type/SessionEndedRequest.cs @@ -2,9 +2,9 @@ namespace Alexa.NET.Request.Type { - public interface ISessionEndedRequest : IRequest + public class SessionEndedRequest : Request { [JsonProperty("reason")] - string Reason { get; set; } + public string Reason { get; set; } } } \ No newline at end of file diff --git a/Alexa.NET/Response/Directive/AudioItem.cs b/Alexa.NET/Response/Directive/AudioItem.cs new file mode 100644 index 0000000..c222acc --- /dev/null +++ b/Alexa.NET/Response/Directive/AudioItem.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace Alexa.NET.Response.Directive +{ + public class AudioItem + { + [JsonRequired] + [JsonProperty("stream")] + public AudioItemStream Stream { get; set; } + } +} diff --git a/Alexa.NET/Response/Directive/AudioItemStream.cs b/Alexa.NET/Response/Directive/AudioItemStream.cs new file mode 100644 index 0000000..55ade21 --- /dev/null +++ b/Alexa.NET/Response/Directive/AudioItemStream.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Alexa.NET.Response.Directive +{ + public class AudioItemStream + { + [JsonRequired] + [JsonProperty("url")] + public string Url { get; set; } + + [JsonRequired] + [JsonProperty("token")] + public string Token { get; set; } + + [JsonProperty("expectedPreviousToken")] + public string ExpectedPreviousToken { get; set; } + + [JsonRequired] + [JsonProperty("offsetInMilliseconds")] + public int OffsetInMilliseconds { get; set; } + } +} diff --git a/Alexa.NET/Response/Directive/AudioPlayerPlayDirective.cs b/Alexa.NET/Response/Directive/AudioPlayerPlayDirective.cs new file mode 100644 index 0000000..81259df --- /dev/null +++ b/Alexa.NET/Response/Directive/AudioPlayerPlayDirective.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Alexa.NET.Response.Directive +{ + public class AudioPlayerPlayDirective : IDirective + { + [JsonProperty("playBehavior")] + [JsonRequired] + [JsonConverter(typeof(StringEnumConverter))] + public PlayBehavior PlayBehavior { get; set; } + + [JsonProperty("audioItem")] + [JsonRequired] + public AudioItem AudioItem { get; set; } + + [JsonProperty("type")] + public string Type => "AudioPlayer.Play"; + } +} diff --git a/Alexa.NET/Response/Directive/PlayBehavior.cs b/Alexa.NET/Response/Directive/PlayBehavior.cs new file mode 100644 index 0000000..8b40629 --- /dev/null +++ b/Alexa.NET/Response/Directive/PlayBehavior.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; + +namespace Alexa.NET.Response.Directive +{ + public enum PlayBehavior + { + [EnumMember(Value = "REPLACE_ALL")] + ReplaceAll, + [EnumMember(Value = "ENQUEUE")] + Enqueue, + [EnumMember(Value = "REPLACE_ENQUEUED")] + ReplaceEnqueued + } +} diff --git a/Alexa.NET/Response/IDirective.cs b/Alexa.NET/Response/IDirective.cs new file mode 100644 index 0000000..0333f73 --- /dev/null +++ b/Alexa.NET/Response/IDirective.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Alexa.NET.Response +{ + public interface IDirective + { + [JsonRequired] + string Type { get; } + } +} diff --git a/Alexa.NET/Response/Response.cs b/Alexa.NET/Response/Response.cs index 4a2d54a..b1c7f99 100644 --- a/Alexa.NET/Response/Response.cs +++ b/Alexa.NET/Response/Response.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using System.Collections.Generic; namespace Alexa.NET.Response { @@ -16,5 +17,8 @@ public class ResponseBody [JsonProperty("shouldEndSession")] [JsonRequired] public bool ShouldEndSession { get; set; } + + [JsonProperty("directives", NullValueHandling = NullValueHandling.Ignore)] + public IList Directives { get; set; } = new List(); } } \ No newline at end of file diff --git a/Alexa.NET/ResponseBuilder.cs b/Alexa.NET/ResponseBuilder.cs index 071c56f..d31b2be 100644 --- a/Alexa.NET/ResponseBuilder.cs +++ b/Alexa.NET/ResponseBuilder.cs @@ -1,5 +1,6 @@ using Alexa.NET.Request; using Alexa.NET.Response; +using Alexa.NET.Response.Directive; using System; using System.Collections.Generic; using System.Linq; @@ -63,6 +64,37 @@ public static SkillResponse Ask(IOutputSpeech speechResponse, Reprompt reprompt, return BuildResponse(speechResponse, false, sessionAttributes, reprompt, null); } + public static SkillResponse AudioPlayerPlay(PlayBehavior playBehavior, string url, string token) + { + return AudioPlayerPlay(playBehavior, url, token, 0); + } + + public static SkillResponse AudioPlayerPlay(PlayBehavior playBehavior, string url, string token, int offsetInMilliseconds) + { + return AudioPlayerPlay(playBehavior, url, token, null, offsetInMilliseconds); + } + + public static SkillResponse AudioPlayerPlay(PlayBehavior playBehavior, string url, string token, string expectedPreviousToken, int offsetInMilliseconds) + { + var response = BuildResponse(null, true, null, null, null); + response.Response.Directives.Add(new AudioPlayerPlayDirective() + { + PlayBehavior = playBehavior, + AudioItem = new AudioItem() + { + Stream = new AudioItemStream() + { + Url = url, + Token = token, + ExpectedPreviousToken = expectedPreviousToken, + OffsetInMilliseconds = offsetInMilliseconds + } + } + }); + + return response; + } + private static SkillResponse BuildResponse(IOutputSpeech outputSpeech, bool shouldEndSession, Session sessionAttributes, Reprompt reprompt, ICard card) { SkillResponse response = new Response.SkillResponse();