Update Manhwa18/Manhwa18.net, Manhwaraw.com (#19516)
* Fix ManyToon.me & ManyToon.club Fix issues in which both sources unable to load any mangas. Closes #16612 * Update multisrc MyMangaCMS to support other site’s langugae Also, support a second alternative name and remove generic words like “manhwa”, “engsub” from those names. Also, mark TruyenTranhLH as NSFW * Move Manhwa18 & Manhwa18.net to MyMangaCMS theme. Also fix Manhwa18.net’s searching issue with the previous theme. Also allow Manhwa18’s filter working since it was not with the previous theme. Close #18818 * Remove unused import causing PR build check to fail * MyMangaCms Bump the `versionId` so the ID will change and users will be forced to migrate to update the URLs. * fix locale friendly * Fix Manhwaraw.com won’t show Popular & Latest manhwa * revert bumping TruyenTranhLH’s version
| @ -1,90 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.en.manhwa18 |  | ||||||
| 
 |  | ||||||
| import eu.kanade.tachiyomi.multisrc.fmreader.FMReader |  | ||||||
| import eu.kanade.tachiyomi.network.GET |  | ||||||
| import eu.kanade.tachiyomi.source.model.FilterList |  | ||||||
| import eu.kanade.tachiyomi.source.model.MangasPage |  | ||||||
| import eu.kanade.tachiyomi.source.model.SChapter |  | ||||||
| import eu.kanade.tachiyomi.source.model.SManga |  | ||||||
| import eu.kanade.tachiyomi.util.asJsoup |  | ||||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull |  | ||||||
| import okhttp3.Request |  | ||||||
| import okhttp3.Response |  | ||||||
| import org.jsoup.nodes.Document |  | ||||||
| import java.text.SimpleDateFormat |  | ||||||
| import java.util.Locale |  | ||||||
| 
 |  | ||||||
| class Manhwa18 : FMReader("Manhwa18", "https://manhwa18.com", "en") { |  | ||||||
|     override val requestPath = "tim-kiem" |  | ||||||
| 
 |  | ||||||
|     override val popularSort = "sort=top" |  | ||||||
| 
 |  | ||||||
|     override fun popularMangaParse(response: Response): MangasPage { |  | ||||||
|         val document = response.asJsoup() |  | ||||||
| 
 |  | ||||||
|         val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) } |  | ||||||
| 
 |  | ||||||
|         return MangasPage(mangas, document.select(".pagination_wrap .disabled").text() != "Bottom") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         val url = "$baseUrl/$requestPath?".toHttpUrlOrNull()!!.newBuilder() |  | ||||||
|             .addQueryParameter("q", query) |  | ||||||
|             .addQueryParameter("page", page.toString()) |  | ||||||
|         return GET(url.toString(), headers) |  | ||||||
|     } |  | ||||||
|     override fun latestUpdatesRequest(page: Int): Request = |  | ||||||
|         GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=update&sort_type=DESC", headers) |  | ||||||
| 
 |  | ||||||
