Funguje stahovani serii/epizod
This commit is contained in:
@@ -5,6 +5,6 @@ namespace StreamCinemaLib.API;
|
||||
public class FilterHits
|
||||
{
|
||||
public FilterTotalHits? total {get;set;}
|
||||
public double max_score {get;set;}
|
||||
public double? max_score {get;set;}
|
||||
public List<MediaInfo>? hits {get; set;}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace StreamCinemaLib.API;
|
||||
public class MediaInfo
|
||||
{
|
||||
public string? _index { get; set; }
|
||||
public string? _id { get; set; }
|
||||
public double _score { get; set; }
|
||||
public string _id { get; set; }
|
||||
public double? _score { get; set; }
|
||||
//"_ignored": [
|
||||
// "i18n_info_labels.plot.keyword"
|
||||
//],
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -14,21 +16,111 @@ public class Metadata
|
||||
private static readonly Uri ApiRoot = new Uri("https://plugin.sc2.zone/api/");
|
||||
private static readonly Uri ApiFilter = new Uri(ApiRoot, "media/filter/v2/");
|
||||
|
||||
public static Task<FilterResponse?> SearchAsync(string expression, ItemOrder order = ItemOrder.Descending, FilterSortBy sort = FilterSortBy.Score, ItemType type = ItemType.All, int limit = 0, CancellationToken cancel = default)
|
||||
/// <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)
|
||||
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)}&size={limit.ToString()}";
|
||||
uri.Query = $"?access_token={AccessToken}&value={Uri.EscapeDataString(expression)}&order={ToString(order)}&sort={ToString(sort)}&type={ToString(type)}&from={offset.ToString()}&size={limit.ToString()}";
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions();
|
||||
options.Converters.Add(new CustomDateTimeConverter());
|
||||
return Program._http.GetFromJsonAsync<FilterResponse>(uri.Uri, options, cancel);
|
||||
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)
|
||||
@@ -79,6 +171,12 @@ public class Metadata
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
17
StreamCinemaLib/API/Stream.cs
Normal file
17
StreamCinemaLib/API/Stream.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace StreamCinemaLib.API;
|
||||
|
||||
public class Stream
|
||||
{
|
||||
public string _id {get; set;}
|
||||
public string name {get;set;}
|
||||
public string media {get;set;}
|
||||
public string provider {get;set;}
|
||||
public DateTime? date_added {get;set;}
|
||||
public string ident {get;set;}
|
||||
public long? size {get;set;}
|
||||
public List<StreamAudio>? audio {get;set;}
|
||||
public List<StreamVideo>? video {get;set;}
|
||||
public List<StreamSubtitle>? subtitles {get;set;}
|
||||
}
|
||||
10
StreamCinemaLib/API/StreamAudio.cs
Normal file
10
StreamCinemaLib/API/StreamAudio.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace StreamCinemaLib.API;
|
||||
|
||||
public class StreamAudio
|
||||
{
|
||||
public string language { get; set; } // two letter ISO code
|
||||
public string codec { get; set; } // ie. AAC
|
||||
public int channels { get; set; }
|
||||
}
|
||||
9
StreamCinemaLib/API/StreamSubtitle.cs
Normal file
9
StreamCinemaLib/API/StreamSubtitle.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace StreamCinemaLib.API;
|
||||
|
||||
public class StreamSubtitle
|
||||
{
|
||||
public string language { get; set; } // two letter ISO code
|
||||
public bool forced { get; set; }
|
||||
}
|
||||
15
StreamCinemaLib/API/StreamVideo.cs
Normal file
15
StreamCinemaLib/API/StreamVideo.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StreamCinemaLib.API;
|
||||
|
||||
public class StreamVideo
|
||||
{
|
||||
public int width { get; set; } // horizontal resolution in pixels
|
||||
public int height { get; set; } // vertical resolution in pixels
|
||||
public string codec { get; set; } // ie. AVC
|
||||
public double? aspect { get; set; } // aspect ratio, ie. 1.778
|
||||
[JsonPropertyName("3d")]
|
||||
public bool is3d { get; set; }
|
||||
public double? duration { get; set; } // in seconds
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
@@ -9,7 +10,7 @@ public class LinkGenerator
|
||||
{
|
||||
private static readonly BaseEncoding UnixMD5 = new BaseEncoding("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", false);
|
||||
private static readonly Uri WebshareApiUri = new Uri("https://webshare.cz/api/");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates a download link for the given Webshare download.
|
||||
/// </summary>
|
||||
@@ -22,24 +23,38 @@ public class LinkGenerator
|
||||
// Obtain the download password salt
|
||||
// Example response: <?xml version="1.0" encoding="UTF-8"?><response><status>OK</status><salt>UX8y8Fpa</salt><app_version>30</app_version></response>
|
||||
Uri saltUri = new Uri(WebshareApiUri, "file_password_salt/");
|
||||
HttpResponseMessage saltRes = await Program._http.PostAsync(saltUri, new FormUrlEncodedContent(new [] { new KeyValuePair<string, string>("ident", fileId) }), cancel);
|
||||
HttpResponseMessage saltRes = await Program._http.PostAsync(saltUri, new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("ident", fileId) }), cancel);
|
||||
if (!saltRes.IsSuccessStatusCode)
|
||||
throw new IOException("Failed to get password salt from Webshare API.");
|
||||
|
||||
XmlReader saltR = XmlReader.Create(saltRes.Content.ReadAsStream());
|
||||
saltR.ReadStartElement("response");
|
||||
ThrowIfStatusNotOK(saltR);
|
||||
string salt = saltR.ReadElementContentAsString("salt", "");
|
||||
saltR.ReadElementContentAsString("app_version", "");
|
||||
saltR.ReadEndElement(); // response
|
||||
Exception? e = GetExceptionIfStatusNotOK(saltR, out string? code);
|
||||
string password;
|
||||
if (e != null)
|
||||
{
|
||||
if (code == "FILE_PASSWORD_SALT_FATAL_2")
|
||||
{
|
||||
// No password is set
|
||||
password = "";
|
||||
}
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Calculate the download password
|
||||
string salt = saltR.ReadElementContentAsString("salt", "");
|
||||
saltR.ReadElementContentAsString("app_version", "");
|
||||
saltR.ReadEndElement(); // response
|
||||
|
||||
// Calculate the download password
|
||||
string password = CalculatePassword(fileId, fileName, salt);
|
||||
password = CalculatePassword(fileId, fileName, salt);
|
||||
}
|
||||
|
||||
// Obtain the download link
|
||||
// Example response: <?xml version="1.0" encoding="UTF-8"?><response><status>OK</status><link>https://free.19.dl.wsfiles.cz/9209/...</link><app_version>30</app_version></response>
|
||||
Uri linkUri = new Uri(WebshareApiUri, "file_link/");
|
||||
HttpResponseMessage linkRes = await Program._http.PostAsync(linkUri, new FormUrlEncodedContent(new [] {
|
||||
HttpResponseMessage linkRes = await Program._http.PostAsync(linkUri, new FormUrlEncodedContent(new[] {
|
||||
new KeyValuePair<string, string>("ident", fileId),
|
||||
new KeyValuePair<string, string>("download_type", "video_stream"),
|
||||
new KeyValuePair<string, string>("device_uuid", Guid.NewGuid().ToString("X")),
|
||||
@@ -61,14 +76,24 @@ public class LinkGenerator
|
||||
return new Uri(link);
|
||||
}
|
||||
|
||||
private static void ThrowIfStatusNotOK(XmlReader r) {
|
||||
private static Exception? GetExceptionIfStatusNotOK(XmlReader r, out string? code)
|
||||
{
|
||||
string status = r.ReadElementContentAsString("status", "");
|
||||
if (status == "OK")
|
||||
return;
|
||||
if (status == "OK") {
|
||||
code = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
string code = r.ReadElementContentAsString("code", "");
|
||||
code = r.ReadElementContentAsString("code", "");
|
||||
string message = r.ReadElementContentAsString("message", "");
|
||||
throw new IOException(code + ": " + message);
|
||||
return new IOException(code + ": " + message);
|
||||
}
|
||||
|
||||
private static void ThrowIfStatusNotOK(XmlReader r)
|
||||
{
|
||||
Exception? e = GetExceptionIfStatusNotOK(r, out _);
|
||||
if (e != null)
|
||||
throw e;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -40,7 +40,17 @@ public abstract class BasicLayout
|
||||
w.WriteLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
|
||||
w.WriteLine("<title>" + HttpUtility.HtmlEncode(title) + "</title>");
|
||||
w.WriteLine("<style>");
|
||||
// style go here
|
||||
w.WriteLine(".search-results { display: flex; flex-wrap: wrap; }");
|
||||
w.WriteLine(".media-episodes { display: flex; flex-direction: column; gap: 1em; }");
|
||||
w.WriteLine(".search-results > .media-info { width: 25%; }");
|
||||
w.WriteLine(".media-episodes > .media-info { height: 12vh; display: flex; align-items: center; }");
|
||||
w.WriteLine(".search-results .media-art { margin: 1em 1em 0em 1em }");
|
||||
w.WriteLine(".media-episodes .media-art { margin: 1em; width: 12vh; height: 12vh; }");
|
||||
w.WriteLine(".search-results .media-art img { width: 100%; }");
|
||||
w.WriteLine(".media-episodes .media-art img { height: 100%; }");
|
||||
w.WriteLine(".media-title { margin: 0em 1em 0em 1em; font-weight: bold; font-size: larger;}");
|
||||
w.WriteLine(".search-results .media-plot { margin: 0em 1em 1em 1em; height: 4.5em; overflow-y: hidden; color: #999;}");
|
||||
w.WriteLine(".media-episodes .media-plot { margin: 1em; overflow-y: hidden; color: #999;}");
|
||||
w.WriteLine("</style>");
|
||||
w.WriteLine("</head><body>");
|
||||
}
|
||||
|
||||
99
StreamCinemaWeb/Pages/EpisodesPage.cs
Normal file
99
StreamCinemaWeb/Pages/EpisodesPage.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using StreamCinemaLib.API;
|
||||
using StreamCinemaWeb.Layouts;
|
||||
|
||||
namespace StreamCinemaWeb.Pages;
|
||||
|
||||
public class EpisodesPage : BasicLayout
|
||||
{
|
||||
private readonly string _parentId;
|
||||
private readonly string _title;
|
||||
|
||||
public EpisodesPage(string parentId, string title)
|
||||
{
|
||||
this._parentId = parentId;
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
protected override string Title => _title;
|
||||
|
||||
protected override async Task RenderContentAsync(HttpRequest req, TextWriter w)
|
||||
{
|
||||
w.WriteLine("<h1>" + HttpUtility.HtmlEncode(_title) + "</h1>");
|
||||
|
||||
FilterResponse? res = await Metadata.ChildrenAsync(_parentId, sort: FilterSortBy.Episode, cancel: req.HttpContext.RequestAborted);
|
||||
|
||||
if (res == null || res.hits == null || res.hits.hits == null || res.hits.hits.Count == 0)
|
||||
{
|
||||
w.WriteLine("<em>Nic nenalezeno</em>");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (res.hits.hits[0]._source?.children_count == 0)
|
||||
{
|
||||
// Just episodes
|
||||
w.WriteLine("<h2>Epizody</h2>");
|
||||
RenderEpisodes(w, res.hits.hits, _title);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Seasons and episodes
|
||||
foreach (MediaInfo i in res.hits.hits)
|
||||
{
|
||||
if (i._source == null)
|
||||
continue;
|
||||
InfoLabelI18n? label = i._source.i18n_info_labels?.Where(x => x.lang == "cs").FirstOrDefault();
|
||||
if (label == null)
|
||||
label = i._source.i18n_info_labels?.FirstOrDefault();
|
||||
if (label == null)
|
||||
continue;
|
||||
|
||||
w.WriteLine("<h2>Sezóna " + label.title + "</h2>");
|
||||
|
||||
FilterResponse? resEp = await Metadata.ChildrenAsync(i._id, sort: FilterSortBy.Episode, cancel: req.HttpContext.RequestAborted);
|
||||
if (resEp == null || resEp.hits == null || resEp.hits.hits == null || resEp.hits.hits.Count == 0)
|
||||
{
|
||||
w.WriteLine("<em>Nic nenalezeno</em>");
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderEpisodes(w, resEp.hits.hits, _title + " - " + label.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderEpisodes(TextWriter w, IEnumerable<MediaInfo> episodes, string parentTitles)
|
||||
{
|
||||
w.WriteLine("<div class=\"media-episodes\">");
|
||||
foreach (MediaInfo i in episodes)
|
||||
{
|
||||
if (i._source == null)
|
||||
continue;
|
||||
InfoLabelI18n? label = i._source.i18n_info_labels?.Where(x => x.lang == "cs").FirstOrDefault();
|
||||
if (label == null)
|
||||
label = i._source.i18n_info_labels?.FirstOrDefault();
|
||||
if (label == null)
|
||||
continue;
|
||||
|
||||
string? artUriS = label?.art?.poster ?? label?.art?.fanart;
|
||||
Uri? artUri;
|
||||
if (artUriS == null)
|
||||
artUri = null;
|
||||
else if (Metadata.TryGetThumbnail(artUri = new Uri(artUriS), 128, 128, out Uri? artThumbUri))
|
||||
artUri = artThumbUri;
|
||||
|
||||
string streamsUri = "/streams/" + i._id + "/" + HttpUtility.UrlEncode(parentTitles + " - " + label?.title ?? "neznamy");
|
||||
|
||||
w.WriteLine("<div class=\"media-info\">");
|
||||
w.WriteLine("<div class=\"media-art\"><a href=\"" + streamsUri + "\"><img src=\"" + artUri?.ToString() + "\"></a></div>");
|
||||
w.WriteLine("<div class=\"media-title\"><a href=\"" + streamsUri + "\">" + HttpUtility.HtmlEncode(label?.title) + "</a></div>");
|
||||
w.WriteLine("<div class=\"media-plot\">" + HttpUtility.HtmlEncode(label?.plot) + "</div>");
|
||||
w.WriteLine("</div>"); // media-info
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
}
|
||||
42
StreamCinemaWeb/Pages/LinkPage.cs
Normal file
42
StreamCinemaWeb/Pages/LinkPage.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using StreamCinema.Webshare;
|
||||
using StreamCinemaLib.API;
|
||||
using StreamCinemaWeb.Layouts;
|
||||
using LinkGenerator = StreamCinema.Webshare.LinkGenerator;
|
||||
|
||||
namespace StreamCinemaWeb.Pages;
|
||||
|
||||
public class LinkPage : BasicLayout
|
||||
{
|
||||
private readonly string _provider;
|
||||
private readonly string _ident;
|
||||
private readonly string _name;
|
||||
private readonly string _title;
|
||||
|
||||
public LinkPage(string provider, string ident, string name, string title)
|
||||
{
|
||||
this._provider = provider;
|
||||
this._ident = ident;
|
||||
this._name = name;
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
protected override string Title => _title;
|
||||
|
||||
protected override async Task RenderContentAsync(HttpRequest req, TextWriter w)
|
||||
{
|
||||
w.WriteLine("<h1>" + HttpUtility.HtmlEncode(_title) + "</h1>");
|
||||
|
||||
Uri link;
|
||||
switch (_provider)
|
||||
{
|
||||
case "webshare": link = await LinkGenerator.GenerateDownloadLinkAsync(_ident, _name, req.HttpContext.RequestAborted); break;
|
||||
default:
|
||||
w.WriteLine("<em>Provider '" + HttpUtility.HtmlEncode(_provider) + "' není aktuálně podporován.</em>");
|
||||
return;
|
||||
}
|
||||
|
||||
w.WriteLine("<div><a href=\"" + link.ToString() + "\">" + HttpUtility.HtmlEncode(link.ToString()) + "</a></div>");
|
||||
}
|
||||
}
|
||||
24
StreamCinemaWeb/Pages/MediaPage.cs
Normal file
24
StreamCinemaWeb/Pages/MediaPage.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using StreamCinemaWeb.Layouts;
|
||||
|
||||
namespace StreamCinemaWeb.Pages;
|
||||
|
||||
public class MediaPage : BasicLayout
|
||||
{
|
||||
private readonly string _id;
|
||||
private readonly string _title;
|
||||
|
||||
public MediaPage(string id, string title)
|
||||
{
|
||||
this._id = id;
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
protected override string Title => _title;
|
||||
|
||||
protected override async Task RenderContentAsync(HttpRequest req, TextWriter w)
|
||||
{
|
||||
w.WriteLine("<h1>" + HttpUtility.HtmlEncode(_title) + "</h1>");
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,30 @@
|
||||
using System;
|
||||
using StreamCinemaWeb.Layouts;
|
||||
|
||||
using StreamCinemaLib.API;
|
||||
using System.Globalization;
|
||||
using System.Web;
|
||||
|
||||
using StreamCinemaWeb.Layouts;
|
||||
using StreamCinemaLib.API;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace StreamCinemaWeb.Pages;
|
||||
|
||||
public class SearchPage : BasicLayout
|
||||
{
|
||||
private const int PageSize = 30;
|
||||
private const string ParamExpression = "expr";
|
||||
private const string ParamPageIdx = "page";
|
||||
|
||||
protected override string Title => "Hledat filmy a seriály";
|
||||
|
||||
protected override async Task RenderContentAsync(HttpRequest req, TextWriter w)
|
||||
{
|
||||
string expr;
|
||||
if (req.Method != "POST" || (expr = req.Form["expr"]) == null || expr.Trim().Length == 0)
|
||||
string? expr;
|
||||
if ((expr = req.Query["expr"]) == null || expr.Trim().Length == 0)
|
||||
{
|
||||
// Show search
|
||||
w.WriteLine("<h1>Hledat filmy a seriály</h1>");
|
||||
w.WriteLine("<form method=\"POST\" action=\"/\">");
|
||||
w.WriteLine("<input type=\"text\" name=\"expr\" style=\"width:100%;margin:1em\">");
|
||||
w.WriteLine("<form method=\"GET\" action=\"/\">");
|
||||
w.WriteLine("<input type=\"text\" name=\"" + ParamExpression + "\" style=\"width:100%;margin:1em\">");
|
||||
w.WriteLine("<input type=\"submit\" value=\"Hledat\">");
|
||||
w.WriteLine("</form>");
|
||||
|
||||
@@ -26,14 +32,18 @@ public class SearchPage : BasicLayout
|
||||
else
|
||||
{
|
||||
// Execute search
|
||||
FilterResponse? res = await Metadata.SearchAsync(expr, cancel: req.HttpContext.RequestAborted);
|
||||
w.WriteLine("<h1>Hledání:" + HttpUtility.HtmlEncode(expr) + "</h1>");
|
||||
string? pageS = req.Query[ParamPageIdx];
|
||||
int page = pageS != null && int.TryParse(pageS, out int value) ? value : 0;
|
||||
FilterResponse? res = await Metadata.SearchAsync(expr, offset: page * PageSize, limit: PageSize, cancel: req.HttpContext.RequestAborted);
|
||||
|
||||
w.WriteLine("<h1>Hledání: " + HttpUtility.HtmlEncode(expr) + "</h1>");
|
||||
if (res == null || res.hits == null || res.hits.hits == null || res.hits.hits.Count == 0)
|
||||
{
|
||||
w.WriteLine("<em>Nic nenalezeno</em>");
|
||||
}
|
||||
else
|
||||
{
|
||||
w.WriteLine("<div class=\"search-results\">");
|
||||
foreach (MediaInfo i in res.hits.hits)
|
||||
{
|
||||
if (i._source == null)
|
||||
@@ -44,12 +54,23 @@ public class SearchPage : BasicLayout
|
||||
if (label == null)
|
||||
continue;
|
||||
|
||||
string? artUriS = label?.art?.poster ?? label?.art?.fanart;
|
||||
Uri? artUri;
|
||||
if (artUriS == null)
|
||||
artUri = null;
|
||||
else if (Metadata.TryGetThumbnail(artUri = new Uri(artUriS), 300, 300, out Uri? artThumbUri))
|
||||
artUri = artThumbUri;
|
||||
|
||||
string detailUri = (i._source.children_count == 0 ? "media" : "episodes") + "/" + i._id + "/" + HttpUtility.UrlEncode(label?.title ?? "neznamy");
|
||||
|
||||
w.WriteLine("<div class=\"media-info\">");
|
||||
w.WriteLine("<div class=\"media-art\"><img src=\"" + label?.art?.poster ?? label?.art?.fanart + "\"></div>");
|
||||
w.WriteLine("<div class=\"media-title\">" + HttpUtility.HtmlEncode(label?.title) + "</div");
|
||||
w.WriteLine("<div class=\"media-plot\">" + HttpUtility.HtmlEncode(label?.plot) + "</div");
|
||||
w.WriteLine("<div class=\"media-art\"><a href=\"" + detailUri + "\"><img src=\"" + artUri?.ToString() + "\"></a></div>");
|
||||
w.WriteLine("<div class=\"media-title\"><a href=\"" + detailUri + "\">" + HttpUtility.HtmlEncode(label?.title) + "</a></div>");
|
||||
w.WriteLine("<div class=\"media-plot\">" + HttpUtility.HtmlEncode(label?.plot) + "</div>");
|
||||
w.WriteLine("</div>"); // media-info
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
w.WriteLine("<div class=\"btn\"><a href=\"?" + ParamExpression + "=" + HttpUtility.UrlEncode(expr) + "&" + ParamPageIdx + "=" + (page + 1).ToString() + "\">Další</a></div>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
StreamCinemaWeb/Pages/StreamsPage.cs
Normal file
73
StreamCinemaWeb/Pages/StreamsPage.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Web;
|
||||
using StreamCinemaLib.API;
|
||||
using StreamCinemaWeb.Layouts;
|
||||
|
||||
namespace StreamCinemaWeb.Pages;
|
||||
|
||||
public class StreamsPage : BasicLayout
|
||||
{
|
||||
private readonly string _id;
|
||||
private readonly string _title;
|
||||
|
||||
public StreamsPage(string id, string title)
|
||||
{
|
||||
this._id = id;
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
protected override string Title => _title;
|
||||
|
||||
protected override async Task RenderContentAsync(HttpRequest req, TextWriter w)
|
||||
{
|
||||
w.WriteLine("<h1>" + HttpUtility.HtmlEncode(_title) + "</h1>");
|
||||
w.WriteLine("<div><b>Zvolte kvalitu</b></div>");
|
||||
|
||||
StreamCinemaLib.API.Stream[]? res = await Metadata.StreamsAsync(_id, cancel: req.HttpContext.RequestAborted);
|
||||
|
||||
if (res == null || res.Length == 0)
|
||||
{
|
||||
w.WriteLine("<em>Nic nenalezeno</em>");
|
||||
}
|
||||
else
|
||||
{
|
||||
w.WriteLine("<div class=\"media-streams\">");
|
||||
foreach (StreamCinemaLib.API.Stream i in res)
|
||||
{
|
||||
w.WriteLine("<div class=\"media-stream\"><a href=\"/link/" + i.provider + "/" + HttpUtility.UrlEncode(i.ident) + "/" + HttpUtility.UrlEncode(i.name) + "/" + HttpUtility.UrlEncode(_title) + "\">");
|
||||
|
||||
w.WriteLine($"<div class=\"stream-basic\">{Math.Round((double)(i.size ?? 0) / (1024 * 1024 * 1024), 2)} GB ({i.date_added})</div>");
|
||||
|
||||
if (i.video != null && i.video.Count != 0)
|
||||
{
|
||||
StreamVideo a = i.video.First();
|
||||
string is3D = a.is3d ? " (3D)" : "";
|
||||
w.WriteLine($"<div class=\"video-channel\">{a.width}x{a.height} {a.codec}{is3D} {Math.Round((a.duration ?? 0) / 60)} min</div>");
|
||||
}
|
||||
|
||||
if (i.audio != null && i.audio.Count != 0)
|
||||
{
|
||||
w.WriteLine("<div class=\"audio-channels\">");
|
||||
foreach (StreamAudio j in i.audio)
|
||||
{
|
||||
w.WriteLine($"<div class=\"audio-channel\">{j.language} {j.codec} {j.channels}</div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
|
||||
if (i.subtitles != null && i.subtitles.Count != 0)
|
||||
{
|
||||
w.WriteLine("<div class=\"subtitle-channels\">");
|
||||
foreach (StreamSubtitle j in i.subtitles)
|
||||
{
|
||||
w.WriteLine($"<div class=\"subtitle-channel\">{j.language}</div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
|
||||
w.WriteLine("</a></div>");
|
||||
}
|
||||
w.WriteLine("</div>");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
using System.Web;
|
||||
using StreamCinemaWeb.Pages;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapGet("/", new SearchPage().ExecuteAsync);
|
||||
app.MapPost("/", new SearchPage().ExecuteAsync);
|
||||
app.MapGet("/episodes/{id}/{title}", (string id, string title, HttpRequest req) => new EpisodesPage(id, HttpUtility.UrlDecode(title)).ExecuteAsync(req));
|
||||
app.MapGet("/streams/{id}/{title}", (string id, string title, HttpRequest req) => new StreamsPage(id, HttpUtility.UrlDecode(title)).ExecuteAsync(req));
|
||||
app.MapGet("/media/{id}/{title}", (string id, string title, HttpRequest req) => new MediaPage(id, HttpUtility.UrlDecode(title)).ExecuteAsync(req));
|
||||
app.MapGet("/link/{provider}/{ident}/{name}/{title}", (string provider, string ident, string name, string title, HttpRequest req) => new LinkPage(provider, HttpUtility.UrlDecode(ident), HttpUtility.UrlDecode(name), HttpUtility.UrlDecode(title)).ExecuteAsync(req));
|
||||
|
||||
app.Run();
|
||||
|
||||
Reference in New Issue
Block a user