193 lines
7.4 KiB
C#
193 lines
7.4 KiB
C#
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace StreamCinemaLib.API;
|
|
|
|
public class Metadata
|
|
{
|
|
private const string UserAgent = "User-Agent: XBMC/19 (Linux 6.1; http://www.xbmc.org)";
|
|
private const string AccessToken = "9ajdu4xyn1ig8nxsodr3";
|
|
private const int MaxPageLimit = 1000;
|
|
|
|
private static readonly Uri ApiRoot = new Uri("https://plugin.sc2.zone/api/");
|
|
private static readonly Uri ApiFilter = new Uri(ApiRoot, "media/filter/v2/");
|
|
|
|
/// <summary>
|
|
/// Searches for media using an expression.
|
|
/// </summary>
|
|
/// <param name="expression">Expression like part of title.</param>
|
|
/// <param name="order">Result ordering direction.</param>
|
|
/// <param name="sort">Result ordering column.</param>
|
|
/// <param name="type">Allowed result type.</param>
|
|
/// <param name="offset">Item offset.</param>
|
|
/// <param name="limit">Maximum returned items count.</param>
|
|
/// <param name="cancel">Asynchronous cancellation.</param>
|
|
/// <returns>Response.</returns>
|
|
public static Task<FilterResponse?> SearchAsync(string expression, ItemOrder order = ItemOrder.Descending, FilterSortBy sort = FilterSortBy.Score, ItemType type = ItemType.All, int offset = 0, int limit = 0, CancellationToken cancel = default)
|
|
{
|
|
if (expression == null)
|
|
throw new ArgumentNullException();
|
|
if (limit < 0 || offset < 0)
|
|
throw new ArgumentOutOfRangeException();
|
|
if (limit == 0 || limit > MaxPageLimit)
|
|
limit = MaxPageLimit;
|
|
|
|
UriBuilder uri = new UriBuilder(new Uri(ApiFilter, "search"));
|
|
uri.Query = $"?access_token={AccessToken}&value={Uri.EscapeDataString(expression)}&order={ToString(order)}&sort={ToString(sort)}&type={ToString(type)}&from={offset.ToString()}&size={limit.ToString()}";
|
|
|
|
return Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Search for child media.
|
|
/// </summary>
|
|
/// <param name="parentId">Identifier of the parent media.</param>
|
|
/// <param name="sort">Result ordering column.</param>
|
|
/// <param name="cancel">Asynchronous cancellation.</param>
|
|
/// <returns>Response.</returns>
|
|
public static Task<FilterResponse?> ChildrenAsync(string parentId, FilterSortBy sort = FilterSortBy.Episode, CancellationToken cancel = default)
|
|
{
|
|
if (parentId == null)
|
|
throw new ArgumentNullException();
|
|
|
|
UriBuilder uri = new UriBuilder(new Uri(ApiFilter, "parent"));
|
|
uri.Query = $"?access_token={AccessToken}&value={parentId}&sort={ToString(sort)}&size={MaxPageLimit.ToString()}";
|
|
|
|
return Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets available streams for the given media.
|
|
/// </summary>
|
|
/// <param name="id">Media identifier.</param>
|
|
/// <param name="cancel">Asynchronous cancellation.</param>
|
|
/// <returns>Available streams for download.</returns>
|
|
public static Task<Stream[]?> StreamsAsync(string id, CancellationToken cancel = default)
|
|
{
|
|
if (id == null)
|
|
throw new ArgumentNullException();
|
|
|
|
UriBuilder uri = new UriBuilder(new Uri(ApiRoot, "media/" + id + "/streams"));
|
|
uri.Query = $"?access_token={AccessToken}";
|
|
|
|
return Program._http.GetFromJsonAsync<Stream[]>(uri.Uri, CreateFilterJsonOptions(), cancel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get a thumbnail image from a well-known image sources to speed-up loading.
|
|
/// </summary>
|
|
/// <param name="imageUri">Image url with probably full-sized image.</param>
|
|
/// <param name="suggestWidth">Requested image width that may however get rounded or completely ignored.</param>
|
|
/// <param name="suggestHeight">Requested image height that may however get rounded or completely ignored.</param>
|
|
/// <param name="thumbUri">On success the thumbnail address.</param>
|
|
/// <returns>True if thumbnail url got calculated, false otherwise.</returns>
|
|
public static bool TryGetThumbnail(Uri imageUri, int suggestWidth, int suggestHeight, [NotNullWhen(true)] out Uri? thumbUri) {
|
|
if (imageUri == null)
|
|
throw new ArgumentNullException();
|
|
|
|
switch (imageUri.Host) {
|
|
case "image.tmdb.org":
|
|
const string TmdbOrigPrefix = "/t/p/original/";
|
|
if (imageUri.AbsolutePath.StartsWith(TmdbOrigPrefix)) {
|
|
UriBuilder ub = new UriBuilder(imageUri);
|
|
ub.Path = "/t/p/w300_and_h450_bestv2/" + ub.Path.Substring(TmdbOrigPrefix.Length);
|
|
thumbUri = ub.Uri;
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case "img.csfd.cz":
|
|
if (imageUri.AbsolutePath.StartsWith("/files/images/film/posters/")) {
|
|
// 140px, 280px, 420px
|
|
int w;
|
|
if (suggestWidth < 160)
|
|
w = 140;
|
|
else if (suggestWidth < 320)
|
|
w = 280;
|
|
else
|
|
w = 420;
|
|
UriBuilder ub = new UriBuilder(imageUri);
|
|
ub.Host = "image.pmgstatic.com";
|
|
ub.Path = "/cache/resized/w" + w.ToString(CultureInfo.InvariantCulture) + ub.Path;
|
|
thumbUri = ub.Uri;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
thumbUri = null;
|
|
return false;
|
|
}
|
|
|
|
private static string ToString(ItemOrder value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case ItemOrder.Ascending: return "asc";
|
|
case ItemOrder.Descending: return "desc";
|
|
default: throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
private static string ToString(FilterSortBy value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case FilterSortBy.Score: return "score";
|
|
case FilterSortBy.Year: return "year";
|
|
case FilterSortBy.Premiered: return "premiered";
|
|
case FilterSortBy.DateAdded: return "dateAdded";
|
|
case FilterSortBy.LastChildrenDateAdded: return "lastChildrenDateAdded";
|
|
case FilterSortBy.LastChildPremiered: return "lastChildPremiered";
|
|
case FilterSortBy.Title: return "title";
|
|
case FilterSortBy.PlayCount: return "playCount";
|
|
case FilterSortBy.LastSeen: return "lastSeen";
|
|
case FilterSortBy.Episode: return "episode";
|
|
case FilterSortBy.News: return "news";
|
|
case FilterSortBy.Popularity: return "popularity";
|
|
case FilterSortBy.Trending: return "trending";
|
|
case FilterSortBy.LangDateAdded: return "langDateAdded";
|
|
case FilterSortBy.Custom: return "custom";
|
|
default: throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
private static string ToString(ItemType value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case ItemType.TVShow: return "tvshow";
|
|
case ItemType.Concert: return "concert";
|
|
case ItemType.Anime: return "anime";
|
|
case ItemType.Movie: return "movie";
|
|
case ItemType.Season: return "season";
|
|
case ItemType.Episode: return "episode";
|
|
case ItemType.All: return "*";
|
|
default: throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
|
|
private static JsonSerializerOptions CreateFilterJsonOptions() {
|
|
JsonSerializerOptions options = new JsonSerializerOptions();
|
|
options.Converters.Add(new CustomDateTimeConverter());
|
|
return options;
|
|
}
|
|
|
|
sealed class CustomDateTimeConverter : JsonConverter<DateTime>
|
|
{
|
|
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
|
{
|
|
return DateTime.Parse(reader.GetString());
|
|
}
|
|
|
|
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
}
|