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 | ||||
| 
 | ||||
| import generator.ThemeSourceData.MultiLang | ||||
| import generator.ThemeSourceData.SingleLang | ||||
| import generator.ThemeSourceGenerator | ||||
| 
 | ||||
| @ -13,12 +12,10 @@ class FMReaderGenerator : ThemeSourceGenerator { | ||||
|     override val baseVersionCode: Int = 8 | ||||
| 
 | ||||
|     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("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4), | ||||
|         SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 2), | ||||
|         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("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5), | ||||
|         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("ManhuaUS", "https://manhuaus.com", "en", overrideVersionCode = 5), | ||||
|         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-raw", "https://manhwa-raw.com", "all", isNsfw = true, className = "ManhwaDashRaw", overrideVersionCode = 1), | ||||
|         SingleLang("Manhwa18.app", "https://manhwa18.app", "en", isNsfw = true, className = "Manhwa18app"), | ||||
|  | ||||
| @ -29,6 +29,19 @@ abstract class MyMangaCMS( | ||||
|     override val lang: String, | ||||
| ) : 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 client = network.cloudflareClient.newBuilder().apply { | ||||
| @ -184,20 +197,23 @@ abstract class MyMangaCMS( | ||||
|         ) | ||||
|         title = document.select(".series-name").first()!!.text().trim() | ||||
| 
 | ||||
|         var alternativeNames: String? = null | ||||
|         var alternativeNames: String = "" | ||||
|         document.select(".info-item").forEach { | ||||
|             val value = it.select(".info-value") | ||||
|             when (it.select(".info-name").text().trim()) { | ||||
|                 "Tên khác:" -> alternativeNames = value.joinToString(", ") { name -> | ||||
|                     name.text().trim() | ||||
|                 "$parseAlternativeNameString:" -> alternativeNames += value.joinToString(", ") { name -> | ||||
|                     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() | ||||
|                 } | ||||
|                 "Tình trạng:" -> status = when (value.first()!!.text().lowercase().trim()) { | ||||
|                     "đang tiến hành" -> SManga.ONGOING | ||||
|                     "tạm ngưng" -> SManga.ON_HIATUS | ||||
|                     "đã hoàn thành" -> SManga.COMPLETED | ||||
|                 "$parseStatusString:" -> status = when (value.first()!!.text().lowercase().trim()) { | ||||
|                     parseStatusOngoingStringLowerCase -> SManga.ONGOING | ||||
|                     parseStatusOnHoldStringLowerCase -> SManga.ON_HIATUS | ||||
|                     parseStatusCompletedStringLowerCase -> SManga.COMPLETED | ||||
|                     else -> SManga.UNKNOWN | ||||
|                 } | ||||
|             } | ||||
| @ -217,8 +233,8 @@ abstract class MyMangaCMS( | ||||
|             descElem.text().trim() | ||||
|         } | ||||
| 
 | ||||
|         if (!alternativeNames.isNullOrEmpty()) { | ||||
|             description = "Tên khác: ${alternativeNames}\n\n" + description | ||||
|         if (alternativeNames.isNotEmpty()) { | ||||
|             description = "$parseAlternativeNameString: ${alternativeNames}\n\n" + description | ||||
|         } | ||||
| 
 | ||||
|         genre = document.select("a[href*=the-loai] span.badge") | ||||
| @ -230,6 +246,14 @@ abstract class MyMangaCMS( | ||||
|             .attr("style") | ||||
|             .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 | ||||
| 
 | ||||
|     //region Chapter list | ||||
| @ -294,30 +318,44 @@ abstract class MyMangaCMS( | ||||
|     ) : Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), state) { | ||||
|         fun toUriPart() = vals[state].second | ||||
|     } | ||||
|     private class Status : Filter.Select<String>( | ||||
|         "Tình trạng", | ||||
|     protected class Status( | ||||
|         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( | ||||
|             "Tất cả", | ||||
|             "Đang tiến hành", | ||||
|             "Tạm ngưng", | ||||
|             "Hoàn thành", | ||||
|             statusAll, | ||||
|             statusOngoing, | ||||
|             statusOnHold, | ||||
|             statusCompleted, | ||||
|         ), | ||||
|     ) | ||||
|     private class Sort : UriPartFilter( | ||||
|         "Sắp xếp", | ||||
|     protected class Sort( | ||||
|         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( | ||||
|             Pair("A-Z", "az"), | ||||
|             Pair("Z-A", "za"), | ||||
|             Pair("Mới cập nhật", "update"), | ||||
|             Pair("Truyện mới", "new"), | ||||
|             Pair("Xem nhiều", "top"), | ||||
|             Pair("Được thích nhiều", "like"), | ||||
|             Pair(sortAZ, "az"), | ||||
|             Pair(sortZA, "za"), | ||||
|             Pair(sortUpdate, "update"), | ||||
|             Pair(sortNew, "new"), | ||||
|             Pair(sortPopular, "top"), | ||||
|             Pair(sortLike, "like"), | ||||
|         ), | ||||
|         4, | ||||
|     ) | ||||
|     open class Genre(name: String, val id: Int) : Filter.TriState(name) | ||||
|     private class Author : Filter.Text("Tác giả") | ||||
|     private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Thể loại", genres) | ||||
|     protected class Author(displayName: String = "Tác giả") : Filter.Text(displayName) | ||||
|     protected class GenreList(genres: List<Genre>, displayName: String = "Thể loại") : Filter.Group<Genre>(displayName, genres) | ||||
| 
 | ||||
|     override fun getFilterList(): FilterList = FilterList( | ||||
|         Author(), | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| package eu.kanade.tachiyomi.multisrc.mymangacms | ||||
| 
 | ||||
| import generator.ThemeSourceData.MultiLang | ||||
| import generator.ThemeSourceData.SingleLang | ||||
| import generator.ThemeSourceGenerator | ||||
| 
 | ||||
| @ -9,15 +10,31 @@ class MyMangaCMSGenerator : ThemeSourceGenerator { | ||||
| 
 | ||||
|     override val themeClass = "MyMangaCMS" | ||||
| 
 | ||||
|     override val baseVersionCode: Int = 1 | ||||
|     override val baseVersionCode: Int = 2 | ||||
| 
 | ||||
|     override val sources = listOf( | ||||
|         SingleLang( | ||||
|             "TruyenTranhLH", | ||||
|             "https://truyentranhlh.net", | ||||
|             "vi", | ||||
|             isNsfw = true, | ||||
|             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 { | ||||
|  | ||||
 Cuong M. Tran
						Cuong M. Tran