269 lines
10 KiB
C#
269 lines
10 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Newtonsoft.Json.Linq;
|
|
using Shared.Models.Online.Kodik;
|
|
using Shared.Models.Online.Settings;
|
|
using System.Buffers;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace Online.Controllers
|
|
{
|
|
public class Kodik : BaseOnlineController<KodikSettings>
|
|
{
|
|
#region database
|
|
static List<Result> databaseCache;
|
|
|
|
static IEnumerable<Result> database
|
|
{
|
|
get
|
|
{
|
|
if (AppInit.conf.multiaccess)
|
|
return databaseCache ??= JsonHelper.ListReader<Result>("data/kodik.json", 100_000);
|
|
|
|
return JsonHelper.IEnumerableReader<Result>("data/kodik.json");
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region HMAC
|
|
static readonly ConcurrentDictionary<string, byte[]> keyBytes = new ConcurrentDictionary<string, byte[]>();
|
|
|
|
static string HMAC(string key, string message)
|
|
{
|
|
if (!keyBytes.TryGetValue(key, out byte[] arraykey))
|
|
{
|
|
arraykey = Encoding.UTF8.GetBytes(key);
|
|
keyBytes.TryAdd(key, arraykey);
|
|
}
|
|
|
|
Span<byte> msgBytes = stackalloc byte[Encoding.UTF8.GetByteCount(message)];
|
|
Span<byte> hash = stackalloc byte[32];
|
|
Span<char> hex = stackalloc char[64];
|
|
|
|
Encoding.UTF8.GetBytes(message, msgBytes);
|
|
|
|
using (var hmac = new HMACSHA256(arraykey))
|
|
hmac.TryComputeHash(msgBytes, hash, out _);
|
|
|
|
const string hexChars = "0123456789abcdef";
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
byte b = hash[i];
|
|
hex[i * 2] = hexChars[b >> 4];
|
|
hex[i * 2 + 1] = hexChars[b & 0xF];
|
|
}
|
|
|
|
return new string(hex);
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region KodikInvoke
|
|
KodikInvoke oninvk;
|
|
|
|
public Kodik() : base(AppInit.conf.Kodik)
|
|
{
|
|
loadKitInitialization = (j, i, c) =>
|
|
{
|
|
if (j.ContainsKey("linkhost"))
|
|
i.linkhost = c.linkhost;
|
|
|
|
if (j.ContainsKey("secret_token"))
|
|
i.secret_token = c.secret_token;
|
|
|
|
if (j.ContainsKey("auto_proxy"))
|
|
i.auto_proxy = c.auto_proxy;
|
|
|
|
if (j.ContainsKey("cdn_is_working"))
|
|
i.cdn_is_working = c.cdn_is_working;
|
|
|
|
return i;
|
|
};
|
|
|
|
requestInitialization = () =>
|
|
{
|
|
oninvk = new KodikInvoke
|
|
(
|
|
host,
|
|
init,
|
|
"video",
|
|
database,
|
|
httpHydra,
|
|
streamfile => HostStreamProxy(streamfile),
|
|
requesterror: () => proxyManager?.Refresh()
|
|
);
|
|
};
|
|
}
|
|
#endregion
|
|
|
|
[HttpGet]
|
|
[Route("lite/kodik")]
|
|
async public Task<ActionResult> Index(string imdb_id, long kinopoisk_id, string title, string original_title, int clarification, string pick, string kid, int s = -1, bool rjson = false, bool similar = false)
|
|
{
|
|
if (await IsRequestBlocked(rch: true))
|
|
return badInitMsg;
|
|
|
|
List<Result> content = null;
|
|
|
|
if (similar || clarification == 1 || (kinopoisk_id == 0 && string.IsNullOrEmpty(imdb_id)))
|
|
{
|
|
EmbedModel res = null;
|
|
|
|
if (clarification == 1)
|
|
{
|
|
if (string.IsNullOrEmpty(title))
|
|
return OnError();
|
|
|
|
res = await InvokeCache($"kodik:search:{title}", 40, () => oninvk.Embed(title, null, clarification));
|
|
if (res?.result == null || res.result.Count == 0)
|
|
return OnError();
|
|
}
|
|
else
|
|
{
|
|
if (string.IsNullOrEmpty(pick) && string.IsNullOrEmpty(title ?? original_title))
|
|
return OnError();
|
|
|
|
res = await InvokeCache($"kodik:search2:{original_title}:{title}:{clarification}", 40, async () =>
|
|
{
|
|
var i = await oninvk.Embed(null, original_title, clarification);
|
|
if (i?.result == null || i.result.Count == 0)
|
|
return await oninvk.Embed(title, null, clarification);
|
|
|
|
return i;
|
|
|
|
});
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(pick))
|
|
return await ContentTpl(res?.stpl);
|
|
|
|
content = oninvk.Embed(res.result, pick);
|
|
}
|
|
else
|
|
{
|
|
content = await InvokeCache($"kodik:search:{kinopoisk_id}:{imdb_id}", 40, () => oninvk.Embed(imdb_id, kinopoisk_id, s));
|
|
if (content == null || content.Count == 0)
|
|
return LocalRedirect(accsArgs($"/lite/kodik?rjson={rjson}&title={HttpUtility.UrlEncode(title)}&original_title={HttpUtility.UrlEncode(original_title)}"));
|
|
}
|
|
|
|
return await ContentTpl(await oninvk.Tpl(content, accsArgs(string.Empty), imdb_id, kinopoisk_id, title, original_title, clarification, pick, kid, s, true, rjson));
|
|
}
|
|
|
|
#region Video
|
|
[HttpGet]
|
|
[Route("lite/kodik/video")]
|
|
[Route("lite/kodik/video.m3u8")]
|
|
async public ValueTask<ActionResult> VideoAPI(string title, string original_title, string link, int episode, bool play)
|
|
{
|
|
if (await IsRequestBlocked(rch: true, rch_check: !play))
|
|
return badInitMsg;
|
|
|
|
if (string.IsNullOrWhiteSpace(init.secret_token))
|
|
{
|
|
var streams = await InvokeCache($"kodik:video:{link}:{play}", 40, () => oninvk.VideoParse(init.linkhost, link));
|
|
if (streams == null)
|
|
return OnError();
|
|
|
|
string result = oninvk.VideoParse(streams, title, original_title, episode, play, vast: init.vast);
|
|
if (string.IsNullOrEmpty(result))
|
|
return OnError();
|
|
|
|
if (play)
|
|
return RedirectToPlay(result);
|
|
|
|
return ContentTo(result);
|
|
}
|
|
else
|
|
{
|
|
string userIp = requestInfo.IP;
|
|
if (init.localip)
|
|
{
|
|
userIp = await mylocalip();
|
|
if (userIp == null)
|
|
return OnError();
|
|
}
|
|
|
|
return await InvkSemaphore($"kodik:view:stream:{link}:{init.secret_token}:{requestInfo.IP}", async key =>
|
|
{
|
|
if (!hybridCache.TryGetValue(key, out (List<(string q, string url)> streams, SegmentTpl segments) cache))
|
|
{
|
|
string deadline = DateTime.Now.AddHours(4).ToString("yyyyMMddHH");
|
|
string hmac = HMAC(init.secret_token, $"{link}:{userIp}:{deadline}");
|
|
|
|
string uri = $"http://kodik.biz/api/video-links?link={link}&p={init.token}&ip={userIp}&d={deadline}&s={hmac}&auto_proxy={init.auto_proxy.ToString().ToLower()}&skip_segments=true";
|
|
|
|
var root = await httpHydra.Get<JObject>(uri, safety: true);
|
|
|
|
if (root == null || !root.ContainsKey("links"))
|
|
return OnError("links", refresh_proxy: true);
|
|
|
|
cache.streams = new List<(string q, string url)>(3);
|
|
|
|
foreach (var link in root["links"].ToObject<Dictionary<string, JObject>>())
|
|
{
|
|
string src = link.Value.Value<string>("Src");
|
|
|
|
if (src.StartsWith("http")) { }
|
|
else if(src.StartsWith("//"))
|
|
src = $"https:{src}";
|
|
else
|
|
src = $"https://{src}";
|
|
|
|
cache.streams.Add(($"{link.Key}p", src));
|
|
}
|
|
|
|
if (cache.streams.Count == 0)
|
|
return OnError("streams", refresh_proxy: true);
|
|
|
|
cache.streams.Reverse();
|
|
|
|
if (root.ContainsKey("segments"))
|
|
{
|
|
var segs = root["segments"] as JObject;
|
|
if (segs != null)
|
|
{
|
|
cache.segments = new SegmentTpl();
|
|
|
|
foreach (string segmentkey in new string[] { "ad", "skip" })
|
|
{
|
|
if (segs.ContainsKey(segmentkey))
|
|
{
|
|
var arr = segs[segmentkey] as JArray;
|
|
if (arr != null)
|
|
{
|
|
foreach (var it in arr)
|
|
{
|
|
int? s = it.Value<int?>("start");
|
|
int? e = it.Value<int?>("end");
|
|
if (s.HasValue && e.HasValue)
|
|
cache.segments.ad(s.Value, e.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
proxyManager?.Success();
|
|
hybridCache.Set(key, cache, cacheTime(120));
|
|
}
|
|
|
|
var streamquality = new StreamQualityTpl();
|
|
foreach (var l in cache.streams)
|
|
streamquality.Append(HostStreamProxy(l.url), l.q);
|
|
|
|
if (play)
|
|
return RedirectToPlay(streamquality.Firts().link);
|
|
|
|
string name = title ?? original_title;
|
|
if (episode > 0)
|
|
name += $" ({episode} серия)";
|
|
|
|
return ContentTo(VideoTpl.ToJson("play", streamquality.Firts().link, name, streamquality: streamquality, vast: init.vast, segments: cache.segments));
|
|
});
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|