WPComics update & add new sources (#1909)

* remove non-relevant query

* WPComics: query for genres instead of hard-code

* language assets to support dual-lang

* update XoxoComics, Nhattruyen, Nettruyen to support updated WPComics

* remove unused status

* JManga with new WPComics

* Fix JManga NextPageSelector

* Allow override some more methods

* correct jmanga's location

* remove redundant XoxoComics override

* Get alternative name and JManga's description

* add sources:
- NetTruyenX
- NhatTruyenS
- NetTruyenCO

* revert format changes

* Update NetTruyen to latest domain

* Minor changes:

- Named parameters;
- intl, lazy;

* Remove NetTruyen’s replaceSearchPath. It’s not necessary

* remove the japanese’s mtl

* remove hard-code user-agent

* remove some unnecessary named parameters

* Use super.headersBuilder & fix Referer

* remove redundant import
This commit is contained in:
Cuong M. Tran 2024-03-19 23:06:03 +07:00 committed by Draff
parent 455f57d209
commit a0fa7fa458
30 changed files with 330 additions and 324 deletions

View File

@ -0,0 +1,7 @@
STATUS=Status
STATUS_ALL=All
STATUS_ONGOING=Ongoing
STATUS_COMPLETED=Completed
GENRE=Genre
GENRES_RESET=Tap 'Reset' to load genres
OTHER_NAME=Alternate Name

View File

@ -0,0 +1,5 @@
STATUS=状態
STATUS_ALL=全て
STATUS_ONGOING=連載中
STATUS_COMPLETED=完結済み
GENRE=ジャンル

View File

@ -0,0 +1,7 @@
STATUS=Trạng thái
STATUS_ALL=Tất cả
STATUS_ONGOING=Đang tiến hành
STATUS_COMPLETED=Hoàn thành
GENRE=Thể loại
GENRES_RESET=Ấn 'Reset' để tải danh sách thể loại
OTHER_NAME=Tên khác

View File

@ -2,4 +2,8 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 4
baseVersionCode = 5
dependencies {
api(project(":lib:i18n"))
}

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.multisrc.wpcomics
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
@ -7,7 +8,10 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.Headers
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
@ -20,23 +24,28 @@ import java.util.Locale
abstract class WPComics(
override val name: String,
override val baseUrl: String,
override val lang: String,
private val dateFormat: SimpleDateFormat = SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US),
private val gmtOffset: String? = "+0500",
final override val lang: String,
protected val dateFormat: SimpleDateFormat = SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US),
protected val gmtOffset: String? = "+0500",
) : ParsedHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0")
.add("Referer", baseUrl)
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
private fun List<String>.doesInclude(thisWord: String): Boolean = this.any { it.contains(thisWord, ignoreCase = true) }
open val intl = Intl(
language = lang,
baseLanguage = "en",
availableLanguages = setOf("en", "vi", "ja"),
classLoader = this::class.java.classLoader!!,
)
protected fun List<String>.doesInclude(thisWord: String): Boolean = this.any { it.contains(thisWord, ignoreCase = true) }
// Popular
open val popularPath = "hot"
override fun popularMangaRequest(page: Int): Request {
@ -58,7 +67,6 @@ abstract class WPComics(
override fun popularMangaNextPageSelector() = "a.next-page, a[rel=next]"
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET(baseUrl + if (page > 1) "?page=$page" else "", headers)
}
@ -70,35 +78,27 @@ abstract class WPComics(
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// Search
protected open val searchPath = "tim-truyen"
protected open val queryParam = "keyword"
protected open fun String.replaceSearchPath() = this
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = filters.let { if (it.isEmpty()) getFilterList() else it }
return if (filterList.isEmpty()) {
GET("$baseUrl/?s=$query&post_type=comics&page=$page")
} else {
val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder()
val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder()
filterList.forEach { filter ->
when (filter) {
is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) }
is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) }
else -> {}
}
filters.forEach { filter ->
when (filter) {
is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) }
is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) }
else -> {}
}
url.apply {
addQueryParameter(queryParam, query)
addQueryParameter("page", page.toString())
addQueryParameter("sort", "0")
}
GET(url.toString().replaceSearchPath(), headers)
}
url.apply {
addQueryParameter(queryParam, query)
addQueryParameter("page", page.toString())
addQueryParameter("sort", "0")
}
return GET(url.toString(), headers)
}
override fun searchMangaSelector() = "div.items div.item"
@ -116,22 +116,23 @@ abstract class WPComics(
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
description = info.select("div.detail-content p").text()
val otherName = info.select("h2.other-name").text()
description = info.select("div.detail-content p").text() +
if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else ""
thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!)
}
}
}
open fun String?.toStatus(): Int {
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành")
val completedWords = listOf("Complete", "Completed", "Hoàn thành")
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành", "連載中")
val completedWords = listOf("Complete", "Completed", "Hoàn thành", "完結済み")
return when {
this == null -> SManga.UNKNOWN
ongoingWords.doesInclude(this) -> SManga.ONGOING
@ -141,7 +142,6 @@ abstract class WPComics(
}
// Chapters
override fun chapterListSelector() = "div.list-chapter li.row:not(.heading)"
override fun chapterFromElement(element: Element): SChapter {
@ -154,10 +154,10 @@ abstract class WPComics(
}
}
private val currentYear by lazy { Calendar.getInstance(Locale.US)[1].toString().takeLast(2) }
protected val currentYear by lazy { Calendar.getInstance(Locale.US)[1].toString().takeLast(2) }
protected fun String?.toDate(): Long {
this ?: return 0
protected open fun String?.toDate(): Long {
this ?: return 0L
val secondWords = listOf("second", "giây")
val minuteWords = listOf("minute", "phút")
@ -182,10 +182,10 @@ abstract class WPComics(
(if (gmtOffset == null) this.substringAfterLast(" ") else "$this $gmtOffset").let {
// timestamp has year
if (Regex("""\d+/\d+/\d\d""").find(it)?.value != null) {
dateFormat.parse(it)?.time ?: 0
dateFormat.parse(it)?.time ?: 0L
} else {
// MangaSum - timestamp sometimes doesn't have year (current year implied)
dateFormat.parse("$it/$currentYear")?.time ?: 0
dateFormat.parse("$it/$currentYear")?.time ?: 0L
}
}
}
@ -195,9 +195,8 @@ abstract class WPComics(
}
// Pages
// sources sometimes have an image element with an empty attr that isn't really an image
open fun imageOrNull(element: Element): String? {
// sources sometimes have an image element with an empty attr that isn't really an image
fun Element.hasValidAttr(attr: String): Boolean {
val regex = Regex("""https?://.*""", RegexOption.IGNORE_CASE)
return when {
@ -226,80 +225,74 @@ abstract class WPComics(
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
// Filters
protected class StatusFilter(name: String, pairs: List<Pair<String?, String>>) : UriPartFilter(name, pairs)
protected class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals)
protected class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Genre", vals)
protected class GenreFilter(name: String, pairs: List<Pair<String?, String>>) : UriPartFilter(name, pairs)
protected open fun getStatusList(): Array<Pair<String?, String>> = arrayOf(
Pair(null, "Tất cả"),
Pair("1", "Đang tiến hành"),
Pair("2", "Đã hoàn thành"),
Pair("3", "Tạm ngừng"),
)
protected open fun getGenreList(): Array<Pair<String?, String>> = arrayOf(
null to "Tất cả",
"action" to "Action",
"adult" to "Adult",
"adventure" to "Adventure",
"anime" to "Anime",
"chuyen-sinh" to "Chuyển Sinh",
"comedy" to "Comedy",
"comic" to "Comic",
"cooking" to "Cooking",
"co-dai" to "Cổ Đại",
"doujinshi" to "Doujinshi",
"drama" to "Drama",
"dam-my" to "Đam Mỹ",
"ecchi" to "Ecchi",
"fantasy" to "Fantasy",
"gender-bender" to "Gender Bender",
"harem" to "Harem",
"historical" to "Historical",
"horror" to "Horror",
"josei" to "Josei",
"live-action" to "Live action",
"manga" to "Manga",
"manhua" to "Manhua",
"manhwa" to "Manhwa",
"martial-arts" to "Martial Arts",
"mature" to "Mature",
"mecha" to "Mecha",
"mystery" to "Mystery",
"ngon-tinh" to "Ngôn Tình",
"one-shot" to "One shot",
"psychological" to "Psychological",
"romance" to "Romance",
"school-life" to "School Life",
"sci-fi" to "Sci-fi",
"seinen" to "Seinen",
"shoujo" to "Shoujo",
"shoujo-ai" to "Shoujo Ai",
"shounen" to "Shounen",
"shounen-ai" to "Shounen Ai",
"slice-of-life" to "Slice of Life",
"smut" to "Smut",
"soft-yaoi" to "Soft Yaoi",
"soft-yuri" to "Soft Yuri",
"sports" to "Sports",
"supernatural" to "Supernatural",
"thieu-nhi" to "Thiếu Nhi",
"tragedy" to "Tragedy",
"trinh-tham" to "Trinh Thám",
"truyen-scan" to "Truyện scan",
"truyen-mau" to "Truyện Màu",
"webtoon" to "Webtoon",
"xuyen-khong" to "Xuyên Không",
)
protected open fun getStatusList(): List<Pair<String?, String>> =
listOf(
Pair(null, intl["STATUS_ALL"]),
Pair("1", intl["STATUS_ONGOING"]),
Pair("2", intl["STATUS_COMPLETED"]),
)
protected var genreList: List<Pair<String?, String>> = emptyList()
private val scope = CoroutineScope(Dispatchers.IO)
protected fun launchIO(block: () -> Unit) = scope.launch { block() }
private var fetchGenresAttempts: Int = 0
protected fun fetchGenres() {
if (fetchGenresAttempts < 3 && genreList.isEmpty()) {
try {
genreList =
client.newCall(genresRequest()).execute()
.asJsoup()
.let(::parseGenres)
} catch (_: Exception) {
} finally {
fetchGenresAttempts++
}
}
}
protected open fun genresRequest() = GET("$baseUrl/$searchPath", headers)
protected open val genresSelector = ".genres ul.nav li:not(.active) a"
protected open val genresUrlDelimiter = "/"
protected open fun parseGenres(document: Document): List<Pair<String?, String>> {
val items = document.select(genresSelector)
return buildList(items.size + 1) {
add(Pair(null, intl["STATUS_ALL"]))
items.mapTo(this) {
Pair(
it.attr("href")
.removeSuffix("/")
.substringAfterLast(genresUrlDelimiter),
it.text(),
)
}
}
}
override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
return FilterList(
StatusFilter(getStatusList()),
GenreFilter(getGenreList()),
StatusFilter(intl["STATUS"], getStatusList()),
if (genreList.isEmpty()) {
Filter.Header(intl["GENRES_RESET"])
} else {
GenreFilter(intl["GENRE"], genreList)
},
)
}
protected open class UriPartFilter(displayName: String, val vals: Array<Pair<String?, String>>) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
fun toUriPart() = vals[state].first
protected open class UriPartFilter(displayName: String, private val pairs: List<Pair<String?, String>>) :
Filter.Select<String>(displayName, pairs.map { it.second }.toTypedArray()) {
fun toUriPart() = pairs[state].first
}
}

View File

@ -15,7 +15,13 @@ import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
class XoxoComics : WPComics("XOXO Comics", "https://xoxocomic.com", "en", SimpleDateFormat("MM/dd/yyyy", Locale.US), null) {
class XoxoComics : WPComics(
"XOXO Comics",
"https://xoxocomic.com",
"en",
dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US),
gmtOffset = null,
) {
override val searchPath = "search-comic"
override val popularPath = "hot-comic"
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/comic-update?page=$page", headers)
@ -84,72 +90,20 @@ class XoxoComics : WPComics("XOXO Comics", "https://xoxocomic.com", "en", Simple
override fun pageListRequest(chapter: SChapter): Request = GET(baseUrl + "${chapter.url}/all")
override fun getStatusList(): Array<Pair<String?, String>> = arrayOf(
Pair(null, "All"),
Pair("ongoing", "Ongoing"),
Pair("completed", "Completed"),
)
override fun getGenreList(): Array<Pair<String?, String>> = arrayOf(
null to "All",
"marvel-comic" to "Marvel",
"dc-comics-comic" to "DC Comics",
"dark-horse-comic" to "Dark Horse",
"action-comic" to "Action",
"adventure-comic" to "Adventure",
"anthology-comic" to "Anthology",
"anthropomorphic-comic" to "Anthropomorphic",
"biography-comic" to "Biography",
"children-comic" to "Children",
"comedy-comic" to "Comedy",
"crime-comic" to "Crime",
"drama-comic" to "Drama",
"family-comic" to "Family",
"fantasy-comic" to "Fantasy",
"fighting-comic" to "Fighting",
"graphic-novels-comic" to "Graphic Novels",
"historical-comic" to "Historical",
"horror-comic" to "Horror",
"leading-ladies-comic" to "Leading Ladies",
"lgbtq-comic" to "LGBTQ",
"literature-comic" to "Literature",
"manga-comic" to "Manga",
"martial-arts-comic" to "Martial Arts",
"military-comic" to "Military",
"mini-series-comic" to "Mini-Series",
"movies-tv-comic" to "Movies &amp; TV",
"music-comic" to "Music",
"mystery-comic" to "Mystery",
"mythology-comic" to "Mythology",
"personal-comic" to "Personal",
"political-comic" to "Political",
"post-apocalyptic-comic" to "Post-Apocalyptic",
"psychological-comic" to "Psychological",
"pulp-comic" to "Pulp",
"religious-comic" to "Religious",
"robots-comic" to "Robots",
"romance-comic" to "Romance",
"school-life-comic" to "School Life",
"sci-fi-comic" to "Sci-Fi",
"slice-of-life-comic" to "Slice of Life",
"sport-comic" to "Sport",
"spy-comic" to "Spy",
"superhero-comic" to "Superhero",
"supernatural-comic" to "Supernatural",
"suspense-comic" to "Suspense",
"teen-comic" to "Teen",
"thriller-comic" to "Thriller",
"vampires-comic" to "Vampires",
"video-games-comic" to "Video Games",
"war-comic" to "War",
"western-comic" to "Western",
"zombies-comic" to "Zombies",
)
override fun genresRequest() = GET("$baseUrl/comic-list", headers)
override val genresSelector = ".genres h2:contains(Genres) + ul.nav li a"
override fun getFilterList(): FilterList {
launchIO { fetchGenres() }
return FilterList(
Filter.Header("Search query won't use Genre/Status filter"),
StatusFilter(getStatusList()),
GenreFilter(getGenreList()),
StatusFilter("Status", getStatusList()),
if (genreList.isEmpty()) {
Filter.Header("Tap 'Reset' to load genres")
} else {
GenreFilter("Genre", genreList)
},
)
}
}

View File

@ -13,27 +13,36 @@ import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.JAPANESE), null) {
class JManga : WPComics(
"JManga",
"https://jmanga.vip",
"ja",
dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", Locale.JAPANESE),
gmtOffset = null,
) {
override fun popularMangaSelector() = "div.items article.item"
override fun popularMangaNextPageSelector() = "li:nth-last-child(2) a.page-link"
override fun popularMangaNextPageSelector() = "li.active + li.page-item a.page-link"
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = when {
info.select("li.status p.col-xs-8").text().contains("連載中", true) -> SManga.ONGOING
info.select("li.status p.col-xs-8").text().contains("完結済み", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
description = info.select("div.detail-content").text()
val otherName = info.select("h2.other-name").text()
description = info.select("div.detail-content").text() +
if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else ""
thumbnail_url = imageOrNull(info[0].selectFirst("div.col-image img")!!)
}
}
}
override val searchPath = "search/manga"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = filters.let { if (it.isEmpty()) getFilterList() else it }
val url = "$baseUrl/search/manga".toHttpUrl().newBuilder()
val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder()
filterList.forEach { filter ->
when (filter) {
@ -51,6 +60,7 @@ class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat("
return GET(url.build(), headers)
}
override fun chapterFromElement(element: Element): SChapter {
val minuteWords = listOf("minute", "")
val hourWords = listOf("hour", "時間")
@ -111,28 +121,15 @@ class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat("
}
}
}
override fun getStatusList(): Array<Pair<String?, String>> {
return arrayOf(
override fun getStatusList(): List<Pair<String?, String>> =
listOf(
Pair("-1", "全て"),
Pair("0", "完結済み"),
Pair("1", "連載中"),
)
}
override fun getGenreList(): Array<Pair<String?, String>> {
return arrayOf(
null to "全てのジャンル",
"TL" to "TL",
"BL" to "BL",
" ファンタジー " to " ファンタジー ",
"恋愛" to "恋愛",
"ドラマ" to "ドラマ",
"アクション" to "アクション",
"ホラー・ミステリー" to "ホラー・ミステリー",
"裏社会・アングラ" to "裏社会・アングラ",
"スポーツ" to "スポーツ",
"グルメ" to "グルメ",
"日常" to "日常",
"SF" to "SF",
)
}
override val genresSelector = ".genres ul.nav li:not(.active) a"
override val genresUrlDelimiter = "="
}

View File

@ -6,9 +6,13 @@ import okhttp3.Response
import java.text.SimpleDateFormat
import java.util.Locale
class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenff.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) {
override fun String.replaceSearchPath() = replace("/$searchPath?status=2&", "/truyen-full?")
class NetTruyen : WPComics(
"NetTruyen",
"https://www.nettruyenff.com",
"vi",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()),
gmtOffset = null,
) {
/**
* NetTruyen/NhatTruyen redirect back to catalog page if searching query is not found.
* That makes both sites always return un-relevant results when searching should return empty.
@ -19,62 +23,4 @@ class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenff.com", "vi", Sim
}
return super.searchMangaParse(response)
}
override fun getGenreList(): Array<Pair<String?, String>> = arrayOf(
null to "Tất cả",
"action-95" to "Action",
"truong-thanh" to "Adult",
"adventure" to "Adventure",
"anime" to "Anime",
"chuyen-sinh-2131" to "Chuyển Sinh",
"comedy-99" to "Comedy",
"comic" to "Comic",
"cooking-101" to "Cooking",
"co-dai-207" to "Cổ Đại",
"doujinshi" to "Doujinshi",
"drama-103" to "Drama",
"dam-my" to "Đam Mỹ",
"ecchi-104" to "Ecchi",
"fantasy-1050" to "Fantasy",
"gender-bender-106" to "Gender Bender",
"harem-107" to "Harem",
"historical" to "Historical",
"horror" to "Horror",
"josei" to "Josei",
"live-action" to "Live action",
"manga-112" to "Manga",
"manhua" to "Manhua",
"manhwa-11400" to "Manhwa",
"martial-arts" to "Martial Arts",
"mature" to "Mature",
"mecha-117" to "Mecha",
"mystery" to "Mystery",
"ngon-tinh" to "Ngôn Tình",
"one-shot" to "One shot",
"psychological" to "Psychological",
"romance-121" to "Romance",
"school-life" to "School Life",
"sci-fi" to "Sci-fi",
"seinen" to "Seinen",
"shoujo" to "Shoujo",
"shoujo-ai-126" to "Shoujo Ai",
"shounen-127" to "Shounen",
"shounen-ai" to "Shounen Ai",
"slice-of-life" to "Slice of Life",
"smut" to "Smut",
"soft-yaoi" to "Soft Yaoi",
"soft-yuri" to "Soft Yuri",
"sports" to "Sports",
"supernatural" to "Supernatural",
"tap-chi-truyen-tranh" to "Tạp chí truyện tranh",
"thieu-nhi" to "Thiếu Nhi",
"tragedy-136" to "Tragedy",
"trinh-tham" to "Trinh Thám",
"truyen-scan" to "Truyện scan",
"truyen-mau-214" to "Truyện Màu",
"viet-nam" to "Việt Nam",
"webtoon-140" to "Webtoon",
"xuyen-khong-205" to "Xuyên Không",
"16" to "16+",
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'NetTruyenCO (unoriginal)'
extClass = '.NetTruyenCO'
themePkg = 'wpcomics'
baseUrl = 'https://nettruyenco.vn'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.extension.vi.nettruyenco
import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
class NetTruyenCO : WPComics(
"NetTruyenCO (unoriginal)",
"https://nettruyenco.vn",
"vi",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()),
gmtOffset = null,
) {
override val popularPath = "truyen-tranh-hot"
// Details
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
val otherName = info.select("h2.other-name").text()
description = info.select("div.detail-content div div.nth-child(3)").text() +
if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else ""
thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!)
}
}
}
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'NetTruyenX (unoriginal)'
extClass = '.NetTruyenX'
themePkg = 'wpcomics'
baseUrl = 'https://nettruyenx.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.extension.vi.nettruyenx
import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
class NetTruyenX : WPComics(
"NetTruyenX (unoriginal)",
"https://nettruyenx.com",
"vi",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()),
gmtOffset = null,
) {
override val popularPath = "truyen-tranh-hot"
// Details
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
val otherName = info.select("h2.other-name").text()
description = info.select("div.detail-content div div:nth-child(4)").text() +
if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else ""
thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!)
}
}
}
}

View File

@ -6,7 +6,13 @@ import okhttp3.Response
import java.text.SimpleDateFormat
import java.util.Locale
class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenup.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) {
class NhatTruyen : WPComics(
"NhatTruyen",
"https://nhattruyenup.com",
"vi",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()),
gmtOffset = null,
) {
override val searchPath = "the-loai"
/**
@ -19,62 +25,4 @@ class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenup.com", "vi", Simp
}
return super.searchMangaParse(response)
}
override fun getGenreList(): Array<Pair<String?, String>> = arrayOf(
null to "Tất cả",
"action" to "Action",
"adult" to "Adult",
"adventure" to "Adventure",
"anime" to "Anime",
"chuyen-sinh" to "Chuyển Sinh",
"comedy" to "Comedy",
"comic" to "Comic",
"cooking" to "Cooking",
"co-dai" to "Cổ Đại",
"doujinshi" to "Doujinshi",
"drama" to "Drama",
"dam-my" to "Đam Mỹ",
"ecchi" to "Ecchi",
"fantasy" to "Fantasy",
"gender-bender" to "Gender Bender",
"harem" to "Harem",
"historical" to "Historical",
"horror" to "Horror",
"josei" to "Josei",
"live-action" to "Live action",
"manga-241" to "Manga",
"manhua" to "Manhua",
"manhwa-2431" to "Manhwa",
"martial-arts" to "Martial Arts",
"mature" to "Mature",
"mecha" to "Mecha",
"mystery" to "Mystery",
"ngon-tinh" to "Ngôn Tình",
"one-shot" to "One shot",
"psychological" to "Psychological",
"romance" to "Romance",
"school-life" to "School Life",
"sci-fi" to "Sci-fi",
"seinen" to "Seinen",
"shoujo" to "Shoujo",
"shoujo-ai" to "Shoujo Ai",
"shounen" to "Shounen",
"shounen-ai" to "Shounen Ai",
"slice-of-life" to "Slice of Life",
"smut" to "Smut",
"soft-yaoi" to "Soft Yaoi",
"soft-yuri" to "Soft Yuri",
"sports" to "Sports",
"supernatural" to "Supernatural",
"tap-chi-truyen-tranh" to "Tạp chí truyện tranh",
"thieu-nhi" to "Thiếu Nhi",
"tragedy" to "Tragedy",
"trinh-tham" to "Trinh Thám",
"truyen-scan" to "Truyện scan",
"truyen-mau" to "Truyện Màu",
"viet-nam" to "Việt Nam",
"webtoon" to "Webtoon",
"xuyen-khong" to "Xuyên Không",
"16" to "16+",
)
}

View File

@ -0,0 +1,9 @@
ext {
extName = 'NhatTruyenS (unoriginal)'
extClass = '.NhatTruyenS'
themePkg = 'wpcomics'
baseUrl = 'https://nhattruyens.com'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,54 @@
package eu.kanade.tachiyomi.extension.vi.nhattruyens
import eu.kanade.tachiyomi.multisrc.wpcomics.WPComics
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
import java.util.Locale
class NhatTruyenS : WPComics(
"NhatTruyenS (unoriginal)",
"https://nhattruyens.com",
"vi",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.getDefault()),
gmtOffset = null,
) {
override val popularPath = "truyen-hot"
/**
* Remove fake-manga ads
*/
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(searchMangaSelector())
.filter { element -> element.select("figure > div > a[rel='nofollow']").isNullOrEmpty() }
.map { element ->
searchMangaFromElement(element)
}
val hasNextPage = searchMangaNextPageSelector().let { selector ->
document.select(selector).first()
} != null
return MangasPage(mangas, hasNextPage)
}
// Details
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.select("article#item-detail").let { info ->
author = info.select("li.author p.col-xs-8").text()
status = info.select("li.status p.col-xs-8").text().toStatus()
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
val otherName = info.select("h2.other-name").text()
description = info.select("div.detail-content div.about:nth-child(3)").text() +
if (otherName.isNotBlank()) "\n\n ${intl["OTHER_NAME"]}: $otherName" else ""
thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!)
}
}
}
}