From b95155dc4743fee8e2249c6ed7d71a32f106f019 Mon Sep 17 00:00:00 2001 From: Nikolay Redko Date: Thu, 8 Jun 2023 15:18:30 +0700 Subject: [PATCH 1/2] Support of zelluloza.ru --- Elib2Ebook/Logic/Getters/ZellulozaGetter.cs | 292 ++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 Elib2Ebook/Logic/Getters/ZellulozaGetter.cs diff --git a/Elib2Ebook/Logic/Getters/ZellulozaGetter.cs b/Elib2Ebook/Logic/Getters/ZellulozaGetter.cs new file mode 100644 index 00000000..192b2b90 --- /dev/null +++ b/Elib2Ebook/Logic/Getters/ZellulozaGetter.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using Elib2Ebook.Configs; +using Elib2Ebook.Extensions; +using Elib2Ebook.Types.AuthorToday; +using Elib2Ebook.Types.Book; +using HtmlAgilityPack; +using HtmlAgilityPack.CssSelectors.NetCore; + +namespace Elib2Ebook.Logic.Getters +{ + public class ZellulozaGetter : GetterBase + { + private string _nonce; + private string _ze_hash = "dummy"; + + public ZellulozaGetter(BookGetterConfig config) : base(config) { } + + protected override Uri SystemUrl => new("https://zelluloza.ru"); + + public override async Task Init() + { + await base.Init(); + var response = await Config.Client.GetWithTriesAsync(SystemUrl.MakeRelativeUri("/my/")); + var doc = await response.Content.ReadAsStringAsync().ContinueWith(t => t.Result.AsHtmlDoc()); + + _nonce = doc.QuerySelector("form[name=logfrm] input[name=nonce]")?.Attributes["value"]?.Value; + if (string.IsNullOrWhiteSpace(_nonce)) + { + throw new ArgumentException("Не удалось получить nonce", nameof(_nonce)); + } + } + + private HttpRequestMessage GetDefaultMessage(Uri uri, Uri host, HttpContent content = null) + { + var message = new HttpRequestMessage(content == default ? HttpMethod.Get : HttpMethod.Post, uri); + message.Content = content; + return message; + } + + public override async Task Authorize() + { + if (!Config.HasCredentials) + { + return; + } + var data = new List>() + { + new KeyValuePair("nonce", _nonce), + new KeyValuePair("log2in", Config.Options.Login), + new KeyValuePair("q", "login"), + new KeyValuePair("token", ""), + new KeyValuePair("pas2sword", Config.Options.Password), + new KeyValuePair("btnvalue", "Войти"), + + }; + var content = new FormUrlEncodedContent(data); + var response = await Config.Client.SendAsync(GetDefaultMessage(SystemUrl.AppendSegment("/"), SystemUrl, content)); + _ze_hash = Config.CookieContainer.GetAllCookies()["ze_hash"].Value; + if(string.IsNullOrEmpty(_ze_hash) || _ze_hash == "dummy") + { + throw new Exception("Не удалось авторизоваться."); + } + + } + + public override async Task Get(Uri url) + { + var doc = await Config.Client.GetHtmlDocWithTriesAsync(url); +#if DEBUG + doc.Save(new FileStream("z.html", FileMode.OpenOrCreate)); +#endif + var book = new Book(url) + { + Title = doc.GetTextBySelector("span[itemprop=name]"), + Cover = await GetCover(doc, url), + Chapters = await FillChapters(url, doc), + Author = GetAuthor(doc, url), + Annotation = doc.QuerySelector("meta[itemprop=description]")?.Attributes["content"]?.Value, + Seria = GetSeria(doc, url) + }; + return book; + } + + private Seria GetSeria(HtmlDocument doc, Uri url) + { + var seria = new Seria() + { + Name = doc.QuerySelector("p[class=jb > a[class=lnk] > b")?.InnerHtml.Replace("'", ""), + Number = "" + }; + return seria; + } + + private Author GetAuthor(HtmlDocument doc, Uri url) + { + return new Author( + doc.QuerySelector("span[itemprop=author] > meta[itemprop=name]")?.Attributes["content"]?.Value, + new Uri(doc.QuerySelector("span[itemprop=author] > link[itemprop=url]")?.Attributes["href"]?.Value) + ); + } + private Task GetCover(HtmlDocument doc, Uri url) + { + var imagePath = doc.QuerySelector("meta[property=og:image]")?.Attributes["content"]?.Value; + return !string.IsNullOrWhiteSpace(imagePath) ? SaveImage(url.MakeRelativeUri(imagePath)) : Task.FromResult(default(Image)); + } + + private async Task> FillChapters(Uri url, HtmlDocument doc) + { + var result = new List(); + foreach (var anchor in doc.QuerySelectorAll("a[class=chptitle]")) + { + Console.WriteLine($"Загружаю главу {anchor.InnerHtml.CoverQuotes()}"); + var content = await GetChapContent(new Uri(url, anchor.Attributes["href"].Value)); + result.Add(new Chapter + { + Title = anchor.InnerHtml, + Content = content, + Images = await GetImages(content.AsHtmlDoc(), url) + }); + } + return result; + } + + private async Task GetChapContent(Uri uri) + { + Console.WriteLine(uri); + var id = uri.Segments[3].Trim('/'); + + var doc = await Config.Client.GetHtmlDocWithTriesAsync(uri); + +#if DEBUG + doc.Save(new FileStream($"{id}.html", FileMode.OpenOrCreate)); +#endif + + var page = doc.AsString(); + + var re = Regex.Match(page, @"InitRead\((.*)\);\s*$", RegexOptions.Multiline); + var vars = re.Groups[1].Value.Split(',').Select(str => str.Trim().Replace("'", "")).ToArray(); + var picsOnly = (vars[2] == "2" && vars[3] == "2"); + var numPages = int.Parse(vars[4]); + re = Regex.Match(page, @"ajax\(\'booktext\',\s*\'\',\s*\'getbook\',([^\)]*).*\)", RegexOptions.Multiline); + if (!re.Success) + { + return "

Глава недоступна

"; + } + if (!picsOnly) + { + vars = re.Groups?[1].Value.Split(",").Select(str => str.Trim().Replace("'", "")).ToArray(); + return await GetChapText(uri, page, numPages, vars); + } + return await GetChapPics(uri, page, numPages); + } + + private Task GetChapPics(Uri uri, string page, int numPages) + { + throw new NotImplementedException(); + } + + private async Task GetChapText(Uri uri, string page, int numPages, string[] vars) + { + var data = new List>() + { + new KeyValuePair("op", "getbook"), + new KeyValuePair("par1", vars[0]), + new KeyValuePair("par2", vars[1]), + new KeyValuePair("par4",vars[2]), + }; + var content = new FormUrlEncodedContent(data); + var response = await Config.Client.SendAsync(GetDefaultMessage(SystemUrl.AppendSegment("/aiaxcall/"), SystemUrl, content)); + var body = await response.Content.ReadAsStringAsync(); + var encrypted = body.Split("")[0].Split("\n"); + var decrypted = encrypted.Select(str => DecryptString(str)).ToArray(); + return string.Join("\n", decrypted); + } + + private string DecryptString(string str) + { + var b = new Dictionary(); + b["~"] = "0"; + b["H"] = "1"; + b["^"] = "2"; + b["@"] = "3"; + b["f"] = "4"; + b["0"] = "5"; + b["5"] = "6"; + b["n"] = "7"; + b["r"] = "8"; + b["="] = "9"; + b["W"] = "a"; + b["L"] = "b"; + b["7"] = "c"; + b[" "] = "d"; + b["u"] = "e"; + b["c"] = "f"; + var f = new List(); + for (var a = 0; a < str.Length; a += 2) + { + f.Add(b[str.Substring(a, 1)] + b[str.Substring(a + 1, 1)]); + }; + var ret = Hex2utf8(f); + ret = ret.Replace("\r", ""); + if (!ret.StartsWith("[ctr]") && !ret.StartsWith("Оставьте отзыв в ленте отзывов")) + { + ret = "

" + ret.Replace("\r", "") + "

\n"; + } + ret = Regex.Replace(ret, @"\[~\]([^\[]*)\[\/]", "$1"); + ret = Regex.Replace(ret, @"\[\*\]([^\]]*)\[\/]", "$1"); + ret = Regex.Replace(ret, @"\[blu\]([^\]]*)\[\/]", "$1"); + ret = Regex.Replace(ret, @"\[\*\]([^\]]*)\[\/]", "$1"); + ret = Regex.Replace(ret, @"\[~\]([^\[]*)\[\/]", "$1"); + ret = Regex.Replace(ret, @"\[blu\]([^\]]*)\[\/]", "$1"); + if (!ret.StartsWith("[ctr]") && !ret.StartsWith("Оставьте отзыв в ленте отзывов")) + { + ret = "

" + ret.Replace("\r", "") + "

\n"; + } + else + { + ret = ""; + } + return ret; + } + + private string Hex2utf8(List d) + { + var b = 0; + var a = ""; + while (b < d.Count) + { + var c = Convert.ToInt16("0x0"+d[b], 16) & 255; + if (c < 128) + { + if (c < 16) + { + switch (c) + { + case 9: + a += " "; + break; + case 13: + a += "\r"; + break; + case 10: + a += "\n"; + break; + } + } + else + { + a += Char.ConvertFromUtf32(c); + }; + b++; + } + else + { + int c2; + int c3; + if ((c > 191) && (c < 224)) + { + if (b + 1 < d.Count) + { + c2 = Convert.ToInt16("0x0" + d[b + 1], 16) & 255; + a += Char.ConvertFromUtf32(((c & 31) << 6) | (c2 & 63)); + }; + b += 2; + } + else + { + if (b + 2 < d.Count) + { + c2 = Convert.ToInt16("0x0" + d[b + 1], 16) & 255; + c3 = Convert.ToInt16("0x0" + d[b + 2], 16) & 255; + a += Char.ConvertFromUtf32(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + }; + b += 3; + } + } + } + return a; + } + } +} From ad36e234d08a89c221d3ee7c2d7d70be753a273e Mon Sep 17 00:00:00 2001 From: Nikolay Redko Date: Mon, 26 Jun 2023 20:10:30 +0700 Subject: [PATCH 2/2] .. --- .../Logic/Getters/Litnet/LitnetGetterBase.cs | 55 +++++++++++++++---- Elib2Ebook/Properties/launchSettings.json | 16 ++++++ 2 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 Elib2Ebook/Properties/launchSettings.json diff --git a/Elib2Ebook/Logic/Getters/Litnet/LitnetGetterBase.cs b/Elib2Ebook/Logic/Getters/Litnet/LitnetGetterBase.cs index 08723f6e..c7838dd7 100644 --- a/Elib2Ebook/Logic/Getters/Litnet/LitnetGetterBase.cs +++ b/Elib2Ebook/Logic/Getters/Litnet/LitnetGetterBase.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Http.Json; using System.Security.Cryptography; using System.Text; @@ -14,7 +16,6 @@ using Elib2Ebook.Types.Litnet; using HtmlAgilityPack; using HtmlAgilityPack.CssSelectors.NetCore; - namespace Elib2Ebook.Logic.Getters.Litnet; public abstract class LitnetGetterBase : GetterBase { @@ -28,22 +29,24 @@ public LitnetGetterBase(BookGetterConfig config) : base(config) { } private string _token { get; set; } protected override string GetId(Uri url) => base.GetId(url).Split('-').Last().Replace("b", string.Empty); - - private static string Decrypt(string text) { + private static byte[] DecryptBin(string text) + { using var aes = Aes.Create(); const int IV_SHIFT = 16; - - aes.Key = Encoding.UTF8.GetBytes(SECRET); + + aes.Key = Encoding.UTF8.GetBytes(SECRET); aes.IV = Encoding.UTF8.GetBytes(text)[..IV_SHIFT]; - + var decryptor = aes.CreateDecryptor(); using var ms = new MemoryStream(Convert.FromBase64String(text)); using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); - + var output = new MemoryStream(); cs.CopyTo(output); - - return Encoding.UTF8.GetString(output.ToArray()[IV_SHIFT..]); + return output.ToArray()[IV_SHIFT..]; + } + private static string Decrypt(string text) { + return Encoding.UTF8.GetString(DecryptBin(text)); } private static string GetSign(string token) { @@ -167,8 +170,26 @@ private async Task> FillChapters(string token, LitnetBookResponse var chapter = new Chapter { Title = (content.Title ?? book.Title).Trim() }; - - if (!string.IsNullOrWhiteSpace(litnetChapter.Text)) { + if (string.IsNullOrWhiteSpace(litnetChapter.Text)) + { + var values = new Dictionary() { + { "app", "android" }, + { "device_id", DeviceId }, + { "user_token", _token }, + { "sign", GetSign(_token) }, + { "version", "1.0" } + }; + var data = new FormUrlEncodedContent(values); + var url = $"https://sapi.litnet.com/v1/text/get-chapter?chapter_id={litnetChapter.Id}"; + var response = await Config.Client.PostAsync(url, data); + var buff = await response.Content.ReadAsByteArrayAsync(); + var gz = DecryptBin(Convert.ToBase64String(buff)); + var txt = Gunzip(gz); + var chapterDoc = txt.Deserialize().Aggregate(new StringBuilder(), (sb, row) => sb.Append(row)).AsHtmlDoc(); + chapter.Images = await GetImages(chapterDoc, SystemUrl); + chapter.Content = chapterDoc.DocumentNode.InnerHtml; + } else + { var chapterDoc = GetChapter(litnetChapter); chapter.Images = await GetImages(chapterDoc, SystemUrl); chapter.Content = chapterDoc.DocumentNode.InnerHtml; @@ -180,6 +201,18 @@ private async Task> FillChapters(string token, LitnetBookResponse return result; } + private string Gunzip(byte[] gz) + { + using (var compressedStream = new MemoryStream(gz)) + using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress)) + using (var resultStream = new MemoryStream()) + { + zipStream.CopyTo(resultStream); + var result = resultStream.ToArray(); + return Encoding.UTF8.GetString(result); + } + } + private static HtmlDocument GetChapter(LitnetChapterResponse chapter) { return Decrypt(chapter.Text).Deserialize().Aggregate(new StringBuilder(), (sb, row) => sb.Append(row)).AsHtmlDoc(); } diff --git a/Elib2Ebook/Properties/launchSettings.json b/Elib2Ebook/Properties/launchSettings.json new file mode 100644 index 00000000..1d449d24 --- /dev/null +++ b/Elib2Ebook/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "profiles": { + "WSL": { + "commandName": "WSL2", + "distributionName": "" + }, + "Litnet": { + "commandName": "Project", + "commandLineArgs": "-u https://litnet.com/ru/reader/vtoraya-doroga-put-oficera-b116264 -l rauf2282004@mail.ru -p parollitnet -f fb2" + }, + "Zelluloza": { + "commandName": "Project", + "commandLineArgs": "-u https://zelluloza.ru/books/14742/ -l katulman@mail.ru -p SzYOYdoN -f fb2" + } + } +} \ No newline at end of file