using Microsoft.AspNetCore.Mvc; using Shared.Engine.Utilities; using Shared.PlaywrightCore; using System.Net.Http; namespace Catalog.Controllers { public class CardController : BaseController { [HttpGet] [Route("catalog/card")] public async Task Index(string plugin, string uri, string type) { var init = ModInit.goInit(plugin)?.Clone(); if (init == null || !init.enable) return BadRequest("init not found"); var rch = new RchClient(HttpContext, host, init, requestInfo); if (rch.IsNotConnected()) rch.Disabled(); var proxyManager = new ProxyManager(init, rch); var proxy = proxyManager.BaseGet(); string memKey = $"catalog:card:{plugin}:{uri}"; return await InvkSemaphore(memKey, rch, async () => { if (!hybridCache.TryGetValue(memKey, out JObject jo, inmemory: false)) { string url = $"{init.host}/{uri}"; var headers = httpHeaders(init); if (init.args != null) url = url.Contains("?") ? $"{url}&{init.args}" : $"{url}?{init.args}"; if (init.card_parse.initUrl != null) url = CSharpEval.Execute(init.card_parse.initUrl, new CatalogInitUrlCard(init.host, init.args, uri, HttpContext.Request.Query, type)); if (init.card_parse.initHeader != null) headers = CSharpEval.Execute>(init.card_parse.initHeader, new CatalogInitHeader(url, headers)); reset: string html = null; if (!string.IsNullOrEmpty(init.card_parse.postData)) { string mediaType = init.card_parse.postData.StartsWith("{") || init.card_parse.postData.StartsWith("[") ? "application/json" : "application/x-www-form-urlencoded"; var httpdata = new StringContent(init.card_parse.postData, Encoding.UTF8, mediaType); html = rch.enable ? await rch.Post(url, init.card_parse.postData, headers, useDefaultHeaders: init.useDefaultHeaders) : await Http.Post(url, httpdata, headers: headers, proxy: proxy.proxy, timeoutSeconds: init.timeout, httpversion: init.GetHttpVersion(), useDefaultHeaders: init.useDefaultHeaders); } else { html = rch.enable ? await rch.Get(url, headers, useDefaultHeaders: init.useDefaultHeaders) : init.priorityBrowser == "playwright" ? await PlaywrightBrowser.Get(init, url, headers, proxy.data, cookies: init.cookies) : await Http.Get(url, headers: headers, proxy: proxy.proxy, timeoutSeconds: init.timeout, httpversion: init.GetHttpVersion(), useDefaultHeaders: init.useDefaultHeaders); } if (html == null) { if (ModInit.IsRhubFallback(init)) goto reset; proxyManager.Refresh(); return BadRequest("html"); } proxyManager.Success(); var parse = init.card_parse; bool? jsonPath = parse.jsonPath; if (jsonPath == null) jsonPath = init.jsonPath; #region parse doc/json HtmlNode node = null; JToken json = null; if (jsonPath == true) { try { json = JToken.Parse(html); if (!string.IsNullOrEmpty(parse.node)) { json = json.SelectToken(parse.node); if (json == null) return BadRequest("parse.node"); } } catch { json = null; return BadRequest("json"); } } else { var doc = new HtmlDocument(); doc.LoadHtml(html); node = doc.DocumentNode; } #endregion #region name / original_name / year string name; string original_name; string year; if (jsonPath == true) { name = ModInit.nodeValue(json, parse.name, host)?.ToString(); original_name = ModInit.nodeValue(json, parse.original_name, host)?.ToString(); year = ModInit.nodeValue(json, parse.year, host)?.ToString(); } else { name = ModInit.nodeValue(node, parse.name, host)?.ToString(); original_name = ModInit.nodeValue(node, parse.original_name, host)?.ToString(); year = ModInit.nodeValue(node, parse.year, host)?.ToString(); } #endregion #region img string img = jsonPath == true ? ModInit.nodeValue(json, parse.image, host)?.ToString() : ModInit.nodeValue(node, parse.image, host)?.ToString(); if (img != null) { img = img.Replace("&", "&").Replace("\\", ""); if (img.StartsWith("../")) img = $"{init.host}/{img.Replace("../", "")}"; else if (img.StartsWith("//")) img = $"https:{img}"; else if (img.StartsWith("/")) img = init.host + img; else if (!img.StartsWith("http")) img = $"{init.host}/{img}"; } #endregion jo = new JObject() { ["id"] = uri.Trim(), ["img"] = PosterApi.Size(host, img), ["vote_average"] = 0, ["genres"] = new JArray(), ["production_countries"] = new JArray(), ["production_companies"] = new JArray() }; string overview = jsonPath == true ? ModInit.nodeValue(json, parse.description, host)?.ToString() : ModInit.nodeValue(node, parse.description, host)?.ToString(); if (!string.IsNullOrEmpty(overview)) jo["overview"] = overview; if (type == "tv") { jo["first_air_date"] = year; jo["name"] = name; if (!string.IsNullOrEmpty(original_name)) jo["original_name"] = original_name; } else { jo["release_date"] = year; jo["title"] = name; if (!string.IsNullOrEmpty(original_name)) jo["original_title"] = original_name; } #region card_args if (init.card_args != null) { foreach (var arg in init.card_args) { object val = jsonPath == true ? ModInit.nodeValue(json, arg, host) : ModInit.nodeValue(node, arg, host); ModInit.setArgsValue(arg, val, jo); } } #endregion if (init.tmdb_injects != null && init.tmdb_injects.Length > 0) await Injects(year, jo, init.tmdb_injects); if (!jo.ContainsKey("tagline") && !string.IsNullOrEmpty(original_name)) jo["tagline"] = original_name; hybridCache.Set(memKey, jo, cacheTimeBase(init.cache_time, init: init), inmemory: false); } return ContentTo(JsonConvertPool.SerializeObject(jo)); }); } #region TMDB Injects static readonly string[] defaultInjectskeys = [ "imdb_id", "external_ids", "backdrop_path", "created_by", "genres", "production_companies", "production_countries", "content_ratings", "episode_run_time", "languages", "number_of_episodes", "number_of_seasons", "last_episode_to_air", "origin_country", "original_language", "status", "networks", "seasons", "type", "budget", "spoken_languages", "alternative_titles", "keywords", // &append_to_response= "videos", "credits", "recommendations", "similar", ]; static readonly string[] addEmptykeys = [ "tagline", "overview", "first_air_date", "last_air_date", "release_date", "runtime" ]; async Task Injects(string year, JObject jo, string[] keys) { if (!jo.ContainsKey("imdb_id") && !jo.ContainsKey("original_title") && !jo.ContainsKey("original_name")) return; if (keys.Length == 1 && keys[0] == "default") keys = defaultInjectskeys; #region Поиск карточки в TMDB string imdbId = null; if (jo.ContainsKey("imdb_id")) imdbId = jo["imdb_id"]?.ToString(); var header = HeadersModel.Init(("localrequest", AppInit.rootPasswd)); long id = 0; string cat = string.Empty; if (!string.IsNullOrWhiteSpace(imdbId) && imdbId.StartsWith("tt")) { var find = await Http.Get($"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/find/{imdbId}?external_source=imdb_id&api_key={AppInit.conf.tmdb.api_key}", timeoutSeconds: 5, headers: header); if (find != null) { foreach (string key in new string[] { "movie_results", "tv_results" }) { if (find.ContainsKey(key)) { var movies = find[key] as JArray; if (movies != null && movies.Count > 0) { id = movies[0].Value("id"); cat = key == "movie_results" ? "movie" : "tv"; break; } } } } } else if (jo.ContainsKey("original_title") || jo.ContainsKey("original_name")) { string type = jo.ContainsKey("original_title") ? "movie" : "tv"; string originalTitle = jo.Value(type == "movie" ? "original_title" : "original_name"); if (!string.IsNullOrEmpty(originalTitle) && int.TryParse(year.Split("-")[0], out int _year) && _year > 0) { var searchMovie = await Http.Get($"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/search/{type}?query={HttpUtility.UrlEncode(originalTitle)}&api_key={AppInit.conf.tmdb.api_key}", timeoutSeconds: 5, headers: header); if (searchMovie != null && searchMovie.ContainsKey("results")) { var results = searchMovie["results"] as JArray; if (results != null && results.Count > 0) { long foundId = 0; for (int i = 0; i < results.Count; i++) { var item = results[i] as JObject; if (item == null) continue; string date = item.Value("release_date") ?? item.Value("first_air_date"); if (string.IsNullOrEmpty(date)) continue; // date is usually in format YYYY-MM-DD, take first 4 chars string yearStr = date.Length >= 4 ? date.Substring(0, 4) : date; if (int.TryParse(yearStr, out int itemYear) && itemYear == _year) { string _s1 = StringConvert.SearchName(originalTitle); string _s2 = StringConvert.SearchName(item.Value(type == "movie" ? "original_title" : "original_name")); if (!string.IsNullOrEmpty(_s1) && !string.IsNullOrEmpty(_s2) && _s1 == _s2) { foundId = item.Value("id"); break; } } } if (foundId != 0) { id = foundId; cat = type; } } } } } #endregion if (id == 0) return; string append = "content_ratings,release_dates,external_ids,keywords,alternative_titles,videos,credits,recommendations,similar"; var result = await Http.Get($"http://{AppInit.conf.listen.localhost}:{AppInit.conf.listen.port}/tmdb/api/3/{cat}/{id}?api_key={AppInit.conf.tmdb.api_key}&append_to_response={append}&language=ru", timeoutSeconds: 5, headers: header); if (result == null) return; foreach (string key in keys) { if (key is "videos" or "recommendations" or "similar") { if (result.ContainsKey(key) && result[key] is JObject _jo && _jo.ContainsKey("results")) jo[key] = _jo["results"]; } else if (result.ContainsKey(key)) { jo[key] = result[key]; } } if (result.ContainsKey("id")) jo["tmdb_id"] = result["id"]; if (!jo.ContainsKey("imdb_id") && result.ContainsKey("external_ids") && result["external_ids"] is JObject extIds && extIds.ContainsKey("imdb_id")) jo["imdb_id"] = extIds["imdb_id"]; foreach (string key in addEmptykeys) { if (!jo.ContainsKey(key) && result.ContainsKey(key)) { var tok = result[key]; if (tok == null) continue; if (tok.Type == JTokenType.String) { var str = tok.Value(); if (!string.IsNullOrWhiteSpace(str)) jo[key] = str; } else { jo[key] = tok; } } } } #endregion } }