2024-12-16 22:54:23 +01:00
using System.Text.RegularExpressions ;
2024-12-14 21:53:29 +01:00
using API.MangaDownloadClients ;
using HtmlAgilityPack ;
namespace API.Schema.MangaConnectors ;
public class ManhuaPlus : MangaConnector
{
public ManhuaPlus ( ) : base ( "ManhuaPlus" , [ "en" ] , [ "manhuaplus.org" ] )
{
this . downloadClient = new ChromiumDownloadClient ( ) ;
}
2024-12-16 22:54:23 +01:00
public override ( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) [ ] GetManga ( string publicationTitle = "" )
2024-12-14 21:53:29 +01:00
{
string sanitizedTitle = string . Join ( ' ' , Regex . Matches ( publicationTitle , "[A-z]*" ) . Where ( str = > str . Length > 0 ) ) . ToLower ( ) ;
string requestUrl = $"https://manhuaplus.org/search?keyword={sanitizedTitle}" ;
RequestResult requestResult =
downloadClient . MakeRequest ( requestUrl , RequestType . Default ) ;
2024-12-14 22:02:32 +01:00
if ( ( int ) requestResult . statusCode < 200 | | ( int ) requestResult . statusCode > = 300 | | requestResult . htmlDocument is null )
return [ ] ;
2024-12-16 22:54:23 +01:00
( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) [ ] publications = ParsePublicationsFromHtml ( requestResult . htmlDocument ) ;
2024-12-14 21:53:29 +01:00
return publications ;
}
2024-12-16 22:54:23 +01:00
private ( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) [ ] ParsePublicationsFromHtml ( HtmlDocument document )
2024-12-14 21:53:29 +01:00
{
if ( document . DocumentNode . SelectSingleNode ( "//h1/../.." ) . ChildNodes //I already want to not.
. Any ( node = > node . InnerText . Contains ( "No manga found" ) ) )
2024-12-15 23:00:35 +01:00
return [ ] ;
2024-12-14 21:53:29 +01:00
List < string > urls = document . DocumentNode
. SelectNodes ( "//h1/../..//a[contains(@href, 'https://manhuaplus.org/manga/') and contains(concat(' ',normalize-space(@class),' '),' clamp ') and not(contains(@href, '/chapter'))]" )
. Select ( mangaNode = > mangaNode . GetAttributeValue ( "href" , "" ) ) . ToList ( ) ;
2024-12-16 22:54:23 +01:00
List < ( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) > ret = new ( ) ;
2024-12-14 21:53:29 +01:00
foreach ( string url in urls )
{
2024-12-16 22:54:23 +01:00
( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) ? manga = GetMangaFromUrl ( url ) ;
2024-12-15 23:00:35 +01:00
if ( manga is { } x )
ret . Add ( x ) ;
2024-12-14 21:53:29 +01:00
}
return ret . ToArray ( ) ;
}
2024-12-16 22:54:23 +01:00
public override ( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) ? GetMangaFromId ( string publicationId )
2024-12-14 21:53:29 +01:00
{
return GetMangaFromUrl ( $"https://manhuaplus.org/manga/{publicationId}" ) ;
}
2024-12-16 22:54:23 +01:00
public override ( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) ? GetMangaFromUrl ( string url )
2024-12-14 21:53:29 +01:00
{
Regex publicationIdRex = new ( @"https:\/\/manhuaplus.org\/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 & & requestResult . redirectedToUrl ! = "https://manhuaplus.org/home" ) //When manga doesnt exists it redirects to home
return ParseSinglePublicationFromHtml ( requestResult . htmlDocument , publicationId , url ) ;
return null ;
}
2024-12-16 22:54:23 +01:00
private ( Manga , List < Author > ? , List < MangaTag > ? , List < Link > ? , List < MangaAltTitle > ? ) ParseSinglePublicationFromHtml ( HtmlDocument document , string publicationId , string websiteUrl )
2024-12-14 21:53:29 +01:00
{
string originalLanguage = "" , status = "" ;
Dictionary < string , string > altTitles = new ( ) , links = new ( ) ;
HashSet < string > tags = new ( ) ;
MangaReleaseStatus releaseStatus = MangaReleaseStatus . Unreleased ;
HtmlNode posterNode = document . DocumentNode . SelectSingleNode ( "/html/body/main/div/div/div[2]/div[1]/figure/a/img" ) ; //BRUH
Regex posterRex = new ( @".*(\/uploads/covers/[a-zA-Z0-9\-\._\~\!\$\&\'\(\)\*\+\,\;\=\:\@]+).*" ) ;
2024-12-15 23:00:35 +01:00
string coverUrl = $"https://manhuaplus.org/{posterRex.Match(posterNode.GetAttributeValue(" src ", " ")).Groups[1].Value}" ;
2024-12-14 21:53:29 +01:00
HtmlNode titleNode = document . DocumentNode . SelectSingleNode ( "//h1" ) ;
string sortName = titleNode . InnerText . Replace ( "\n" , "" ) ;
2024-12-15 23:00:35 +01:00
List < string > authorNames = new ( ) ;
2024-12-14 21:53:29 +01:00
try
{
HtmlNode [ ] authorsNodes = document . DocumentNode
. SelectNodes ( "//a[contains(@href, 'https://manhuaplus.org/authors/')]" )
. ToArray ( ) ;
foreach ( HtmlNode authorNode in authorsNodes )
2024-12-15 23:00:35 +01:00
authorNames . Add ( authorNode . InnerText ) ;
2024-12-14 21:53:29 +01:00
}
catch ( ArgumentNullException e )
{
}
2024-12-16 22:54:23 +01:00
List < Author > authors = authorNames . Select ( a = > new Author ( a ) ) . ToList ( ) ;
2024-12-14 21:53:29 +01:00
try
{
HtmlNode [ ] genreNodes = document . DocumentNode
. SelectNodes ( "//a[contains(@href, 'https://manhuaplus.org/genres/')]" ) . ToArray ( ) ;
foreach ( HtmlNode genreNode in genreNodes )
tags . Add ( genreNode . InnerText . Replace ( "\n" , "" ) ) ;
}
catch ( ArgumentNullException e )
{
}
2024-12-16 22:54:23 +01:00
List < MangaTag > mangaTags = tags . Select ( t = > new MangaTag ( t ) ) . ToList ( ) ;
2024-12-14 21:53:29 +01:00
Regex yearRex = new ( @"(?:[0-9]{1,2}\/){2}([0-9]{2,4}) [0-9]{1,2}:[0-9]{1,2}" ) ;
HtmlNode yearNode = document . DocumentNode . SelectSingleNode ( "//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-clock ')]/../span" ) ;
Match match = yearRex . Match ( yearNode . InnerText ) ;
2024-12-15 23:00:35 +01:00
uint year = match . Success & & match . Groups [ 1 ] . Success ? uint . Parse ( match . Groups [ 1 ] . Value ) : 0 ;
2024-12-14 21:53:29 +01:00
status = document . DocumentNode . SelectSingleNode ( "//aside//i[contains(concat(' ',normalize-space(@class),' '),' fa-rss ')]/../span" ) . InnerText . Replace ( "\n" , "" ) ;
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
. SelectSingleNode ( "//div[@id='syn-target']" ) ;
string description = descriptionNode . InnerText ;
2024-12-15 23:00:35 +01:00
Manga manga = new ( publicationId , sortName , description , websiteUrl , coverUrl , null , year ,
2024-12-18 16:42:59 +01:00
originalLanguage , releaseStatus , - 1 ,
2024-12-16 19:25:22 +01:00
this ,
authors ,
mangaTags ,
2024-12-15 23:00:35 +01:00
[] ,
[] ) ;
return ( manga , authors , mangaTags , [ ] , [ ] ) ;
2024-12-14 21:53:29 +01:00
}
public override Chapter [ ] GetChapters ( Manga manga , string language = "en" )
{
RequestResult result = downloadClient . MakeRequest ( $"https://manhuaplus.org/manga/{manga.MangaId}" , RequestType . Default ) ;
if ( ( int ) result . statusCode < 200 | | ( int ) result . statusCode > = 300 | | result . htmlDocument is null )
{
return Array . Empty < Chapter > ( ) ;
}
HtmlNodeCollection chapterNodes = result . htmlDocument . DocumentNode . SelectNodes ( "//li[contains(concat(' ',normalize-space(@class),' '),' chapter ')]//a" ) ;
string [ ] urls = chapterNodes . Select ( node = > node . GetAttributeValue ( "href" , "" ) ) . ToArray ( ) ;
Regex urlRex = new ( @".*\/chapter-([0-9\-]+).*" ) ;
List < Chapter > chapters = new ( ) ;
foreach ( string url in urls )
{
Match rexMatch = urlRex . Match ( url ) ;
2025-01-25 11:57:54 +01:00
string chapterNumber = new ( rexMatch . Groups [ 1 ] . Value ) ;
2024-12-14 21:53:29 +01:00
string fullUrl = url ;
try
{
chapters . Add ( new Chapter ( manga , fullUrl , chapterNumber , null , null ) ) ;
}
catch ( Exception e )
{
}
}
//Return Chapters ordered by Chapter-Number
return chapters . Order ( ) . ToArray ( ) ;
}
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 [ ] images = document . DocumentNode . SelectNodes ( "//a[contains(concat(' ',normalize-space(@class),' '),' readImg ')]/img" ) . ToArray ( ) ;
List < string > urls = images . Select ( node = > node . GetAttributeValue ( "src" , "" ) ) . ToList ( ) ;
return urls . ToArray ( ) ;
}
}