Add Windows desktop version

This commit is contained in:
renato97
2025-12-17 19:20:55 +00:00
parent 93dbe0941e
commit 8921d7f2a6
36 changed files with 2760 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace StreamPlayer.Desktop.Services;
/// <summary>
/// Replica el resolvedor ofuscado que utiliza la app Android para reconstruir la URL real del stream.
/// </summary>
public sealed class StreamUrlResolver
{
private static readonly Regex ArrayNameRegex =
new(@"var\s+playbackURL\s*=\s*""""\s*,\s*([A-Za-z0-9]+)\s*=\s*\[\]", RegexOptions.Compiled);
private static readonly Regex EntryRegex =
new(@"\[(\d+),""([A-Za-z0-9+/=]+)""\]", RegexOptions.Compiled);
private static readonly Regex KeyFunctionsRegex =
new(@"var\s+k=(\w+)\(\)\+(\w+)\(\);", RegexOptions.Compiled);
private const string FunctionTemplate = @"function\s+{0}\(\)\s*\{{\s*return\s+(\d+);\s*\}}";
private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) StreamPlayerResolver/1.0";
private static readonly HttpClient HttpClient = CreateHttpClient();
public async Task<string> ResolveAsync(string pageUrl, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(pageUrl))
{
throw new ArgumentException("URL inválida", nameof(pageUrl));
}
string html = await DownloadPageAsync(pageUrl, cancellationToken).ConfigureAwait(false);
long keyOffset = ExtractKeyOffset(html);
var entries = ExtractEntries(html);
if (entries.Count == 0)
{
throw new InvalidOperationException("No se pudieron obtener los fragmentos del stream.");
}
var builder = new StringBuilder();
foreach (var entry in entries)
{
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(entry.Encoded));
string numeric = new string(decoded.Where(char.IsDigit).ToArray());
if (string.IsNullOrEmpty(numeric))
{
continue;
}
if (!long.TryParse(numeric, out long value))
{
continue;
}
builder.Append((char)(value - keyOffset));
}
string url = builder.ToString();
if (string.IsNullOrWhiteSpace(url))
{
throw new InvalidOperationException("No se pudo reconstruir la URL de reproducción.");
}
return url.Trim();
}
private static async Task<string> DownloadPageAsync(string url, CancellationToken cancellationToken)
{
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.UserAgent.ParseAdd(UserAgent);
request.Headers.Referrer = new Uri("https://streamtpmedia.com/");
using var response = await HttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
}
private static long ExtractKeyOffset(string html)
{
var match = KeyFunctionsRegex.Match(html);
if (!match.Success)
{
throw new InvalidOperationException("No se encontró la clave para el stream.");
}
string first = match.Groups[1].Value;
string second = match.Groups[2].Value;
long firstVal = ExtractReturnValue(html, first);
long secondVal = ExtractReturnValue(html, second);
return firstVal + secondVal;
}
private static long ExtractReturnValue(string html, string functionName)
{
var pattern = string.Format(CultureInfo.InvariantCulture, FunctionTemplate, Regex.Escape(functionName));
var regex = new Regex(pattern);
var match = regex.Match(html);
if (!match.Success)
{
throw new InvalidOperationException($"No se encontró el valor de la función {functionName}.");
}
return long.Parse(match.Groups[1].Value);
}
private static List<Entry> ExtractEntries(string html)
{
var matcher = ArrayNameRegex.Match(html);
if (!matcher.Success)
{
throw new InvalidOperationException("No se detectó la variable de fragmentos.");
}
string arrayName = matcher.Groups[1].Value;
var arrayRegex = new Regex($"{Regex.Escape(arrayName)}=\\[(.*?)\\];", RegexOptions.Singleline);
var arrayMatch = arrayRegex.Match(html);
if (!arrayMatch.Success)
{
throw new InvalidOperationException("No se encontró el arreglo de fragmentos.");
}
string rawEntries = arrayMatch.Groups[1].Value;
var entries = new List<Entry>();
foreach (Match match in EntryRegex.Matches(rawEntries))
{
if (int.TryParse(match.Groups[1].Value, out int index))
{
entries.Add(new Entry(index, match.Groups[2].Value));
}
}
return entries.OrderBy(e => e.Index).ToList();
}
private static HttpClient CreateHttpClient()
{
var handler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.All,
AllowAutoRedirect = true
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(20)
};
}
private sealed record Entry(int Index, string Encoded);
}