diff --git a/src/Limbo.Integrations.Skyfish/Endpoints/SkyfishSearchEndpoint.cs b/src/Limbo.Integrations.Skyfish/Endpoints/SkyfishSearchEndpoint.cs new file mode 100644 index 0000000..246f292 --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Endpoints/SkyfishSearchEndpoint.cs @@ -0,0 +1,22 @@ +using Limbo.Integrations.Skyfish.Options.Videos; +using Limbo.Integrations.Skyfish.Responses.Search; + +namespace Limbo.Integrations.Skyfish.Endpoints { + + public class SkyfishSearchEndpoint { + + public SkyfishHttpService Service { get; } + + public SkyfishSearchRawEndpoint Raw => Service.Client.Search; + + public SkyfishSearchEndpoint(SkyfishHttpService service) { + Service = service; + } + + public SkyfishSearchResponse Search(SkyfishSearchOptions options) { + return new(Raw.Search(options)); + } + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Endpoints/SkyfishSearchRawEndpoint.cs b/src/Limbo.Integrations.Skyfish/Endpoints/SkyfishSearchRawEndpoint.cs new file mode 100644 index 0000000..085b526 --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Endpoints/SkyfishSearchRawEndpoint.cs @@ -0,0 +1,27 @@ +using System; +using Limbo.Integrations.Skyfish.Http; +using Limbo.Integrations.Skyfish.Options.Videos; +using Skybrud.Essentials.Http; + +namespace Limbo.Integrations.Skyfish.Endpoints { + + public class SkyfishSearchRawEndpoint { + + public SkyfishHttpClient Client { get; } + + public SkyfishSearchRawEndpoint(SkyfishHttpClient client) { + Client = client; + } + + #region Member methods + + public IHttpResponse Search(SkyfishSearchOptions options) { + if (options == null) throw new ArgumentNullException(nameof(options)); + return Client.GetResponse(options); + } + + #endregion + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Http/SkyfishHttpClient.cs b/src/Limbo.Integrations.Skyfish/Http/SkyfishHttpClient.cs index 7dfc92b..8663989 100644 --- a/src/Limbo.Integrations.Skyfish/Http/SkyfishHttpClient.cs +++ b/src/Limbo.Integrations.Skyfish/Http/SkyfishHttpClient.cs @@ -1,11 +1,11 @@ using System; using System.Runtime.Caching; using System.Threading; +using Limbo.Integrations.Skyfish.Endpoints; using Limbo.Integrations.Skyfish.Models; using Newtonsoft.Json.Linq; using Skybrud.Essentials.Http; using Skybrud.Essentials.Http.Client; -using Skybrud.Essentials.Json; using Skybrud.Essentials.Json.Extensions; using Skybrud.Essentials.Security; using Skybrud.Essentials.Time.UnixTime; @@ -22,15 +22,22 @@ public class SkyfishHttpClient : HttpClient { public string Password { get; } + public SkyfishSearchRawEndpoint Search { get; } + private readonly string _token; - public SkyfishHttpClient() { } + public SkyfishHttpClient() { + Search = new SkyfishSearchRawEndpoint(this); + } public SkyfishHttpClient(string apikey, string secretkey, string username, string password) { + ApiKey = apikey; SecretKey = secretkey; Username = username; Password = password; + + Search = new SkyfishSearchRawEndpoint(this); _token = GetToken(); } @@ -69,55 +76,55 @@ private string GetToken() { return token; } - public SkyfishVideo GetVideo(int videoId) { - SkyfishVideo video = GetSkyfishVideoData(videoId); - var videoStream = GetSkyfishVideo(video.VideoId); + public string GetEmbedUrl(int uniqueMediaId) { + + IHttpResponse videoStreamUrl = GetSkyfishVideoStream(uniqueMediaId); + string response = ""; // if stream doesn't exist we generate it - if (videoStream.StatusCode != System.Net.HttpStatusCode.OK) { - CreateSkyfishStream(video.VideoId); + if (videoStreamUrl.StatusCode != System.Net.HttpStatusCode.OK) { + CreateSkyfishStream(uniqueMediaId); - // wait 1 sec then check if it's ready Thread.Sleep(1000); - video = GetVideo(video); - } else { - video.EmbedUrl = "https://player.skyfish.com/?v=" + JObject.Parse(videoStream.Body).GetString("Stream") + "&media=" + video.VideoId; + + // Call this method again + GetEmbedUrl(uniqueMediaId); + } else if (string.IsNullOrWhiteSpace(JObject.Parse(videoStreamUrl.Body).GetString("Stream"))) { + // for some reason they only return 404 if it's not currently generating, so any subsequent requests to see if its ready we need to parse the json body to see if it has the stream link + + response = GetEmbedUrlWithRetries(uniqueMediaId, 0); } - return video; + return response; } + + private string GetEmbedUrlWithRetries(int uniqueMediaId, int count) { + IHttpResponse videoStream = GetSkyfishVideoStream(uniqueMediaId); - private SkyfishVideo GetVideo(SkyfishVideo video) { - var videoStream = GetSkyfishVideo(video.VideoId); + // Exit condition? Only retry for 2 mins. + if (count >= 120) return null; + + string response; + + if (string.IsNullOrWhiteSpace(JObject.Parse(videoStream.Body).GetString("Stream"))) { + // for some reason they only return 404 if it's not currently generating, so any subsequent requests to see if its ready we need to parse the json body to see if it has the stream link - if (videoStream.StatusCode != System.Net.HttpStatusCode.OK) { - // if not ready yet, we wait 1s then query again - normally quite fast Thread.Sleep(1000); - video = GetVideo(video); + count++; + + response = GetEmbedUrlWithRetries(uniqueMediaId, count); } else { - // for some reason they only return 404 if it's not currently generating, so any subsequent requests to see if its ready we need to parse the json body to see if it has the stream link - if (string.IsNullOrWhiteSpace(JObject.Parse(videoStream.Body).GetString("Stream"))) { - // if not ready yet, we wait 1s then query again - normally quite fast - Thread.Sleep(1000); - video = GetVideo(video); - } else { - video.EmbedUrl = "https://player.skyfish.com/?v=" + JObject.Parse(videoStream.Body).GetString("Stream") + "&media=" + video.VideoId; - } + response = "https://player.skyfish.com/?v=" + JObject.Parse(videoStream.Body).GetString("Stream") + "&media=" + uniqueMediaId; } - return video; - } - - private SkyfishVideo GetSkyfishVideoData(int videoId) { - var response = Get($"/search?media_id={videoId}&return_values=unique_media_id+height+width+title+description+thumbnail_url+thumbnail_url_ssl+filename+file_disksize+file_mimetype"); - return JsonUtils.ParseJsonObject(response.Body, SkyfishVideo.Parse); + return response; } - private void CreateSkyfishStream(int videoId) { - Post($"/media/{videoId}/stream"); + private void CreateSkyfishStream(int uniqueMediaId) { + Post($"/media/{uniqueMediaId}/stream"); } - private IHttpResponse GetSkyfishVideo(int videoId) { + private IHttpResponse GetSkyfishVideoStream(int videoId) { return Get($"/media/{videoId}/metadata/stream_url"); } diff --git a/src/Limbo.Integrations.Skyfish/Models/Media/SkyfishMediaItem.cs b/src/Limbo.Integrations.Skyfish/Models/Media/SkyfishMediaItem.cs new file mode 100644 index 0000000..4ef6576 --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Models/Media/SkyfishMediaItem.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json.Linq; +using Skybrud.Essentials.Json; +using Skybrud.Essentials.Json.Extensions; + +namespace Limbo.Integrations.Skyfish.Models.Media { + + public class SkyfishMediaItem : JsonObjectBase { + + #region Properties + + public string[] Keywords { get; } + + public int Height { get; } + + public int Width { get; } + + public int UniqueMediaId { get; } + + public string Title { get; } + + public string Description { get; } + + public string ThumbnailUrl { get; } + + public string ThumbnailUrlSsl { get; } + + public string FileName { get; } + + public string FileMimeType { get; } + + public long FileDiskSize { get; } + + #endregion + + protected SkyfishMediaItem(JObject json) : base(json) { + Keywords = json.GetStringArray("keywords"); + Height = json.GetInt32("height"); + Width = json.GetInt32("width"); + UniqueMediaId = json.GetInt32("unique_media_id"); + Title = json.GetString("title"); + Description = json.GetString("description"); + ThumbnailUrl = json.GetString("thumbnail_url"); + ThumbnailUrlSsl = json.GetString("thumbnail_url_ssl"); + FileName = json.GetString("filename"); + FileDiskSize = json.GetInt32("file_disksize"); + FileMimeType = json.GetString("file_mimetype"); + } + + public static SkyfishMediaItem Parse(JObject json) { + return json == null ? null : new SkyfishMediaItem(json); + + } + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Models/Media/SkyfishMediaType.cs b/src/Limbo.Integrations.Skyfish/Models/Media/SkyfishMediaType.cs new file mode 100644 index 0000000..d01b89c --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Models/Media/SkyfishMediaType.cs @@ -0,0 +1,12 @@ +namespace Limbo.Integrations.Skyfish.Models.Media { + + public enum SkyfishMediaType { + Unrecognized = -1, + Unspecified = 0, + Image, + Vector, + Video, + Generic, + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Models/Search/SkyfishSearchResult.cs b/src/Limbo.Integrations.Skyfish/Models/Search/SkyfishSearchResult.cs new file mode 100644 index 0000000..129c87e --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Models/Search/SkyfishSearchResult.cs @@ -0,0 +1,25 @@ +using Limbo.Integrations.Skyfish.Models.Media; +using Newtonsoft.Json.Linq; +using Skybrud.Essentials.Json; +using Skybrud.Essentials.Json.Extensions; + +namespace Limbo.Integrations.Skyfish.Models.Search { + + public class SkyfishSearchResult : JsonObjectBase { + + public int Hits { get; } + + public SkyfishMediaItem[] Media { get; } + + private SkyfishSearchResult(JObject json) : base(json) { + Hits = json.GetInt32("response.hits"); + Media = json.GetArrayItems("response.media", SkyfishMediaItem.Parse); + } + + public static SkyfishSearchResult Parse(JObject json) { + return json == null ? null : new SkyfishSearchResult(json); + } + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Options/Search/SkyfishSearchOptions.cs b/src/Limbo.Integrations.Skyfish/Options/Search/SkyfishSearchOptions.cs new file mode 100644 index 0000000..5cfe240 --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Options/Search/SkyfishSearchOptions.cs @@ -0,0 +1,92 @@ +using System.Collections.Generic; +using System.Linq; +using Limbo.Integrations.Skyfish.Models.Media; +using Skybrud.Essentials.Http; +using Skybrud.Essentials.Http.Collections; +using Skybrud.Essentials.Http.Options; +using Skybrud.Essentials.Strings.Extensions; + +namespace Limbo.Integrations.Skyfish.Options.Videos { + + /// + /// Class with options for getting a list of videos. + /// + public class SkyfishSearchOptions : IHttpRequestOptions { + + #region Properties + + /// + /// Gets or sets the ID of a specific media to be returned. + /// + public int MediaId { get; set; } + + /// + /// Gets or sets the unique ID of a specific media to be returned. + /// + public int UniqueMediaId { get; set; } + + /// + /// Gets or sets a list of values (field) to be returned for each media. + /// + public List ReturnValues { get; set; } + + /// + /// Gets or sets the media types to be returned. + /// + public List MediaTypes { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance with default options. + /// + public SkyfishSearchOptions() { + + ReturnValues = new List { + "unique_media_id", + "height", + "width", + "title", + "description", + "thumbnail_url", + "thumbnail_url_ssl", + "filename", + "file_disksize", + "file_mimetype" + }; + + MediaTypes = new List(); + + } + + #endregion + + #region Member methods + + /// + public IHttpRequest GetRequest() { + + // Initialize the query string + IHttpQueryString query = new HttpQueryString(); + + // Append optional parameters if specified + if (MediaId > 0) query.Add("media_id", MediaId); + if (UniqueMediaId > 0) query.Add("unique_media_id", UniqueMediaId); + + // The documentation says to split the values with +, but the API doesn't support URL encoded + chars + // Instead we can split by space and it turns into ASCII + chars ¯\(º_o)/¯ + if (ReturnValues != null && ReturnValues.Count > 0) query.Add("return_values", string.Join(" ", ReturnValues)); + if (MediaTypes != null && MediaTypes.Count > 0) query.Add("media_type", string.Join(" ", from type in MediaTypes select type.ToUnderscore())); + + // Initialize a new GET request + return HttpRequest.Get("/search", query); + + } + + #endregion + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Responses/Search/SkyfishSearchResponse.cs b/src/Limbo.Integrations.Skyfish/Responses/Search/SkyfishSearchResponse.cs new file mode 100644 index 0000000..7c9cc00 --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Responses/Search/SkyfishSearchResponse.cs @@ -0,0 +1,14 @@ +using Skybrud.Essentials.Http; +using Limbo.Integrations.Skyfish.Models.Search; + +namespace Limbo.Integrations.Skyfish.Responses.Search { + + public class SkyfishSearchResponse : SkyfishResponse { + + public SkyfishSearchResponse(IHttpResponse response) : base(response) { + Body = ParseJsonObject(response.Body, SkyfishSearchResult.Parse); + } + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/Responses/SkyfishResponse.cs b/src/Limbo.Integrations.Skyfish/Responses/SkyfishResponse.cs new file mode 100644 index 0000000..ccf3a9f --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/Responses/SkyfishResponse.cs @@ -0,0 +1,42 @@ +using System.Net; +using Skybrud.Essentials.Http; +using Limbo.Integrations.Skyfish.Exceptions; + +namespace Limbo.Integrations.Skyfish.Responses { + + /// + /// Class representing a response from the Skyfish API. + /// + public class SkyfishResponse : HttpResponseBase { + + /// + /// Initializes a new instance based on the specified . + /// + /// The instance of representing the raw response. + public SkyfishResponse(IHttpResponse response) : base(response) { + if (response.StatusCode == HttpStatusCode.OK) return; + if (response.StatusCode == HttpStatusCode.Created) return; + throw new SkyfishHttpException(response); + } + + } + + /// + /// Class representing a response from the Skyfish API. + /// + public class SkyfishResponse : SkyfishResponse { + + /// + /// /// Gets the body of the response. + /// + public T Body { get; protected set; } + + /// + /// Initializes a new instance based on the specified . + /// + /// The instance of representing the raw response. + public SkyfishResponse(IHttpResponse response) : base(response) { } + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/SkyfishHttpHelper.cs b/src/Limbo.Integrations.Skyfish/SkyfishHttpHelper.cs new file mode 100644 index 0000000..a04b982 --- /dev/null +++ b/src/Limbo.Integrations.Skyfish/SkyfishHttpHelper.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using Limbo.Integrations.Skyfish.Models.Media; +using Limbo.Integrations.Skyfish.Options.Videos; +using Limbo.Integrations.Skyfish.Responses.Search; + +namespace Limbo.Integrations.Skyfish { + + public class SkyfishHttpHelper { + + #region Properties + + public SkyfishHttpService Service { get; } + + #endregion + + #region Constructors + + public SkyfishHttpHelper(SkyfishHttpService service) { + Service = service ?? throw new ArgumentNullException(nameof(service)); + } + + #endregion + + #region Member methods + + public SkyfishMediaItem GetVideoByMediaId(int mediaId) { + + // Search for the video in the via the Search API + SkyfishSearchResponse response = Service.Search.Search(new SkyfishSearchOptions { + MediaId = mediaId + }); + + + return response.Body.Media.FirstOrDefault(); + + } + + public SkyfishMediaItem GetVideoByUniqueMediaId(int uniqueMediaId) { + + // Search for the video in the via the Search API + SkyfishSearchResponse response = Service.Search.Search(new SkyfishSearchOptions { + UniqueMediaId = uniqueMediaId + }); + + + return response.Body.Media.FirstOrDefault(); + + } + + public string GetEmbedUrlByUniqueMediaId(int uniqueMediaId) { + return Service.GetEmbedUrl(uniqueMediaId); + } + + #endregion + + } + +} \ No newline at end of file diff --git a/src/Limbo.Integrations.Skyfish/SkyfishHttpService.cs b/src/Limbo.Integrations.Skyfish/SkyfishHttpService.cs index 2c0102f..5d5986b 100644 --- a/src/Limbo.Integrations.Skyfish/SkyfishHttpService.cs +++ b/src/Limbo.Integrations.Skyfish/SkyfishHttpService.cs @@ -1,17 +1,20 @@ using System; +using Limbo.Integrations.Skyfish.Endpoints; using Limbo.Integrations.Skyfish.Http; -using Limbo.Integrations.Skyfish.Models; namespace Limbo.Integrations.Skyfish { public class SkyfishHttpService { public SkyfishHttpClient Client { get; } + public SkyfishSearchEndpoint Search { get; } + private SkyfishHttpService(SkyfishHttpClient client) { Client = client; + Search = new SkyfishSearchEndpoint(this); } - public SkyfishVideo GetVideo(int id) { - return Client.GetVideo(id); + public string GetEmbedUrl(int uniqueMediaId) { + return Client.GetEmbedUrl(uniqueMediaId); } public static SkyfishHttpService CreateFromKeys(string publicKey, string secretKey, string username, string password) {