mirror of
https://github.com/C9Glax/tranga.git
synced 2025-05-08 16:12:10 +02:00
Compare commits
12 Commits
7dc34c10e5
...
be6b3da1be
Author | SHA1 | Date | |
---|---|---|---|
be6b3da1be | |||
d0b775444d | |||
268441a47d | |||
b818f63f2a | |||
78a9322036 | |||
cc32b3dfae | |||
110a0bf481 | |||
fdbe585aa0 | |||
123a8b06b2 | |||
2350c5a04b | |||
f532e2ff76 | |||
6a8df2f5f8 |
@ -9,7 +9,6 @@ namespace API.MangaDownloadClients;
|
|||||||
internal class ChromiumDownloadClient : DownloadClient
|
internal class ChromiumDownloadClient : DownloadClient
|
||||||
{
|
{
|
||||||
private static IBrowser? _browser;
|
private static IBrowser? _browser;
|
||||||
private const int StartTimeoutMs = 10000;
|
|
||||||
private readonly HttpDownloadClient _httpDownloadClient;
|
private readonly HttpDownloadClient _httpDownloadClient;
|
||||||
|
|
||||||
private static async Task<IBrowser> StartBrowser()
|
private static async Task<IBrowser> StartBrowser()
|
||||||
@ -22,7 +21,7 @@ internal class ChromiumDownloadClient : DownloadClient
|
|||||||
"--disable-dev-shm-usage",
|
"--disable-dev-shm-usage",
|
||||||
"--disable-setuid-sandbox",
|
"--disable-setuid-sandbox",
|
||||||
"--no-sandbox"},
|
"--no-sandbox"},
|
||||||
Timeout = StartTimeoutMs
|
Timeout = 30000
|
||||||
}, new LoggerFactory([new LogProvider()])); //TODO
|
}, new LoggerFactory([new LogProvider()])); //TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,15 +66,19 @@ internal class ChromiumDownloadClient : DownloadClient
|
|||||||
|
|
||||||
private RequestResult MakeRequestBrowser(string url, string? referrer = null, string? clickButton = null)
|
private RequestResult MakeRequestBrowser(string url, string? referrer = null, string? clickButton = null)
|
||||||
{
|
{
|
||||||
|
if (_browser is null)
|
||||||
|
return new RequestResult(HttpStatusCode.InternalServerError, null, Stream.Null);
|
||||||
IPage page = _browser.NewPageAsync().Result;
|
IPage page = _browser.NewPageAsync().Result;
|
||||||
page.DefaultTimeout = 10000;
|
page.DefaultTimeout = 10000;
|
||||||
IResponse response;
|
IResponse response;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
response = page.GoToAsync(url, WaitUntilNavigation.Networkidle0).Result;
|
response = page.GoToAsync(url, WaitUntilNavigation.Networkidle0).Result;
|
||||||
|
//Log($"Page loaded. {url}");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
//Log($"Could not load Page {url}\n{e.Message}");
|
||||||
page.CloseAsync();
|
page.CloseAsync();
|
||||||
return new RequestResult(HttpStatusCode.InternalServerError, null, Stream.Null);
|
return new RequestResult(HttpStatusCode.InternalServerError, null, Stream.Null);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,6 @@ using (var scope = app.Services.CreateScope())
|
|||||||
new MangaKatana(),
|
new MangaKatana(),
|
||||||
new MangaLife(),
|
new MangaLife(),
|
||||||
new Manganato(),
|
new Manganato(),
|
||||||
new Mangasee(),
|
|
||||||
new Mangaworld(),
|
new Mangaworld(),
|
||||||
new ManhuaPlus(),
|
new ManhuaPlus(),
|
||||||
new Weebcentral()
|
new Weebcentral()
|
||||||
|
@ -1,215 +0,0 @@
|
|||||||
using System.Data;
|
|
||||||
using System.Net;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
using API.MangaDownloadClients;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Soenneker.Utils.String.NeedlemanWunsch;
|
|
||||||
|
|
||||||
namespace API.Schema.MangaConnectors;
|
|
||||||
|
|
||||||
public class Mangasee : MangaConnector
|
|
||||||
{
|
|
||||||
public Mangasee() : base("Mangasee", ["en"], ["mangasee123.com"])
|
|
||||||
{
|
|
||||||
this.downloadClient = new ChromiumDownloadClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct SearchResult
|
|
||||||
{
|
|
||||||
public string i { get; set; }
|
|
||||||
public string s { get; set; }
|
|
||||||
public string[] a { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)[] GetManga(string publicationTitle = "")
|
|
||||||
{
|
|
||||||
string requestUrl = "https://mangasee123.com/_search.php";
|
|
||||||
RequestResult requestResult =
|
|
||||||
downloadClient.MakeRequest(requestUrl, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
SearchResult[] searchResults = JsonConvert.DeserializeObject<SearchResult[]>(requestResult.htmlDocument!.DocumentNode.InnerText) ??
|
|
||||||
throw new NoNullAllowedException();
|
|
||||||
SearchResult[] filteredResults = FilteredResults(publicationTitle, searchResults);
|
|
||||||
|
|
||||||
|
|
||||||
string[] urls = filteredResults.Select(result => $"https://mangasee123.com/manga/{result.i}").ToArray();
|
|
||||||
List<(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)> searchResultManga = new();
|
|
||||||
foreach (string url in urls)
|
|
||||||
{
|
|
||||||
(Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? newManga = GetMangaFromUrl(url);
|
|
||||||
if(newManga is { } manga)
|
|
||||||
searchResultManga.Add(manga);
|
|
||||||
}
|
|
||||||
return searchResultManga.ToArray();
|
|
||||||
}
|
|
||||||
catch (NoNullAllowedException)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly string[] _filterWords = {"a", "the", "of", "as", "to", "no", "for", "on", "with", "be", "and", "in", "wa", "at", "be", "ni"};
|
|
||||||
private string ToFilteredString(string input) => string.Join(' ', input.ToLower().Split(' ').Where(word => _filterWords.Contains(word) == false));
|
|
||||||
private SearchResult[] FilteredResults(string publicationTitle, SearchResult[] unfilteredSearchResults)
|
|
||||||
{
|
|
||||||
Dictionary<SearchResult, int> similarity = new();
|
|
||||||
foreach (SearchResult sr in unfilteredSearchResults)
|
|
||||||
{
|
|
||||||
List<int> scores = new();
|
|
||||||
string filteredPublicationString = ToFilteredString(publicationTitle);
|
|
||||||
string filteredSString = ToFilteredString(sr.s);
|
|
||||||
scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(filteredSString, filteredPublicationString));
|
|
||||||
foreach (string srA in sr.a)
|
|
||||||
{
|
|
||||||
string filteredAString = ToFilteredString(srA);
|
|
||||||
scores.Add(NeedlemanWunschStringUtil.CalculateSimilarity(filteredAString, filteredPublicationString));
|
|
||||||
}
|
|
||||||
similarity.Add(sr, scores.Sum() / scores.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SearchResult> ret = similarity.OrderBy(s => s.Value).Take(10).Select(s => s.Key).ToList();
|
|
||||||
return ret.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromId(string publicationId)
|
|
||||||
{
|
|
||||||
return GetMangaFromUrl($"https://mangasee123.com/manga/{publicationId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?)? GetMangaFromUrl(string url)
|
|
||||||
{
|
|
||||||
Regex publicationIdRex = new(@"https:\/\/mangasee123.com\/manga\/(.*)(\/.*)*");
|
|
||||||
string publicationId = publicationIdRex.Match(url).Groups[1].Value;
|
|
||||||
|
|
||||||
RequestResult requestResult = this.downloadClient.MakeRequest(url, RequestType.MangaInfo);
|
|
||||||
if((int)requestResult.statusCode < 300 && (int)requestResult.statusCode >= 200 && requestResult.htmlDocument is not null)
|
|
||||||
return ParseSinglePublicationFromHtml(requestResult.htmlDocument, publicationId, url);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Manga, List<Author>?, List<MangaTag>?, List<Link>?, List<MangaAltTitle>?) ParseSinglePublicationFromHtml(HtmlDocument document, string publicationId, string websiteUrl)
|
|
||||||
{
|
|
||||||
string originalLanguage = "", status = "";
|
|
||||||
Dictionary<string, string> altTitles = new(), links = new();
|
|
||||||
HashSet<string> tags = new();
|
|
||||||
MangaReleaseStatus releaseStatus = MangaReleaseStatus.Unreleased;
|
|
||||||
|
|
||||||
HtmlNode posterNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//img");
|
|
||||||
string coverUrl = posterNode.GetAttributeValue("src", "");
|
|
||||||
|
|
||||||
HtmlNode titleNode = document.DocumentNode.SelectSingleNode("//div[@class='BoxBody']//div[@class='row']//h1");
|
|
||||||
string sortName = titleNode.InnerText;
|
|
||||||
|
|
||||||
HtmlNode[] authorsNodes = document.DocumentNode
|
|
||||||
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Author(s):']/..").Descendants("a")
|
|
||||||
.ToArray();
|
|
||||||
List<string> authorNames = new();
|
|
||||||
foreach (HtmlNode authorNode in authorsNodes)
|
|
||||||
authorNames.Add(authorNode.InnerText);
|
|
||||||
List<Author> authors = authorNames.Select(a => new Author(a)).ToList();
|
|
||||||
|
|
||||||
HtmlNode[] genreNodes = document.DocumentNode
|
|
||||||
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Genre(s):']/..").Descendants("a")
|
|
||||||
.ToArray();
|
|
||||||
foreach (HtmlNode genreNode in genreNodes)
|
|
||||||
tags.Add(genreNode.InnerText);
|
|
||||||
List<MangaTag> mangaTags = tags.Select(t => new MangaTag(t)).ToList();
|
|
||||||
|
|
||||||
HtmlNode yearNode = document.DocumentNode
|
|
||||||
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Released:']/..").Descendants("a")
|
|
||||||
.First();
|
|
||||||
uint year = uint.Parse(yearNode.InnerText);
|
|
||||||
|
|
||||||
HtmlNode[] statusNodes = document.DocumentNode
|
|
||||||
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Status:']/..").Descendants("a")
|
|
||||||
.ToArray();
|
|
||||||
foreach (HtmlNode statusNode in statusNodes)
|
|
||||||
if (statusNode.InnerText.Contains("publish", StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
status = statusNode.InnerText.Split(' ')[0];
|
|
||||||
switch (status.ToLower())
|
|
||||||
{
|
|
||||||
case "cancelled": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "hiatus": releaseStatus = MangaReleaseStatus.OnHiatus; break;
|
|
||||||
case "discontinued": releaseStatus = MangaReleaseStatus.Cancelled; break;
|
|
||||||
case "complete": releaseStatus = MangaReleaseStatus.Completed; break;
|
|
||||||
case "ongoing": releaseStatus = MangaReleaseStatus.Continuing; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlNode descriptionNode = document.DocumentNode
|
|
||||||
.SelectNodes("//div[@class='BoxBody']//div[@class='row']//span[text()='Description:']/..")
|
|
||||||
.Descendants("div").First();
|
|
||||||
string description = descriptionNode.InnerText;
|
|
||||||
|
|
||||||
Manga manga = new (publicationId, sortName, description, websiteUrl, coverUrl, null, year,
|
|
||||||
originalLanguage, releaseStatus, -1,
|
|
||||||
this,
|
|
||||||
authors,
|
|
||||||
mangaTags,
|
|
||||||
[],
|
|
||||||
[]);
|
|
||||||
|
|
||||||
return (manga, authors, mangaTags, [], []);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Chapter[] GetChapters(Manga manga, string language="en")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
XDocument doc = XDocument.Load($"https://mangasee123.com/rss/{manga.MangaId}.xml");
|
|
||||||
XElement[] chapterItems = doc.Descendants("item").ToArray();
|
|
||||||
List<Chapter> chapters = new();
|
|
||||||
Regex chVolRex = new(@".*chapter-([0-9\.]+)(?:-index-([0-9\.]+))?.*");
|
|
||||||
foreach (XElement chapter in chapterItems)
|
|
||||||
{
|
|
||||||
string url = chapter.Descendants("link").First().Value;
|
|
||||||
Match m = chVolRex.Match(url);
|
|
||||||
int? volumeNumber = m.Groups[2].Success ? int.Parse(m.Groups[2].Value) : null;
|
|
||||||
if(!ChapterNumber.CanParse(m.Groups[1].Value))
|
|
||||||
continue;
|
|
||||||
ChapterNumber chapterNumber = new(m.Groups[1].Value);
|
|
||||||
|
|
||||||
string chapterUrl = Regex.Replace(url, @"-page-[0-9]+(\.html)", ".html");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
chapters.Add(new Chapter(manga, chapterUrl,chapterNumber, volumeNumber, null));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Return Chapters ordered by Chapter-Number
|
|
||||||
return chapters.Order().ToArray();
|
|
||||||
}
|
|
||||||
catch (HttpRequestException e)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override string[] GetChapterImageUrls(Chapter chapter)
|
|
||||||
{
|
|
||||||
RequestResult requestResult = this.downloadClient.MakeRequest(chapter.Url, RequestType.Default);
|
|
||||||
if ((int)requestResult.statusCode < 200 || (int)requestResult.statusCode >= 300 || requestResult.htmlDocument is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlDocument document = requestResult.htmlDocument;
|
|
||||||
|
|
||||||
HtmlNode gallery = document.DocumentNode.Descendants("div").First(div => div.HasClass("ImageGallery"));
|
|
||||||
HtmlNode[] images = gallery.Descendants("img").Where(img => img.HasClass("img-fluid")).ToArray();
|
|
||||||
List<string> urls = new();
|
|
||||||
foreach(HtmlNode galleryImage in images)
|
|
||||||
urls.Add(galleryImage.GetAttributeValue("src", ""));
|
|
||||||
return urls.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,7 +30,6 @@ public class PgsqlContext(DbContextOptions<PgsqlContext> options) : DbContext(op
|
|||||||
.HasValue<MangaKatana>("MangaKatana")
|
.HasValue<MangaKatana>("MangaKatana")
|
||||||
.HasValue<MangaLife>("Manga4Life")
|
.HasValue<MangaLife>("Manga4Life")
|
||||||
.HasValue<Manganato>("Manganato")
|
.HasValue<Manganato>("Manganato")
|
||||||
.HasValue<Mangasee>("Mangasee")
|
|
||||||
.HasValue<Mangaworld>("Mangaworld")
|
.HasValue<Mangaworld>("Mangaworld")
|
||||||
.HasValue<ManhuaPlus>("ManhuaPlus")
|
.HasValue<ManhuaPlus>("ManhuaPlus")
|
||||||
.HasValue<Weebcentral>("Weebcentral")
|
.HasValue<Weebcentral>("Weebcentral")
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace API;
|
namespace API;
|
||||||
|
|
||||||
@ -20,4 +21,20 @@ public static class TokenGen
|
|||||||
key = string.Join('-', prefix, key);
|
key = string.Join('-', prefix, key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string CreateTokenHash(string prefix, uint fullLength, string[] keys)
|
||||||
|
{
|
||||||
|
if (prefix.Length + 1 >= fullLength - MinimumLength)
|
||||||
|
throw new ArgumentException("Prefix to long to create Token of meaningful length.");
|
||||||
|
int l = (int)(fullLength - prefix.Length - 1);
|
||||||
|
MD5 md5 = MD5.Create();
|
||||||
|
byte[][] hashes = keys.Select(key => md5.ComputeHash(Encoding.UTF8.GetBytes(key))).ToArray();
|
||||||
|
byte[] xOrHash = new byte[l];
|
||||||
|
foreach (byte[] hash in hashes)
|
||||||
|
for(int i = 0; i < hash.Length; i++)
|
||||||
|
xOrHash[i] = (byte)(xOrHash[i] ^ (i >= hash.Length ? 0 : hash[i]));
|
||||||
|
string key = new (xOrHash.Select(b => Chars[b % Chars.Length]).ToArray());
|
||||||
|
key = string.Join('-', prefix, key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user