mirror of
https://github.com/lampame/lampac-ukraine.git
synced 2026-04-16 09:22:21 +00:00
feat: implement Uaflix authentication and cookie management with login, cookie parsing, and caching.
This commit is contained in:
parent
4d4ac22601
commit
bc1fbac530
@ -62,6 +62,9 @@ modules - optional, if not specified, all modules from the repository will be in
|
|||||||
"enable": true,
|
"enable": true,
|
||||||
"domain": "https://uaflix.net",
|
"domain": "https://uaflix.net",
|
||||||
"displayname": "Uaflix",
|
"displayname": "Uaflix",
|
||||||
|
"login": null,
|
||||||
|
"passwd": null,
|
||||||
|
"cookie": null,
|
||||||
"webcorshost": null,
|
"webcorshost": null,
|
||||||
"streamproxy": false,
|
"streamproxy": false,
|
||||||
"useproxy": false,
|
"useproxy": false,
|
||||||
|
|||||||
@ -18,7 +18,7 @@ using Uaflix.Models;
|
|||||||
namespace Uaflix.Controllers
|
namespace Uaflix.Controllers
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Controller : BaseOnlineController
|
public class Controller : BaseOnlineController<UaflixSettings>
|
||||||
{
|
{
|
||||||
ProxyManager proxyManager;
|
ProxyManager proxyManager;
|
||||||
|
|
||||||
@ -42,7 +42,8 @@ namespace Uaflix.Controllers
|
|||||||
OnLog($"Uaflix Index: kinopoisk_id={kinopoisk_id}, imdb_id={imdb_id}, id={id}");
|
OnLog($"Uaflix Index: kinopoisk_id={kinopoisk_id}, imdb_id={imdb_id}, id={id}");
|
||||||
OnLog($"Uaflix Index: year={year}, source={source}, t={t}, e={e}, rjson={rjson}");
|
OnLog($"Uaflix Index: year={year}, source={source}, t={t}, e={e}, rjson={rjson}");
|
||||||
|
|
||||||
var invoke = new UaflixInvoke(init, hybridCache, OnLog, proxyManager);
|
var auth = new UaflixAuth(init, memoryCache, OnLog, proxyManager);
|
||||||
|
var invoke = new UaflixInvoke(init, hybridCache, OnLog, proxyManager, auth);
|
||||||
|
|
||||||
// Обробка параметра checksearch - повертаємо спеціальну відповідь для валідації
|
// Обробка параметра checksearch - повертаємо спеціальну відповідь для валідації
|
||||||
if (checksearch)
|
if (checksearch)
|
||||||
@ -52,32 +53,21 @@ namespace Uaflix.Controllers
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string filmTitle = !string.IsNullOrEmpty(title) ? title : original_title;
|
bool hasContent = await invoke.CheckSearchAvailability(title, original_title);
|
||||||
string searchUrl = $"{init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(filmTitle)}";
|
if (hasContent)
|
||||||
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", init.host) };
|
|
||||||
|
|
||||||
var searchHtml = await Http.Get(init.cors(searchUrl), headers: headers, proxy: proxyManager.Get(), timeoutSeconds: 10);
|
|
||||||
|
|
||||||
// Швидка перевірка наявності результатів без повного парсингу
|
|
||||||
if (!string.IsNullOrEmpty(searchHtml) &&
|
|
||||||
(searchHtml.Contains("sres-wrap") || searchHtml.Contains("sres-item") || searchHtml.Contains("search-results")))
|
|
||||||
{
|
{
|
||||||
// Якщо знайдено контент, повертаємо "data-json=" для валідації
|
OnLog("checksearch: Контент знайдено, повертаю валідаційний маркер");
|
||||||
OnLog("checksearch: Content found, returning validation response");
|
|
||||||
OnLog("=== RETURN: checksearch validation (data-json=) ===");
|
OnLog("=== RETURN: checksearch validation (data-json=) ===");
|
||||||
return Content("data-json=", "text/plain; charset=utf-8");
|
return Content("data-json=", "text/plain; charset=utf-8");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
OnLog("checksearch: Контент не знайдено");
|
||||||
// Якщо нічого не знайдено, повертаємо OnError
|
OnLog("=== RETURN: checksearch OnError ===");
|
||||||
OnLog("checksearch: No content found");
|
return OnError("uaflix", proxyManager);
|
||||||
OnLog("=== RETURN: checksearch OnError ===");
|
|
||||||
return OnError("uaflix", proxyManager);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
OnLog($"checksearch error: {ex.Message}");
|
OnLog($"checksearch: помилка - {ex.Message}");
|
||||||
OnLog("=== RETURN: checksearch exception OnError ===");
|
OnLog("=== RETURN: checksearch exception OnError ===");
|
||||||
return OnError("uaflix", proxyManager);
|
return OnError("uaflix", proxyManager);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,72 +1,67 @@
|
|||||||
using Newtonsoft.Json;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using Shared;
|
using Shared;
|
||||||
using Shared.Engine;
|
using Shared.Engine;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Shared.Models.Online.Settings;
|
|
||||||
using Shared.Models.Module;
|
using Shared.Models.Module;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.CodeAnalysis.Scripting;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Shared.Models;
|
|
||||||
using Shared.Models.Events;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Uaflix.Models;
|
||||||
|
|
||||||
namespace Uaflix
|
namespace Uaflix
|
||||||
{
|
{
|
||||||
public class ModInit
|
public class ModInit
|
||||||
{
|
{
|
||||||
public static double Version => 3.8;
|
public static double Version => 3.9;
|
||||||
|
|
||||||
|
public static UaflixSettings UaFlix;
|
||||||
|
|
||||||
public static OnlinesSettings UaFlix;
|
|
||||||
public static bool ApnHostProvided;
|
public static bool ApnHostProvided;
|
||||||
|
|
||||||
public static OnlinesSettings Settings
|
public static UaflixSettings Settings
|
||||||
{
|
{
|
||||||
get => UaFlix;
|
get => UaFlix;
|
||||||
set => UaFlix = value;
|
set => UaFlix = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// модуль загружен
|
/// Модуль завантажено.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void loaded(InitspaceModel initspace)
|
public static void loaded(InitspaceModel initspace)
|
||||||
{
|
{
|
||||||
|
UaFlix = new UaflixSettings("Uaflix", "https://uafix.net", streamproxy: false, useproxy: false)
|
||||||
|
|
||||||
UaFlix = new OnlinesSettings("Uaflix", "https://uafix.net", streamproxy: false, useproxy: false)
|
|
||||||
{
|
{
|
||||||
displayname = "UaFlix",
|
displayname = "UaFlix",
|
||||||
group = 0,
|
group = 0,
|
||||||
group_hide = false,
|
group_hide = false,
|
||||||
globalnameproxy = null,
|
globalnameproxy = null,
|
||||||
displayindex = 0,
|
displayindex = 0,
|
||||||
|
login = null,
|
||||||
|
passwd = null,
|
||||||
proxy = new Shared.Models.Base.ProxySettings()
|
proxy = new Shared.Models.Base.ProxySettings()
|
||||||
{
|
{
|
||||||
useAuth = true,
|
useAuth = true,
|
||||||
username = "a",
|
username = "a",
|
||||||
password = "a",
|
password = "a",
|
||||||
list = new string[] { "socks5://IP:PORT" }
|
list = new string[] { "socks5://IP:PORT" }
|
||||||
},
|
}
|
||||||
// Note: OnlinesSettings не має властивості additional, використовуємо інший підхід
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var conf = ModuleInvoke.Conf("Uaflix", UaFlix);
|
var conf = ModuleInvoke.Conf("Uaflix", UaFlix) ?? JObject.FromObject(UaFlix);
|
||||||
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
|
bool hasApn = ApnHelper.TryGetInitConf(conf, out bool apnEnabled, out string apnHost);
|
||||||
conf.Remove("apn");
|
conf.Remove("apn");
|
||||||
conf.Remove("apn_host");
|
conf.Remove("apn_host");
|
||||||
UaFlix = conf.ToObject<OnlinesSettings>();
|
UaFlix = conf.ToObject<UaflixSettings>();
|
||||||
|
|
||||||
if (hasApn)
|
if (hasApn)
|
||||||
ApnHelper.ApplyInitConf(apnEnabled, apnHost, UaFlix);
|
ApnHelper.ApplyInitConf(apnEnabled, apnHost, UaFlix);
|
||||||
|
|
||||||
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
ApnHostProvided = hasApn && apnEnabled && !string.IsNullOrWhiteSpace(apnHost);
|
||||||
if (hasApn && apnEnabled)
|
if (hasApn && apnEnabled)
|
||||||
{
|
{
|
||||||
@ -78,7 +73,7 @@ namespace Uaflix
|
|||||||
UaFlix.apn = null;
|
UaFlix.apn = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Виводити "уточнити пошук"
|
// Показувати «уточнити пошук».
|
||||||
AppInit.conf.online.with_search.Add("uaflix");
|
AppInit.conf.online.with_search.Add("uaflix");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -186,6 +181,7 @@ namespace Uaflix
|
|||||||
_resetTimer = null;
|
_resetTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsDisconnected()
|
public static bool IsDisconnected()
|
||||||
{
|
{
|
||||||
return _disconnectTime is not null
|
return _disconnectTime is not null
|
||||||
|
|||||||
26
Uaflix/Models/UaflixSettings.cs
Normal file
26
Uaflix/Models/UaflixSettings.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Shared.Models.Online.Settings;
|
||||||
|
|
||||||
|
namespace Uaflix.Models
|
||||||
|
{
|
||||||
|
public class UaflixSettings : OnlinesSettings, ICloneable
|
||||||
|
{
|
||||||
|
public UaflixSettings(string plugin, string host, string apihost = null, bool useproxy = false, string token = null, bool enable = true, bool streamproxy = false, bool rip = false, bool forceEncryptToken = false, string rch_access = null, string stream_access = null)
|
||||||
|
: base(plugin, host, apihost, useproxy, token, enable, streamproxy, rip, forceEncryptToken, rch_access, stream_access)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string login { get; set; }
|
||||||
|
|
||||||
|
public string passwd { get; set; }
|
||||||
|
|
||||||
|
public new UaflixSettings Clone()
|
||||||
|
{
|
||||||
|
return (UaflixSettings)MemberwiseClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
object ICloneable.Clone()
|
||||||
|
{
|
||||||
|
return MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
333
Uaflix/UaflixAuth.cs
Normal file
333
Uaflix/UaflixAuth.cs
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
using Shared.Engine;
|
||||||
|
using Shared.Models;
|
||||||
|
using Uaflix.Models;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Uaflix
|
||||||
|
{
|
||||||
|
public sealed class UaflixAuth
|
||||||
|
{
|
||||||
|
private static readonly ConcurrentDictionary<string, CookieContainer> CookieContainers = new();
|
||||||
|
private static readonly ConcurrentDictionary<string, string> CookieHeaders = new();
|
||||||
|
|
||||||
|
private readonly UaflixSettings _init;
|
||||||
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly Action<string> _onLog;
|
||||||
|
private readonly ProxyManager _proxyManager;
|
||||||
|
|
||||||
|
public UaflixAuth(UaflixSettings init, IMemoryCache memoryCache, Action<string> onLog, ProxyManager proxyManager)
|
||||||
|
{
|
||||||
|
_init = init;
|
||||||
|
_memoryCache = memoryCache;
|
||||||
|
_onLog = onLog;
|
||||||
|
_proxyManager = proxyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanUseCredentials => !string.IsNullOrWhiteSpace(_init?.login) && !string.IsNullOrWhiteSpace(_init?.passwd);
|
||||||
|
|
||||||
|
public async ValueTask<string> GetCookieHeaderAsync(bool forceRefresh = false)
|
||||||
|
{
|
||||||
|
if (_init == null || string.IsNullOrWhiteSpace(_init.host))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Uri hostUri;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
hostUri = new Uri(EnsureTrailingSlash(_init.host));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_onLog("UaflixAuth: некоректний host у конфігурації");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string key = BuildAuthKey();
|
||||||
|
|
||||||
|
if (forceRefresh)
|
||||||
|
{
|
||||||
|
CookieHeaders.TryRemove(key, out _);
|
||||||
|
CookieContainers.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CookieHeaders.TryGetValue(key, out string cachedCookie) && !string.IsNullOrWhiteSpace(cachedCookie))
|
||||||
|
return cachedCookie;
|
||||||
|
|
||||||
|
if (CookieContainers.TryGetValue(key, out CookieContainer cachedContainer))
|
||||||
|
{
|
||||||
|
string cookieFromContainer = BuildCookieHeader(cachedContainer, hostUri);
|
||||||
|
if (!string.IsNullOrWhiteSpace(cookieFromContainer))
|
||||||
|
{
|
||||||
|
CookieHeaders[key] = cookieFromContainer;
|
||||||
|
return cookieFromContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_init.cookie))
|
||||||
|
{
|
||||||
|
string normalized = NormalizeCookie(_init.cookie);
|
||||||
|
if (string.IsNullOrWhiteSpace(normalized))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var manualContainer = CreateContainerFromCookie(normalized);
|
||||||
|
CacheAuthState(key, normalized, manualContainer);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanUseCredentials)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string loginThrottleKey = $"uaflix:login:{_init.host}:{_init.login}";
|
||||||
|
if (!forceRefresh && _memoryCache.TryGetValue(loginThrottleKey, out _))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_memoryCache.Set(loginThrottleKey, 0, TimeSpan.FromSeconds(20));
|
||||||
|
|
||||||
|
var authResult = await LoginByCredentials();
|
||||||
|
if (!authResult.success || string.IsNullOrWhiteSpace(authResult.cookie))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
CacheAuthState(key, authResult.cookie, authResult.container);
|
||||||
|
return authResult.cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyCookieHeader(List<HeadersModel> headers, string cookie)
|
||||||
|
{
|
||||||
|
if (headers == null || string.IsNullOrWhiteSpace(cookie))
|
||||||
|
return;
|
||||||
|
|
||||||
|
headers.RemoveAll(h => h.name.Equals("Cookie", StringComparison.OrdinalIgnoreCase));
|
||||||
|
headers.Add(new HeadersModel("Cookie", cookie));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(bool success, string cookie, CookieContainer container)> LoginByCredentials()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string host = EnsureTrailingSlash(_init.host);
|
||||||
|
var hostUri = new Uri(host);
|
||||||
|
var container = new CookieContainer();
|
||||||
|
|
||||||
|
var headers = new List<HeadersModel>
|
||||||
|
{
|
||||||
|
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||||
|
new HeadersModel("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
|
||||||
|
new HeadersModel("Referer", host),
|
||||||
|
new HeadersModel("Origin", _init.host),
|
||||||
|
new HeadersModel("Accept-Language", "uk-UA,uk;q=0.9")
|
||||||
|
};
|
||||||
|
|
||||||
|
var postParams = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["login_name"] = _init.login,
|
||||||
|
["login_password"] = _init.passwd,
|
||||||
|
["login"] = "submit",
|
||||||
|
["login_not_save"] = "1"
|
||||||
|
};
|
||||||
|
|
||||||
|
using var postData = new FormUrlEncodedContent(postParams);
|
||||||
|
var response = await Http.BasePost(host, postData,
|
||||||
|
timeoutSeconds: 20,
|
||||||
|
headers: headers,
|
||||||
|
proxy: _proxyManager?.Get(),
|
||||||
|
cookieContainer: container,
|
||||||
|
statusCodeOK: false);
|
||||||
|
|
||||||
|
if (response.response == null)
|
||||||
|
{
|
||||||
|
_onLog("UaflixAuth: логін не вдався, немає HTTP-відповіді");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
string body = response.content ?? string.Empty;
|
||||||
|
bool hasAuthError = body.Contains("Помилка авторизації", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| body.Contains("Вхід на сайт не був проведений", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
string cookie = BuildCookieHeader(container, hostUri) ?? string.Empty;
|
||||||
|
bool hasSession = cookie.Contains("PHPSESSID=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
bool hasDleAuthCookie = cookie.Contains("dle_newpm=", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| cookie.Contains("dle_user_id=", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| cookie.Contains("dle_password=", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| cookie.Contains("dle_hash=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (response.response.Headers.TryGetValues("Set-Cookie", out IEnumerable<string> setCookies))
|
||||||
|
{
|
||||||
|
foreach (string line in setCookies)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line) || IsDeletedCookie(line))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
TrySetCookie(container, hostUri, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie = BuildCookieHeader(container, hostUri) ?? string.Empty;
|
||||||
|
hasSession = cookie.Contains("PHPSESSID=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
hasDleAuthCookie = cookie.Contains("dle_newpm=", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| cookie.Contains("dle_user_id=", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| cookie.Contains("dle_password=", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| cookie.Contains("dle_hash=", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAuthError || !hasSession || !hasDleAuthCookie)
|
||||||
|
{
|
||||||
|
_onLog($"UaflixAuth: авторизація неуспішна, status={(int)response.response.StatusCode}");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLog("UaflixAuth: авторизація успішна");
|
||||||
|
return (true, cookie, container);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_onLog($"UaflixAuth: помилка авторизації - {ex.Message}");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildAuthKey()
|
||||||
|
{
|
||||||
|
string login = _init.login ?? string.Empty;
|
||||||
|
string manualCookie = _init.cookie ?? string.Empty;
|
||||||
|
return $"{_init.host}|{login}|{manualCookie}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheAuthState(string key, string cookie, CookieContainer container)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(cookie))
|
||||||
|
CookieHeaders[key] = cookie;
|
||||||
|
|
||||||
|
if (container != null)
|
||||||
|
CookieContainers[key] = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CookieContainer CreateContainerFromCookie(string cookie)
|
||||||
|
{
|
||||||
|
var container = new CookieContainer();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cookie))
|
||||||
|
return container;
|
||||||
|
|
||||||
|
Uri hostUri = new Uri(EnsureTrailingSlash(_init.host));
|
||||||
|
foreach (string part in cookie.Split(';'))
|
||||||
|
{
|
||||||
|
string row = part.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(row) || !row.Contains('='))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string name = row[..row.IndexOf('=')].Trim();
|
||||||
|
string value = row[(row.IndexOf('=') + 1)..].Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
TryAddCookie(container, hostUri.Host, name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TrySetCookie(CookieContainer container, Uri uri, string setCookieLine)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
container.SetCookies(uri, setCookieLine);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
string raw = setCookieLine.Split(';')[0].Trim();
|
||||||
|
int eq = raw.IndexOf('=');
|
||||||
|
if (eq <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string name = raw[..eq].Trim();
|
||||||
|
string value = raw[(eq + 1)..].Trim();
|
||||||
|
TryAddCookie(container, uri.Host, name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryAddCookie(CookieContainer container, string host, string name, string value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cookie = new Cookie(name, value, "/", host)
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
Expires = name.Equals("PHPSESSID", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? default
|
||||||
|
: DateTime.UtcNow.AddMonths(6)
|
||||||
|
};
|
||||||
|
|
||||||
|
container.Add(cookie);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildCookieHeader(CookieContainer container, Uri hostUri)
|
||||||
|
{
|
||||||
|
if (container == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var cookies = container.GetCookies(hostUri)
|
||||||
|
.Cast<Cookie>()
|
||||||
|
.Where(c => !string.IsNullOrWhiteSpace(c.Name) && !string.IsNullOrWhiteSpace(c.Value))
|
||||||
|
.Select(c => $"{c.Name}={c.Value}")
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return cookies.Count == 0 ? null : string.Join("; ", cookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDeletedCookie(string line)
|
||||||
|
{
|
||||||
|
return line.Contains("=deleted;", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| line.Contains("Max-Age=0", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| line.Contains("expires=Thu, 01-Jan-1970", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeCookie(string cookie)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(cookie))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var pairs = new List<string>();
|
||||||
|
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (string part in cookie.Split(';'))
|
||||||
|
{
|
||||||
|
string row = part.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(row) || !row.Contains('='))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int eq = row.IndexOf('=');
|
||||||
|
if (eq <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string name = row[..eq].Trim();
|
||||||
|
string value = row[(eq + 1)..].Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
map[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kv in map)
|
||||||
|
pairs.Add($"{kv.Key}={kv.Value}");
|
||||||
|
|
||||||
|
return pairs.Count == 0 ? null : string.Join("; ", pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EnsureTrailingSlash(string url)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
return url;
|
||||||
|
|
||||||
|
return url.EndsWith('/') ? url : $"{url}/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,17 +23,19 @@ namespace Uaflix
|
|||||||
private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase);
|
private static readonly Regex Quality4kRegex = new Regex(@"(^|[^0-9])(2160p?)([^0-9]|$)|\b4k\b|\buhd\b", RegexOptions.IgnoreCase);
|
||||||
private static readonly Regex QualityFhdRegex = new Regex(@"(^|[^0-9])(1080p?)([^0-9]|$)|\bfhd\b", RegexOptions.IgnoreCase);
|
private static readonly Regex QualityFhdRegex = new Regex(@"(^|[^0-9])(1080p?)([^0-9]|$)|\bfhd\b", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private OnlinesSettings _init;
|
private readonly UaflixSettings _init;
|
||||||
private IHybridCache _hybridCache;
|
private readonly IHybridCache _hybridCache;
|
||||||
private Action<string> _onLog;
|
private readonly Action<string> _onLog;
|
||||||
private ProxyManager _proxyManager;
|
private readonly ProxyManager _proxyManager;
|
||||||
|
private readonly UaflixAuth _auth;
|
||||||
|
|
||||||
public UaflixInvoke(OnlinesSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager)
|
public UaflixInvoke(UaflixSettings init, IHybridCache hybridCache, Action<string> onLog, ProxyManager proxyManager, UaflixAuth auth)
|
||||||
{
|
{
|
||||||
_init = init;
|
_init = init;
|
||||||
_hybridCache = hybridCache;
|
_hybridCache = hybridCache;
|
||||||
_onLog = onLog;
|
_onLog = onLog;
|
||||||
_proxyManager = proxyManager;
|
_proxyManager = proxyManager;
|
||||||
|
_auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
string AshdiRequestUrl(string url)
|
string AshdiRequestUrl(string url)
|
||||||
@ -47,6 +49,97 @@ namespace Uaflix
|
|||||||
return ApnHelper.WrapUrl(_init, url);
|
return ApnHelper.WrapUrl(_init, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CheckSearchAvailability(string title, string originalTitle)
|
||||||
|
{
|
||||||
|
string filmTitle = !string.IsNullOrWhiteSpace(title) ? title : originalTitle;
|
||||||
|
if (string.IsNullOrWhiteSpace(filmTitle))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(filmTitle)}";
|
||||||
|
var headers = new List<HeadersModel>()
|
||||||
|
{
|
||||||
|
new HeadersModel("User-Agent", "Mozilla/5.0"),
|
||||||
|
new HeadersModel("Referer", _init.host)
|
||||||
|
};
|
||||||
|
|
||||||
|
string searchHtml = await GetHtml(searchUrl, headers, timeoutSeconds: 10);
|
||||||
|
if (string.IsNullOrWhiteSpace(searchHtml))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return searchHtml.Contains("sres-wrap")
|
||||||
|
|| searchHtml.Contains("sres-item")
|
||||||
|
|| searchHtml.Contains("search-results");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<string> GetHtml(string url, List<HeadersModel> headers, int timeoutSeconds = 15, bool retryOnUnauthorized = true)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string requestUrl = _init.cors(url);
|
||||||
|
bool withAuth = ShouldUseAuth(url);
|
||||||
|
var requestHeaders = headers != null ? new List<HeadersModel>(headers) : new List<HeadersModel>();
|
||||||
|
|
||||||
|
if (withAuth && _auth != null)
|
||||||
|
{
|
||||||
|
string cookie = await _auth.GetCookieHeaderAsync();
|
||||||
|
_auth.ApplyCookieHeader(requestHeaders, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await Http.BaseGet(requestUrl,
|
||||||
|
headers: requestHeaders,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
proxy: _proxyManager.Get(),
|
||||||
|
statusCodeOK: false);
|
||||||
|
|
||||||
|
if (response.response?.StatusCode == HttpStatusCode.Forbidden
|
||||||
|
&& retryOnUnauthorized
|
||||||
|
&& withAuth
|
||||||
|
&& _auth != null
|
||||||
|
&& _auth.CanUseCredentials)
|
||||||
|
{
|
||||||
|
_onLog($"UaflixAuth: отримано 403 для {url}, виконую повторну авторизацію");
|
||||||
|
string refreshedCookie = await _auth.GetCookieHeaderAsync(forceRefresh: true);
|
||||||
|
_auth.ApplyCookieHeader(requestHeaders, refreshedCookie);
|
||||||
|
|
||||||
|
response = await Http.BaseGet(requestUrl,
|
||||||
|
headers: requestHeaders,
|
||||||
|
timeoutSeconds: timeoutSeconds,
|
||||||
|
proxy: _proxyManager.Get(),
|
||||||
|
statusCodeOK: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.response?.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
if (response.response != null)
|
||||||
|
_onLog($"Uaflix HTTP {(int)response.response.StatusCode} для {url}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ShouldUseAuth(string url)
|
||||||
|
{
|
||||||
|
if (_auth == null || string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(_init?.host))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Uri siteUri = new Uri(_init.host);
|
||||||
|
Uri requestUri;
|
||||||
|
if (!Uri.TryCreate(url, UriKind.Absolute, out requestUri))
|
||||||
|
requestUri = new Uri(siteUri, url.TrimStart('/'));
|
||||||
|
|
||||||
|
return string.Equals(requestUri.Host, siteUri.Host, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Методи для визначення та парсингу різних типів плеєрів
|
#region Методи для визначення та парсингу різних типів плеєрів
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -144,7 +237,7 @@ namespace Uaflix
|
|||||||
new HeadersModel("Referer", _init.host)
|
new HeadersModel("Referer", _init.host)
|
||||||
};
|
};
|
||||||
|
|
||||||
string html = await Http.Get(_init.cors(pageUrl), headers: headers, proxy: _proxyManager.Get());
|
string html = await GetHtml(pageUrl, headers);
|
||||||
if (string.IsNullOrWhiteSpace(html))
|
if (string.IsNullOrWhiteSpace(html))
|
||||||
return (null, null);
|
return (null, null);
|
||||||
|
|
||||||
@ -294,7 +387,7 @@ namespace Uaflix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string html = await Http.Get(_init.cors(AshdiRequestUrl(requestUrl)), headers: headers, proxy: _proxyManager.Get());
|
string html = await GetHtml(AshdiRequestUrl(requestUrl), headers);
|
||||||
|
|
||||||
// Знайти JSON у new Playerjs({file:'...'})
|
// Знайти JSON у new Playerjs({file:'...'})
|
||||||
var match = Regex.Match(html, @"file:'(\[.+?\])'", RegexOptions.Singleline);
|
var match = Regex.Match(html, @"file:'(\[.+?\])'", RegexOptions.Singleline);
|
||||||
@ -405,7 +498,7 @@ namespace Uaflix
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string html = await Http.Get(_init.cors(iframeUrl), headers: headers, proxy: _proxyManager.Get());
|
string html = await GetHtml(iframeUrl, headers);
|
||||||
|
|
||||||
// Знайти file:"url"
|
// Знайти file:"url"
|
||||||
var match = Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
|
var match = Regex.Match(html, @"file:\s*""([^""]+\.m3u8)""");
|
||||||
@ -441,7 +534,7 @@ namespace Uaflix
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
string requestUrl = WithAshdiMultivoice(iframeUrl);
|
string requestUrl = WithAshdiMultivoice(iframeUrl);
|
||||||
string html = await Http.Get(_init.cors(AshdiRequestUrl(requestUrl)), headers: headers, proxy: _proxyManager.Get());
|
string html = await GetHtml(AshdiRequestUrl(requestUrl), headers);
|
||||||
if (string.IsNullOrEmpty(html))
|
if (string.IsNullOrEmpty(html))
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@ -740,7 +833,7 @@ namespace Uaflix
|
|||||||
foreach (string query in queries)
|
foreach (string query in queries)
|
||||||
{
|
{
|
||||||
string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(query)}";
|
string searchUrl = $"{_init.host}/index.php?do=search&subaction=search&story={System.Web.HttpUtility.UrlEncode(query)}";
|
||||||
string searchHtml = await Http.Get(_init.cors(searchUrl), headers: headers, proxy: _proxyManager.Get());
|
string searchHtml = await GetHtml(searchUrl, headers);
|
||||||
if (string.IsNullOrWhiteSpace(searchHtml))
|
if (string.IsNullOrWhiteSpace(searchHtml))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -926,7 +1019,7 @@ namespace Uaflix
|
|||||||
new HeadersModel("Referer", _init.host)
|
new HeadersModel("Referer", _init.host)
|
||||||
};
|
};
|
||||||
|
|
||||||
string html = await Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
|
string html = await GetHtml(url, headers);
|
||||||
if (!string.IsNullOrWhiteSpace(html))
|
if (!string.IsNullOrWhiteSpace(html))
|
||||||
{
|
{
|
||||||
var doc = new HtmlDocument();
|
var doc = new HtmlDocument();
|
||||||
@ -1152,7 +1245,7 @@ namespace Uaflix
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
||||||
var filmHtml = await Http.Get(_init.cors(filmUrl), headers: headers, proxy: _proxyManager.Get());
|
var filmHtml = await GetHtml(filmUrl, headers);
|
||||||
var doc = new HtmlDocument();
|
var doc = new HtmlDocument();
|
||||||
doc.LoadHtml(filmHtml);
|
doc.LoadHtml(filmHtml);
|
||||||
|
|
||||||
@ -1227,7 +1320,7 @@ namespace Uaflix
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
var headers = new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) };
|
||||||
var filmHtml = await Http.Get(_init.cors(filmUrl), headers: headers, proxy: _proxyManager.Get());
|
var filmHtml = await GetHtml(filmUrl, headers);
|
||||||
var filmDoc = new HtmlDocument();
|
var filmDoc = new HtmlDocument();
|
||||||
filmDoc.LoadHtml(filmHtml);
|
filmDoc.LoadHtml(filmHtml);
|
||||||
|
|
||||||
@ -1265,7 +1358,7 @@ namespace Uaflix
|
|||||||
if (safeSeasonUrls.Count == 0)
|
if (safeSeasonUrls.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var seasonTasks = safeSeasonUrls.Select(url => Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get()));
|
var seasonTasks = safeSeasonUrls.Select(url => GetHtml(url, headers));
|
||||||
var seasonPagesHtml = await Task.WhenAll(seasonTasks);
|
var seasonPagesHtml = await Task.WhenAll(seasonTasks);
|
||||||
|
|
||||||
foreach (var html in seasonPagesHtml)
|
foreach (var html in seasonPagesHtml)
|
||||||
@ -1326,7 +1419,7 @@ namespace Uaflix
|
|||||||
var result = new Uaflix.Models.PlayResult() { streams = new List<PlayStream>() };
|
var result = new Uaflix.Models.PlayResult() { streams = new List<PlayStream>() };
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string html = await Http.Get(_init.cors(url), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) }, proxy: _proxyManager.Get());
|
string html = await GetHtml(url, new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", _init.host) });
|
||||||
var doc = new HtmlDocument();
|
var doc = new HtmlDocument();
|
||||||
doc.LoadHtml(html);
|
doc.LoadHtml(html);
|
||||||
|
|
||||||
@ -1429,7 +1522,7 @@ namespace Uaflix
|
|||||||
async Task<List<PlayStream>> ParseAllZetvideoSources(string iframeUrl)
|
async Task<List<PlayStream>> ParseAllZetvideoSources(string iframeUrl)
|
||||||
{
|
{
|
||||||
var result = new List<PlayStream>();
|
var result = new List<PlayStream>();
|
||||||
var html = await Http.Get(_init.cors(iframeUrl), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://zetvideo.net/") }, proxy: _proxyManager.Get());
|
var html = await GetHtml(iframeUrl, new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://zetvideo.net/") });
|
||||||
if (string.IsNullOrEmpty(html)) return result;
|
if (string.IsNullOrEmpty(html)) return result;
|
||||||
|
|
||||||
var doc = new HtmlDocument();
|
var doc = new HtmlDocument();
|
||||||
@ -1473,7 +1566,7 @@ namespace Uaflix
|
|||||||
async Task<List<PlayStream>> ParseAllAshdiSources(string iframeUrl)
|
async Task<List<PlayStream>> ParseAllAshdiSources(string iframeUrl)
|
||||||
{
|
{
|
||||||
var result = new List<PlayStream>();
|
var result = new List<PlayStream>();
|
||||||
var html = await Http.Get(_init.cors(AshdiRequestUrl(iframeUrl)), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") }, proxy: _proxyManager.Get());
|
var html = await GetHtml(AshdiRequestUrl(iframeUrl), new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") });
|
||||||
if (string.IsNullOrEmpty(html)) return result;
|
if (string.IsNullOrEmpty(html)) return result;
|
||||||
|
|
||||||
var doc = new HtmlDocument();
|
var doc = new HtmlDocument();
|
||||||
@ -1500,7 +1593,7 @@ namespace Uaflix
|
|||||||
async Task<SubtitleTpl?> GetAshdiSubtitles(string id)
|
async Task<SubtitleTpl?> GetAshdiSubtitles(string id)
|
||||||
{
|
{
|
||||||
string url = $"https://ashdi.vip/vod/{id}";
|
string url = $"https://ashdi.vip/vod/{id}";
|
||||||
var html = await Http.Get(_init.cors(AshdiRequestUrl(url)), headers: new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") }, proxy: _proxyManager.Get());
|
var html = await GetHtml(AshdiRequestUrl(url), new List<HeadersModel>() { new HeadersModel("User-Agent", "Mozilla/5.0"), new HeadersModel("Referer", "https://ashdi.vip/") });
|
||||||
string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value;
|
string subtitle = new Regex("subtitle(\")?:\"([^\"]+)\"").Match(html).Groups[2].Value;
|
||||||
if (!string.IsNullOrEmpty(subtitle))
|
if (!string.IsNullOrEmpty(subtitle))
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user