lampac/Shared/Engine/SISI/EpornerTo.cs
lampac-talks f843f04fd4 chore: initial commit 154.3
Signed-off-by: lampac-talks <lampac-talks@users.noreply.github.com>
2026-01-30 16:23:09 +03:00

682 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Shared.Engine.RxEnumerate;
using Shared.Models.SISI.Base;
using Shared.Models.SISI.OnResult;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
namespace Shared.Engine.SISI
{
public static class EpornerTo
{
static readonly ThreadLocal<StringBuilder> sbUri = new(() => new StringBuilder(PoolInvk.rentChunk));
static readonly ThreadLocal<StringBuilder> sbHash = new(() => new StringBuilder(64));
#region Uri
public static string Uri(string host, string search, string sort, string c, int pg)
{
var url = sbUri.Value;
url.Clear();
url.Append(host);
url.Append("/");
if (!string.IsNullOrWhiteSpace(search))
{
url.Append($"search/{HttpUtility.UrlEncode(search)}/");
if (pg > 1)
url.Append($"{pg}/");
if (!string.IsNullOrEmpty(sort))
url.Append($"{sort}/");
}
else
{
if (!string.IsNullOrEmpty(c))
{
url.Append($"cat/{c}/");
if (pg > 1)
url.Append($"{pg}/");
}
else
{
if (pg > 1)
url.Append($"{pg}/");
if (!string.IsNullOrEmpty(sort))
url.Append($"{sort}/");
}
}
return url.ToString();
}
#endregion
#region Playlist
public static List<PlaylistItem> Playlist(string route, ReadOnlySpan<char> html, Func<PlaylistItem, PlaylistItem> onplaylist = null)
{
if (html.IsEmpty)
return null;
var single = ReadOnlySpan<char>.Empty;
if (html.Contains("id=\"relateddiv\"", StringComparison.Ordinal))
single = HtmlSpan.Node(html, "*", "id", "relateddiv", HtmlSpanTargetType.Exact);
else if (html.Contains("id=\"vidresults\"", StringComparison.Ordinal))
single = HtmlSpan.Node(html, "*", "id", "vidresults", HtmlSpanTargetType.Exact);
else if (html.Contains("class=\"toptopbelinset\"", StringComparison.Ordinal))
single = Rx.Split("class=\"toptopbelinset\"", html)[1].Span;
else if (html.Contains("class=\"relatedtext\"", StringComparison.Ordinal))
single = Rx.Split("class=\"relatedtext\"", html)[1].Span;
if (single.IsEmpty)
return null;
var rx = Rx.Split("<div class=\"mb( hdy)?\"", single, 1);
if (rx.Count == 0)
return null;
var playlists = new List<PlaylistItem>(rx.Count);
foreach (var row in rx.Rows())
{
var g = row.Groups("<p class=\"mbtit\"><a href=\"/([^\"]+)\">([^<]+)</a>");
if (!string.IsNullOrWhiteSpace(g[1].Value) && !string.IsNullOrWhiteSpace(g[2].Value))
{
string img = row.Match(" data-src=\"([^\"]+)\"");
if (img == null)
img = row.Match("<img src=\"([^\"]+)\"");
if (img == null)
img = string.Empty;
var pl = new PlaylistItem()
{
name = g[2].Value,
video = $"{route}?uri={g[1].Value}",
picture = img,
preview = Regex.Replace(img, "/[^/]+$", "") + $"/{row.Match("data-id=\"([^\"]+)\"")}-preview.webm",
quality = row.Match("<div class=\"mvhdico\"([^>]+)?><span>([^\"<]+)", 2),
time = row.Match("<span class=\"mbtim\"([^>]+)?>([^<]+)</span>", 2, trim: true),
json = true,
related = true,
bookmark = new Bookmark()
{
site = "epr",
href = g[1].Value,
image = img
}
};
if (onplaylist != null)
pl = onplaylist.Invoke(pl);
playlists.Add(pl);
}
}
return playlists;
}
#endregion
#region Menu
public static List<MenuItem> Menu(string host, string search, string sort, string c)
{
host = string.IsNullOrWhiteSpace(host) ? string.Empty : $"{host}/";
string url = host + "epr";
var menu = new List<MenuItem>()
{
new MenuItem()
{
title = "Поиск",
search_on = "search_on",
playlist_url = url,
}
};
if (!string.IsNullOrEmpty(search))
{
string encodesearch = HttpUtility.UrlEncode(search);
menu.Add(new MenuItem()
{
title = $"Сортировка: {(string.IsNullOrWhiteSpace(sort) ? "новинки" : sort)}",
playlist_url = "submenu",
submenu = new List<MenuItem>()
{
new MenuItem()
{
title = "Новинки",
playlist_url = url + $"?search={encodesearch}"
},
new MenuItem()
{
title = "Топ просмотра",
playlist_url = url + $"?sort=most-viewed&search={encodesearch}"
},
new MenuItem()
{
title = "Топ рейтинга",
playlist_url = url + $"?sort=top-rated&search={encodesearch}"
},
new MenuItem()
{
title = "Длинные ролики",
playlist_url = url + $"?sort=longest&search={encodesearch}"
},
new MenuItem()
{
title = "Короткие ролики",
playlist_url = url + $"?sort=shortest&search={encodesearch}"
}
}
});
return menu;
}
if (string.IsNullOrEmpty(c))
{
menu.Add(new MenuItem()
{
title = $"Сортировка: {(string.IsNullOrWhiteSpace(sort) ? "новинки" : sort)}",
playlist_url = "submenu",
submenu = new List<MenuItem>()
{
new MenuItem()
{
title = "Новинки",
playlist_url = url
},
new MenuItem()
{
title = "Топ просмотра",
playlist_url = url + "?sort=most-viewed"
},
new MenuItem()
{
title = "Топ рейтинга",
playlist_url = url + "?sort=top-rated"
},
new MenuItem()
{
title = "Длинные ролики",
playlist_url = url + "?sort=longest"
},
new MenuItem()
{
title = "Короткие ролики",
playlist_url = url + "?sort=shortest"
}
}
});
}
{
var submenu = new List<MenuItem>()
{
new MenuItem()
{
title = "Все",
playlist_url = url
},
new MenuItem()
{
title = "4K UHD",
playlist_url = url + $"?c=4k-porn"
},
new MenuItem()
{
title = "60 FPS",
playlist_url = url + $"?c=60fps"
},
new MenuItem()
{
title = "Amateur",
playlist_url = url + $"?c=amateur"
},
new MenuItem()
{
title = "Anal",
playlist_url = url + $"?c=anal"
},
new MenuItem()
{
title = "Asian",
playlist_url = url + $"?c=asian"
},
new MenuItem()
{
title = "ASMR",
playlist_url = url + $"?c=asmr"
},
new MenuItem()
{
title = "BBW",
playlist_url = url + $"?c=bbw"
},
new MenuItem()
{
title = "BDSM",
playlist_url = url + $"?c=bdsm"
},
new MenuItem()
{
title = "Big Ass",
playlist_url = url + $"?c=big-ass"
},
new MenuItem()
{
title = "Big Dick",
playlist_url = url + $"?c=big-dick"
},
new MenuItem()
{
title = "Big Tits",
playlist_url = url + $"?c=big-tits"
},
new MenuItem()
{
title = "Bisexual",
playlist_url = url + $"?c=bisexual"
},
new MenuItem()
{
title = "Blonde",
playlist_url = url + $"?c=blonde"
},
new MenuItem()
{
title = "Blowjob",
playlist_url = url + $"?c=blowjob"
},
new MenuItem()
{
title = "Bondage",
playlist_url = url + $"?c=bondage"
},
new MenuItem()
{
title = "Brunette",
playlist_url = url + $"?c=brunette"
},
new MenuItem()
{
title = "Bukkake",
playlist_url = url + $"?c=bukkake"
},
new MenuItem()
{
title = "Creampie",
playlist_url = url + $"?c=creampie"
},
new MenuItem()
{
title = "Cumshot",
playlist_url = url + $"?c=cumshot"
},
new MenuItem()
{
title = "Double Penetration",
playlist_url = url + $"?c=double-penetration"
},
new MenuItem()
{
title = "Ebony",
playlist_url = url + $"?c=ebony"
},
new MenuItem()
{
title = "Fat",
playlist_url = url + $"?c=fat"
},
new MenuItem()
{
title = "Fetish",
playlist_url = url + $"?c=fetish"
},
new MenuItem()
{
title = "Fisting",
playlist_url = url + $"?c=fisting"
},
new MenuItem()
{
title = "Footjob",
playlist_url = url + $"?c=footjob"
},
new MenuItem()
{
title = "For Women",
playlist_url = url + $"?c=for-women"
},
new MenuItem()
{
title = "Gay",
playlist_url = url + $"?c=gay"
},
new MenuItem()
{
title = "Group Sex",
playlist_url = url + $"?c=group-sex"
},
new MenuItem()
{
title = "Handjob",
playlist_url = url + $"?c=handjob"
},
new MenuItem()
{
title = "Hardcore",
playlist_url = url + $"?c=hardcore"
},
new MenuItem()
{
title = "Hentai",
playlist_url = url + $"?c=hentai"
},
new MenuItem()
{
title = "Homemade",
playlist_url = url + $"?c=homemade"
},
new MenuItem()
{
title = "Hotel",
playlist_url = url + $"?c=hotel"
},
new MenuItem()
{
title = "Housewives",
playlist_url = url + $"?c=housewives"
},
new MenuItem()
{
title = "Indian",
playlist_url = url + $"?c=indian"
},
new MenuItem()
{
title = "Interracial",
playlist_url = url + $"?c=interracial"
},
new MenuItem()
{
title = "Japanese",
playlist_url = url + $"?c=japanese"
},
new MenuItem()
{
title = "Latina",
playlist_url = url + $"?c=latina"
},
new MenuItem()
{
title = "Lesbian",
playlist_url = url + $"?c=lesbians"
},
new MenuItem()
{
title = "Lingerie",
playlist_url = url + $"?c=lingerie"
},
new MenuItem()
{
title = "Massage",
playlist_url = url + $"?c=massage"
},
new MenuItem()
{
title = "Masturbation",
playlist_url = url + $"?c=masturbation"
},
new MenuItem()
{
title = "Mature",
playlist_url = url + $"?c=mature"
},
new MenuItem()
{
title = "MILF",
playlist_url = url + $"?c=milf"
},
new MenuItem()
{
title = "Nurses",
playlist_url = url + $"?c=nurse"
},
new MenuItem()
{
title = "Office",
playlist_url = url + $"?c=office"
},
new MenuItem()
{
title = "Older Men",
playlist_url = url + $"?c=old-man"
},
new MenuItem()
{
title = "Orgy",
playlist_url = url + $"?c=orgy"
},
new MenuItem()
{
title = "Outdoor",
playlist_url = url + $"?c=outdoor"
},
new MenuItem()
{
title = "Petite",
playlist_url = url + $"?c=petite"
},
new MenuItem()
{
title = "Pornstar",
playlist_url = url + $"?c=pornstar"
},
new MenuItem()
{
title = "POV",
playlist_url = url + $"?c=pov-porn"
},
new MenuItem()
{
title = "Public",
playlist_url = url + $"?c=public"
},
new MenuItem()
{
title = "Redhead",
playlist_url = url + $"?c=redhead"
},
new MenuItem()
{
title = "Shemale",
playlist_url = url + $"?c=shemale"
},
new MenuItem()
{
title = "Sleep",
playlist_url = url + $"?c=sleep"
},
new MenuItem()
{
title = "Small Tits",
playlist_url = url + $"?c=small-tits"
},
new MenuItem()
{
title = "Squirt",
playlist_url = url + $"?c=squirt"
},
new MenuItem()
{
title = "Striptease",
playlist_url = url + $"?c=striptease"
},
new MenuItem()
{
title = "Students",
playlist_url = url + $"?c=students"
},
new MenuItem()
{
title = "Swinger",
playlist_url = url + $"?c=swingers"
},
new MenuItem()
{
title = "Teen",
playlist_url = url + $"?c=teens"
},
new MenuItem()
{
title = "Threesome",
playlist_url = url + $"?c=threesome"
},
new MenuItem()
{
title = "Toys",
playlist_url = url + $"?c=toys"
},
new MenuItem()
{
title = "Uncategorized",
playlist_url = url + $"?c=uncategorized"
},
new MenuItem()
{
title = "Uniform",
playlist_url = url + $"?c=uniform"
},
new MenuItem()
{
title = "Vintage",
playlist_url = url + $"?c=vintage"
},
new MenuItem()
{
title = "Webcam",
playlist_url = url + $"?c=webcam"
}
};
menu.Add(new MenuItem()
{
title = $"Категория: {submenu.FirstOrDefault(i => i.playlist_url!.EndsWith($"c={c}")).title ?? "все"}",
playlist_url = "submenu",
submenu = submenu
});
}
return menu;
}
#endregion
#region StreamLinks
async public static Task<StreamItem> StreamLinks(HttpHydra http, string route, string host, string url, Func<string, string> onlog = null)
{
if (string.IsNullOrEmpty(url))
return null;
string vid = null, hash = null;
List<PlaylistItem> recomends = null;
await http.GetSpan($"{host}/{url}", html =>
{
vid = Rx.Match(html, "vid ?= ?'([^']+)'");
hash = Rx.Match(html, "hash ?= ?'([^']+)'");
if (vid != null && hash != null)
recomends = Playlist(route, html);
});
if (vid == null || hash == null)
return null;
var stream_links = new Dictionary<string, string>(5);
await http.GetSpan($"{host}/xhr/video/{vid}?hash={convertHash(hash)}&domain={Regex.Replace(host, "^https?://", "")}&fallback=false&embed=false&supportedFormats=dash,mp4&_={DateTimeOffset.UtcNow.ToUnixTimeSeconds()}", json =>
{
foreach (var row in Rx.Matches("\"src\":( +)?\"(https?://[^/]+/[^\"]+-([0-9]+p).mp4)\",", json).Rows())
{
var g = row.Groups("\"src\":( +)?\"(https?://[^/]+/[^\"]+-([0-9]+p).mp4)\",");
if (!string.IsNullOrEmpty(g[3].Value))
stream_links.TryAdd(g[3].Value, g[2].Value);
}
});
onlog?.Invoke("stream_links: " + stream_links.Count);
return new StreamItem()
{
qualitys = stream_links,
recomends = recomends
};
}
#endregion
#region convertHash
static string convertHash(string h)
{
StringBuilder builder = sbHash.Value;
builder.Clear();
Base36(h.AsSpan(0, 8), builder);
Base36(h.AsSpan(8, 8), builder);
Base36(h.AsSpan(16, 8), builder);
Base36(h.AsSpan(24, 8), builder);
return builder.ToString();
}
#endregion
#region Base36
static void Base36(ReadOnlySpan<char> hex, StringBuilder builder)
{
// Парсинг 8 hex-символов без Substring и без аллокаций
ulong value = ulong.Parse(hex, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
const int Base = 36;
const string Chars = "0123456789abcdefghijklmnopqrstuvwxyz";
int start = builder.Length;
if (value == 0)
{
builder.Append('0');
return;
}
while (value > 0)
{
builder.Append(Chars[(int)(value % Base)]);
value /= Base;
}
// Разворот добавленного участка (т.к. цифры добавлялись в обратном порядке)
int i = start;
int j = builder.Length - 1;
while (i < j)
{
(builder[i], builder[j]) = (builder[j], builder[i]);
i++;
j--;
}
}
#endregion
}
}