|     override fun mangaDetailsParse(document: Document): SManga { |  | ||||||
|         return SManga.create().apply { |  | ||||||
|             title = document.select(".series-name").text() |  | ||||||
|             thumbnail_url = document.select("meta[property='og:image']").attr("abs:content") |  | ||||||
| 
 |  | ||||||
|             document.select(".series-information")?.let { info -> |  | ||||||
|                 author = info.select(".info-name:contains(Author:) + .info-value").text() |  | ||||||
|                 genre = info.select(".info-name:contains(Genre:) + .info-value > a") |  | ||||||
|                     .joinToString { it.text().trim() } |  | ||||||
| 
 |  | ||||||
|                 description = document.select(".summary-content").text().trim() |  | ||||||
|                 info.select(".info-name:contains(Other name:) + .info-value") |  | ||||||
|                     .firstOrNull()?.text()?.let { |  | ||||||
|                         val altName = removeGenericWords(it) |  | ||||||
|                         description = when (title.lowercase(Locale.US)) { |  | ||||||
|                             altName.lowercase(Locale.US) -> description |  | ||||||
|                             else -> description + "\n\n$altName" |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 status = |  | ||||||
|                     parseStatus(info.select(".info-name:contains(Status:) + .info-value").text()) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private fun removeGenericWords(name: String): String { |  | ||||||
|         val excludeList = listOf("manhwa", "engsub") |  | ||||||
|         return name.split(' ').filterNot { word -> |  | ||||||
|             word.lowercase(Locale.US) in excludeList |  | ||||||
|         }.joinToString(" ") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun chapterListParse(response: Response): List<SChapter> { |  | ||||||
|         val document = response.asJsoup() |  | ||||||
| 
 |  | ||||||
|         return document.select(".list-chapters > a").map { element -> |  | ||||||
|             SChapter.create().apply { |  | ||||||
|                 setUrlWithoutDomain(element.attr("abs:href")) |  | ||||||
|                 name = element.attr("title") |  | ||||||
|                 date_upload = |  | ||||||
|                     SimpleDateFormat("dd/MM/yyyy", Locale.US).parse( |  | ||||||
|                         element.select(".chapter-time").text().substringAfter(" - "), |  | ||||||
|                     )?.time ?: 0L |  | ||||||
|                 chapter_number = element.attr("time").substringAfterLast(' ').toFloatOrNull() ?: -1f |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override val pageListImageSelector = "#chapter-content > img" |  | ||||||
| 
 |  | ||||||
|     override fun getFilterList() = FilterList() |  | ||||||
| } |  | ||||||
| @ -1,77 +0,0 @@ | |||||||
| package eu.kanade.tachiyomi.extension.all.manhwa18net |  | ||||||
| 
 |  | ||||||
| import eu.kanade.tachiyomi.multisrc.fmreader.FMReader |  | ||||||
| import eu.kanade.tachiyomi.network.GET |  | ||||||
| import eu.kanade.tachiyomi.source.Source |  | ||||||
| import eu.kanade.tachiyomi.source.SourceFactory |  | ||||||
| import eu.kanade.tachiyomi.source.model.FilterList |  | ||||||
| import eu.kanade.tachiyomi.source.model.SChapter |  | ||||||
| import okhttp3.Request |  | ||||||
| import org.jsoup.nodes.Element |  | ||||||
| 
 |  | ||||||
| class Manhwa18NetFactory : SourceFactory { |  | ||||||
|     override fun createSources(): List<Source> = listOf( |  | ||||||
|         Manhwa18Net(), |  | ||||||
|         Manhwa18NetRaw(), |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class Manhwa18Net : FMReader("Manhwa18.net", "https://manhwa18.net", "en") { |  | ||||||
|     override val requestPath = "genre/manhwa" |  | ||||||
|     override val popularSort = "sort=top" |  | ||||||
|     override val pageListImageSelector = "div#chapter-content > img" |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesRequest(page: Int): Request = |  | ||||||
|         GET( |  | ||||||
|             "$baseUrl/$requestPath?listType=pagination&page=$page&sort=update&sort_type=DESC", |  | ||||||
|             headers, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         val noRawsUrl = super.searchMangaRequest(page, query, filters).url.newBuilder().toString() |  | ||||||
|         return GET(noRawsUrl, headers) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun getGenreList() = getAdultGenreList() |  | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element, mangaTitle: String): SChapter { |  | ||||||
|         return SChapter.create().apply { |  | ||||||
|             setUrlWithoutDomain(element.attr("abs:href")) |  | ||||||
|             name = element.attr("title") |  | ||||||
|             date_upload = parseAbsoluteDate( |  | ||||||
|                 element.select(chapterTimeSelector).text().substringAfter(" - "), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class Manhwa18NetRaw : FMReader("Manhwa18.net", "https://manhwa18.net", "ko") { |  | ||||||
|     override val requestPath = "genre/raw" |  | ||||||
|     override val popularSort = "sort=top" |  | ||||||
|     override val pageListImageSelector = "div#chapter-content > img" |  | ||||||
| 
 |  | ||||||
|     override fun latestUpdatesRequest(page: Int): Request = |  | ||||||
|         GET( |  | ||||||
|             "$baseUrl/$requestPath?listType=pagination&page=$page&sort=update&sort_type=DESC", |  | ||||||
|             headers, |  | ||||||
|         ) |  | ||||||
| 
 |  | ||||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { |  | ||||||
|         val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url.newBuilder().toString() |  | ||||||
|         return GET(onlyRawsUrl, headers) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun getFilterList() = FilterList( |  | ||||||
|         super.getFilterList().filterNot { it == GenreList(getGenreList()) }, |  | ||||||
|     ) |  | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element, mangaTitle: String): SChapter { |  | ||||||
|         return SChapter.create().apply { |  | ||||||
|             setUrlWithoutDomain(element.attr("abs:href")) |  | ||||||
|             name = element.attr("title") |  | ||||||
|             date_upload = parseAbsoluteDate( |  | ||||||
|                 element.select(chapterTimeSelector).text().substringAfter(" - "), |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										11
									
								
								multisrc/overrides/madara/manhwaraw/src/ManhwaRaw.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,11 @@ | |||||||
|  | package eu.kanade.tachiyomi.extension.ko.manhwaraw | ||||||
|  | 
 | ||||||
|  | import eu.kanade.tachiyomi.multisrc.madara.Madara | ||||||
|  | 
 | ||||||
|  | class ManhwaRaw : Madara("ManhwaRaw", "https://manhwaraw.com", "ko") { | ||||||
|  | 
 | ||||||
|  |     override val mangaSubString = "manhwa-raw" | ||||||
|  | 
 | ||||||
|  |     // The website does not flag the content. | ||||||
|  |     override val filterNonMangaItems = false | ||||||
|  | } | ||||||
| Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB | 
| Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB | 
| Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB | 
| Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 212 KiB | 
							
								
								
									
										62
									
								
								multisrc/overrides/mymangacms/manhwa18/src/Manhwa18.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,62 @@ | |||||||
|  | package eu.kanade.tachiyomi.extension.en.manhwa18 | ||||||
|  | 
 | ||||||
|  | import eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMS | ||||||
|  | import eu.kanade.tachiyomi.source.model.FilterList | ||||||
|  | 
 | ||||||
|  | class Manhwa18 : MyMangaCMS("Manhwa18", "https://manhwa18.com", "en") { | ||||||
|  | 
 | ||||||
|  |     // Migrated from FMReader to MyMangaCMS. | ||||||
|  |     override val versionId = 2 | ||||||
|  | 
 | ||||||
|  |     override val parseAuthorString = "Author" | ||||||
|  |     override val parseAlternativeNameString = "Other name" | ||||||
|  |     override val parseAlternative2ndNameString = "Doujinshi" | ||||||
|  |     override val parseStatusString = "Status" | ||||||
|  |     override val parseStatusOngoingStringLowerCase = "on going" | ||||||
|  |     override val parseStatusOnHoldStringLowerCase = "on hold" | ||||||
|  |     override val parseStatusCompletedStringLowerCase = "completed" | ||||||
|  | 
 | ||||||
|  |     override fun getFilterList(): FilterList = FilterList( | ||||||
|  |         Author("Author"), | ||||||
|  |         Status( | ||||||
|  |             "Status", | ||||||
|  |             "All", | ||||||
|  |             "Ongoing", | ||||||
|  |             "On hold", | ||||||
|  |             "Completed", | ||||||
|  |         ), | ||||||
|  |         Sort( | ||||||
|  |             "Order", | ||||||
|  |             "A-Z", | ||||||
|  |             "Z-A", | ||||||
|  |             "Latest update", | ||||||
|  |             "New manhwa", | ||||||
|  |             "Most view", | ||||||
|  |             "Most like", | ||||||
|  |         ), | ||||||
|  |         GenreList(getGenreList(), "Genre"), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // To populate this list: | ||||||
|  |     // console.log([...document.querySelectorAll("div.search-gerne_item")].map(elem => `Genre("${elem.textContent.trim()}", ${elem.querySelector("label").getAttribute("data-genre-id")}),`).join("\n")) | ||||||
|  |     override fun getGenreList() = listOf( | ||||||
|  |         Genre("Adult", 4), | ||||||
|  |         Genre("Doujinshi", 9), | ||||||
|  |         Genre("Harem", 17), | ||||||
|  |         Genre("Manga", 24), | ||||||
|  |         Genre("Manhwa", 26), | ||||||
|  |         Genre("Mature", 28), | ||||||
|  |         Genre("NTR", 33), | ||||||
|  |         Genre("Romance", 36), | ||||||
|  |         Genre("Webtoon", 57), | ||||||
|  |         Genre("Action", 59), | ||||||
|  |         Genre("Comedy", 60), | ||||||
|  |         Genre("BL", 61), | ||||||
|  |         Genre("Horror", 62), | ||||||
|  |         Genre("Raw", 63), | ||||||
|  |         Genre("Uncensore", 64), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     override fun dateUpdatedParser(date: String): Long = | ||||||
|  |         runCatching { dateFormatter.parse(date.substringAfter(" - "))?.time }.getOrNull() ?: 0L | ||||||
|  | } | ||||||
| Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB | 
| Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB | 
| Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB | 
| Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 212 KiB | 
							
								
								
									
										62
									
								
								multisrc/overrides/mymangacms/manhwa18net/src/Manhwa18Net.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,62 @@ | |||||||
|  | package eu.kanade.tachiyomi.extension.all.manhwa18net | ||||||
|  | 
 | ||||||
|  | import eu.kanade.tachiyomi.multisrc.mymangacms.MyMangaCMS | ||||||
|  | import eu.kanade.tachiyomi.source.model.FilterList | ||||||
|  | 
 | ||||||
|  | class Manhwa18Net : MyMangaCMS("Manhwa18.net", "https://manhwa18.net", "en") { | ||||||
|  | 
 | ||||||
|  |     // Migrated from FMReader to MyMangaCms. | ||||||
|  |     override val versionId = 2 | ||||||
|  | 
 | ||||||
|  |     override val parseAuthorString = "Author" | ||||||
|  |     override val parseAlternativeNameString = "Other name" | ||||||
|  |     override val parseAlternative2ndNameString = "Doujinshi" | ||||||
|  |     override val parseStatusString = "Status" | ||||||
|  |     override val parseStatusOngoingStringLowerCase = "on going" | ||||||
|  |     override val parseStatusOnHoldStringLowerCase = "on hold" | ||||||
|  |     override val parseStatusCompletedStringLowerCase = "completed" | ||||||
|  | 
 | ||||||
|  |     override fun getFilterList(): FilterList = FilterList( | ||||||
|  |         Author("Author"), | ||||||
|  |         Status( | ||||||
|  |             "Status", | ||||||
|  |             "All", | ||||||
|  |             "Ongoing", | ||||||
|  |             "On hold", | ||||||
|  |             "Completed", | ||||||
|  |         ), | ||||||
|  |         Sort( | ||||||
|  |             "Order", | ||||||
|  |             "A-Z", | ||||||
|  |             "Z-A", | ||||||
|  |             "Latest update", | ||||||
|  |             "New manhwa", | ||||||
|  |             "Most view", | ||||||
|  |             "Most like", | ||||||
|  |         ), | ||||||
|  |         GenreList(getGenreList(), "Genre"), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // To populate this list: | ||||||
|  |     // console.log([...document.querySelectorAll("div.search-gerne_item")].map(elem => `Genre("${elem.textContent.trim()}", ${elem.querySelector("label").getAttribute("data-genre-id")}),`).join("\n")) | ||||||
|  |     override fun getGenreList() = listOf( | ||||||
|  |         Genre("Adult", 4), | ||||||
|  |         Genre("Doujinshi", 9), | ||||||
|  |         Genre("Harem", 17), | ||||||
|  |         Genre("Manga", 24), | ||||||
|  |         Genre("Manhwa", 26), | ||||||
|  |         Genre("Mature", 28), | ||||||
|  |         Genre("NTR", 33), | ||||||
|  |         Genre("Romance", 36), | ||||||
|  |         Genre("Webtoon", 57), | ||||||
|  |         Genre("Action", 59), | ||||||
|  |         Genre("Comedy", 60), | ||||||
|  |         Genre("BL", 61), | ||||||
|  |         Genre("Horror", 62), | ||||||
|  |         Genre("Raw", 63), | ||||||
|  |         Genre("Uncensore", 64), | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     override fun dateUpdatedParser(date: String): Long = | ||||||
|  |         runCatching { dateFormatter.parse(date.substringAfter(" - "))?.time }.getOrNull() ?: 0L | ||||||
|  | } | ||||||
| @ -1,6 +1,5 @@ | |||||||
| package eu.kanade.tachiyomi.multisrc.fmreader | package eu.kanade.tachiyomi.multisrc.fmreader | ||||||
| 
 | 
 | ||||||
| import generator.ThemeSourceData.MultiLang |  | ||||||
| import generator.ThemeSourceData.SingleLang | import generator.ThemeSourceData.SingleLang | ||||||
| import generator.ThemeSourceGenerator | import generator.ThemeSourceGenerator | ||||||
| 
 | 
 | ||||||
| @ -13,12 +12,10 @@ class FMReaderGenerator : ThemeSourceGenerator { | |||||||
|     override val baseVersionCode: Int = 8 |     override val baseVersionCode: Int = 8 | ||||||
| 
 | 
 | ||||||
|     override val sources = listOf( |     override val sources = listOf( | ||||||
|         MultiLang("Manhwa18.net", "https://manhwa18.net", listOf("en", "ko"), className = "Manhwa18NetFactory", isNsfw = true, overrideVersionCode = 1), |  | ||||||
|         SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"), |         SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"), | ||||||
|         SingleLang("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4), |         SingleLang("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4), | ||||||
|         SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 2), |         SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 2), | ||||||
|         SingleLang("ManhuaRock", "https://manhuarock.net", "vi", overrideVersionCode = 1), |         SingleLang("ManhuaRock", "https://manhuarock.net", "vi", overrideVersionCode = 1), | ||||||
|         SingleLang("Manhwa18", "https://manhwa18.com", "en", isNsfw = true, overrideVersionCode = 2), |  | ||||||
|         SingleLang("Say Truyen", "https://saytruyenvip.com", "vi", overrideVersionCode = 3), |         SingleLang("Say Truyen", "https://saytruyenvip.com", "vi", overrideVersionCode = 3), | ||||||
|         SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5), |         SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5), | ||||||
|         SingleLang("Manga1000", "https://manga1000.top", "ja"), |         SingleLang("Manga1000", "https://manga1000.top", "ja"), | ||||||
|  | |||||||
| @ -329,7 +329,7 @@ class MadaraGenerator : ThemeSourceGenerator { | |||||||
|         SingleLang("ManhuaScan.info (unoriginal)", "https://manhuascan.info", "en", isNsfw = true, className = "ManhuaScanInfo"), |         SingleLang("ManhuaScan.info (unoriginal)", "https://manhuascan.info", "en", isNsfw = true, className = "ManhuaScanInfo"), | ||||||
|         SingleLang("ManhuaUS", "https://manhuaus.com", "en", overrideVersionCode = 5), |         SingleLang("ManhuaUS", "https://manhuaus.com", "en", overrideVersionCode = 5), | ||||||
|         SingleLang("ManhuaZone", "https://manhuazone.org", "en", overrideVersionCode = 1), |         SingleLang("ManhuaZone", "https://manhuazone.org", "en", overrideVersionCode = 1), | ||||||
|         SingleLang("Manhwa Raw", "https://manhwaraw.com", "ko", isNsfw = true, overrideVersionCode = 1), |         SingleLang("Manhwa Raw", "https://manhwaraw.com", "ko", isNsfw = true, overrideVersionCode = 2), | ||||||
|         SingleLang("Manhwa-Latino", "https://manhwa-latino.com", "es", isNsfw = true, className = "ManhwaLatino", overrideVersionCode = 7), |         SingleLang("Manhwa-Latino", "https://manhwa-latino.com", "es", isNsfw = true, className = "ManhwaLatino", overrideVersionCode = 7), | ||||||
|         SingleLang("Manhwa-raw", "https://manhwa-raw.com", "all", isNsfw = true, className = "ManhwaDashRaw", overrideVersionCode = 1), |         SingleLang("Manhwa-raw", "https://manhwa-raw.com", "all", isNsfw = true, className = "ManhwaDashRaw", overrideVersionCode = 1), | ||||||
|         SingleLang("Manhwa18.app", "https://manhwa18.app", "en", isNsfw = true, className = "Manhwa18app"), |         SingleLang("Manhwa18.app", "https://manhwa18.app", "en", isNsfw = true, className = "Manhwa18app"), | ||||||
|  | |||||||
| @ -29,6 +29,19 @@ abstract class MyMangaCMS( | |||||||
|     override val lang: String, |     override val lang: String, | ||||||
| ) : ParsedHttpSource() { | ) : ParsedHttpSource() { | ||||||
| 
 | 
 | ||||||
|  |     protected open val parseAuthorString = "Tác giả" | ||||||
|  |     protected open val parseAlternativeNameString = "Tên khác" | ||||||
|  |     protected open val parseAlternative2ndNameString = "Tên gốc" | ||||||
|  |     protected open val parseStatusString = "Tình trạng" | ||||||
|  |     protected open val parseStatusOngoingStringLowerCase = "đang tiến hành" | ||||||
|  |     protected open val parseStatusOnHoldStringLowerCase = "tạm ngưng" | ||||||
|  |     protected open val parseStatusCompletedStringLowerCase = "đã hoàn thành" | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * List of words to be removed when parsing alternative names | ||||||
|  |      */ | ||||||
|  |     protected open val removeGenericWords = listOf("manhwa", "engsub") | ||||||
|  | 
 | ||||||
|     override val supportsLatest = true |     override val supportsLatest = true | ||||||
| 
 | 
 | ||||||
|     override val client = network.cloudflareClient.newBuilder().apply { |     override val client = network.cloudflareClient.newBuilder().apply { | ||||||
| @ -184,20 +197,23 @@ abstract class MyMangaCMS( | |||||||
|         ) |         ) | ||||||
|         title = document.select(".series-name").first()!!.text().trim() |         title = document.select(".series-name").first()!!.text().trim() | ||||||
| 
 | 
 | ||||||
|         var alternativeNames: String? = null |         var alternativeNames: String = "" | ||||||
|         document.select(".info-item").forEach { |         document.select(".info-item").forEach { | ||||||
|             val value = it.select(".info-value") |             val value = it.select(".info-value") | ||||||
|             when (it.select(".info-name").text().trim()) { |             when (it.select(".info-name").text().trim()) { | ||||||
|                 "Tên khác:" -> alternativeNames = value.joinToString(", ") { name -> |                 "$parseAlternativeNameString:" -> alternativeNames += value.joinToString(", ") { name -> | ||||||
|                     name.text().trim() |                     removeGenericWords(name.text()).trim() + ", " | ||||||
|                 } |                 } | ||||||
|                 "Tác giả:" -> author = value.joinToString(", ") { auth -> |                 "$parseAlternative2ndNameString:" -> alternativeNames += value.joinToString(", ") { name -> | ||||||
|  |                     removeGenericWords(name.text()).trim() + ", " | ||||||
|  |                 } | ||||||
|  |                 "$parseAuthorString:" -> author = value.joinToString(", ") { auth -> | ||||||
|                     auth.text().trim() |                     auth.text().trim() | ||||||
|                 } |                 } | ||||||
|                 "Tình trạng:" -> status = when (value.first()!!.text().lowercase().trim()) { |                 "$parseStatusString:" -> status = when (value.first()!!.text().lowercase().trim()) { | ||||||
|                     "đang tiến hành" -> SManga.ONGOING |                     parseStatusOngoingStringLowerCase -> SManga.ONGOING | ||||||
|                     "tạm ngưng" -> SManga.ON_HIATUS |                     parseStatusOnHoldStringLowerCase -> SManga.ON_HIATUS | ||||||
|                     "đã hoàn thành" -> SManga.COMPLETED |                     parseStatusCompletedStringLowerCase -> SManga.COMPLETED | ||||||
|                     else -> SManga.UNKNOWN |                     else -> SManga.UNKNOWN | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -217,8 +233,8 @@ abstract class MyMangaCMS( | |||||||
|             descElem.text().trim() |             descElem.text().trim() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!alternativeNames.isNullOrEmpty()) { |         if (alternativeNames.isNotEmpty()) { | ||||||
|             description = "Tên khác: ${alternativeNames}\n\n" + description |             description = "$parseAlternativeNameString: ${alternativeNames}\n\n" + description | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         genre = document.select("a[href*=the-loai] span.badge") |         genre = document.select("a[href*=the-loai] span.badge") | ||||||
| @ -230,6 +246,14 @@ abstract class MyMangaCMS( | |||||||
|             .attr("style") |             .attr("style") | ||||||
|             .let { backgroundImageRegex.find(it)?.groups?.get(1)?.value } |             .let { backgroundImageRegex.find(it)?.groups?.get(1)?.value } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     private fun removeGenericWords(name: String): String { | ||||||
|  |         val locale = Locale.forLanguageTag(lang) | ||||||
|  | 
 | ||||||
|  |         return name.split(' ') | ||||||
|  |             .filterNot { word -> word.lowercase(locale) in removeGenericWords } | ||||||
|  |             .joinToString(" ") | ||||||
|  |     } | ||||||
|     //endregion |     //endregion | ||||||
| 
 | 
 | ||||||
|     //region Chapter list |     //region Chapter list | ||||||
| @ -294,30 +318,44 @@ abstract class MyMangaCMS( | |||||||
|     ) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) { |     ) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) { | ||||||
|         fun toUriPart() = vals[state].second |         fun toUriPart() = vals[state].second | ||||||
|     } |     } | ||||||
|     private class Status : Filter.Select<String>( |     protected class Status( | ||||||
|         "Tình trạng", |         displayName: String = "Tình trạng", | ||||||
|  |         statusAll: String = "Tất cả", | ||||||
|  |         statusOngoing: String = "Đang tiến hành", | ||||||
|  |         statusOnHold: String = "Tạm ngưng", | ||||||
|  |         statusCompleted: String = "Hoàn thành", | ||||||
|  |     ) : Filter.Select<String>( | ||||||
|  |         displayName, | ||||||
|         arrayOf( |         arrayOf( | ||||||
|             "Tất cả", |             statusAll, | ||||||
|             "Đang tiến hành", |             statusOngoing, | ||||||
|             "Tạm ngưng", |             statusOnHold, | ||||||
|             "Hoàn thành", |             statusCompleted, | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     private class Sort : UriPartFilter( |     protected class Sort( | ||||||
|         "Sắp xếp", |         displayName: String = "Sắp xếp", | ||||||
|  |         sortAZ: String = "A-Z", | ||||||
|  |         sortZA: String = "Z-A", | ||||||
|  |         sortUpdate: String = "Mới cập nhật", | ||||||
|  |         sortNew: String = "Truyện mới", | ||||||
|  |         sortPopular: String = "Xem nhiều", | ||||||
|  |         sortLike: String = "Được thích nhiều", | ||||||
|  |     ) : UriPartFilter( | ||||||
|  |         displayName, | ||||||
|         arrayOf( |         arrayOf( | ||||||
|             Pair("A-Z", "az"), |             Pair(sortAZ, "az"), | ||||||
|             Pair("Z-A", "za"), |             Pair(sortZA, "za"), | ||||||
|             Pair("Mới cập nhật", "update"), |             Pair(sortUpdate, "update"), | ||||||
|             Pair("Truyện mới", "new"), |             Pair(sortNew, "new"), | ||||||
|             Pair("Xem nhiều", "top"), |             Pair(sortPopular, "top"), | ||||||
|             Pair("Được thích nhiều", "like"), |             Pair(sortLike, "like"), | ||||||
|         ), |         ), | ||||||
|         4, |         4, | ||||||
|     ) |     ) | ||||||
|     open class Genre(name: String, val id: Int) : Filter.TriState(name) |     open class Genre(name: String, val id: Int) : Filter.TriState(name) | ||||||
|     private class Author : Filter.Text("Tác giả") |     protected class Author(displayName: String = "Tác giả") : Filter.Text(displayName) | ||||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) |     protected class GenreList(genres: List<Genre>, displayName: String = "Thể loại") : Filter.Group<Genre>(displayName, genres) | ||||||
| 
 | 
 | ||||||
|     override fun getFilterList(): FilterList = FilterList( |     override fun getFilterList(): FilterList = FilterList( | ||||||
|         Author(), |         Author(), | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| package eu.kanade.tachiyomi.multisrc.mymangacms | package eu.kanade.tachiyomi.multisrc.mymangacms | ||||||
| 
 | 
 | ||||||
|  | import generator.ThemeSourceData.MultiLang | ||||||
| import generator.ThemeSourceData.SingleLang | import generator.ThemeSourceData.SingleLang | ||||||
| import generator.ThemeSourceGenerator | import generator.ThemeSourceGenerator | ||||||
| 
 | 
 | ||||||
| @ -9,15 +10,31 @@ class MyMangaCMSGenerator : ThemeSourceGenerator { | |||||||
| 
 | 
 | ||||||
|     override val themeClass = "MyMangaCMS" |     override val themeClass = "MyMangaCMS" | ||||||
| 
 | 
 | ||||||
|     override val baseVersionCode: Int = 1 |     override val baseVersionCode: Int = 2 | ||||||
| 
 | 
 | ||||||
|     override val sources = listOf( |     override val sources = listOf( | ||||||
|         SingleLang( |         SingleLang( | ||||||
|             "TruyenTranhLH", |             "TruyenTranhLH", | ||||||
|             "https://truyentranhlh.net", |             "https://truyentranhlh.net", | ||||||
|             "vi", |             "vi", | ||||||
|  |             isNsfw = true, | ||||||
|             overrideVersionCode = 9, |             overrideVersionCode = 9, | ||||||
|         ), |         ), | ||||||
|  |         SingleLang( | ||||||
|  |             "Manhwa18", | ||||||
|  |             "https://manhwa18.com", | ||||||
|  |             "en", | ||||||
|  |             isNsfw = true, | ||||||
|  |             overrideVersionCode = 9, | ||||||
|  |         ), | ||||||
|  |         MultiLang( | ||||||
|  |             "Manhwa18.net", | ||||||
|  |             "https://manhwa18.net", | ||||||
|  |             listOf("en"), | ||||||
|  |             className = "Manhwa18Net", | ||||||
|  |             isNsfw = true, | ||||||
|  |             overrideVersionCode = 8, | ||||||
|  |         ), | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     companion object { |     companion object { | ||||||
|  | |||||||
 Cuong M. Tran
						Cuong M. Tran