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
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
STATUS=状態
|
||||||
|
STATUS_ALL=全て
|
||||||
|
STATUS_ONGOING=連載中
|
||||||
|
STATUS_COMPLETED=完結済み
|
||||||
|
GENRE=ジャンル
|
|
@ -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
|
|
@ -2,4 +2,8 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 4
|
baseVersionCode = 5
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(":lib:i18n"))
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.wpcomics
|
package eu.kanade.tachiyomi.multisrc.wpcomics
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.lib.i18n.Intl
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
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.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
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.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
@ -20,23 +24,28 @@ import java.util.Locale
|
||||||
abstract class WPComics(
|
abstract class WPComics(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
override val lang: String,
|
final override val lang: String,
|
||||||
private val dateFormat: SimpleDateFormat = SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US),
|
protected val dateFormat: SimpleDateFormat = SimpleDateFormat("HH:mm - dd/MM/yyyy Z", Locale.US),
|
||||||
private val gmtOffset: String? = "+0500",
|
protected val gmtOffset: String? = "+0500",
|
||||||
) : ParsedHttpSource() {
|
) : ParsedHttpSource() {
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0")
|
.add("Referer", "$baseUrl/")
|
||||||
.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
|
// Popular
|
||||||
|
|
||||||
open val popularPath = "hot"
|
open val popularPath = "hot"
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
@ -58,7 +67,6 @@ abstract class WPComics(
|
||||||
override fun popularMangaNextPageSelector() = "a.next-page, a[rel=next]"
|
override fun popularMangaNextPageSelector() = "a.next-page, a[rel=next]"
|
||||||
|
|
||||||
// Latest
|
// Latest
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
return GET(baseUrl + if (page > 1) "?page=$page" else "", headers)
|
return GET(baseUrl + if (page > 1) "?page=$page" else "", headers)
|
||||||
}
|
}
|
||||||
|
@ -70,35 +78,27 @@ abstract class WPComics(
|
||||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
protected open val searchPath = "tim-truyen"
|
protected open val searchPath = "tim-truyen"
|
||||||
protected open val queryParam = "keyword"
|
protected open val queryParam = "keyword"
|
||||||
|
|
||||||
protected open fun String.replaceSearchPath() = this
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val filterList = filters.let { if (it.isEmpty()) getFilterList() else it }
|
val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder()
|
||||||
return if (filterList.isEmpty()) {
|
|
||||||
GET("$baseUrl/?s=$query&post_type=comics&page=$page")
|
|
||||||
} else {
|
|
||||||
val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder()
|
|
||||||
|
|
||||||
filterList.forEach { filter ->
|
filters.forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) }
|
is GenreFilter -> filter.toUriPart()?.let { url.addPathSegment(it) }
|
||||||
is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) }
|
is StatusFilter -> filter.toUriPart()?.let { url.addQueryParameter("status", it) }
|
||||||
else -> {}
|
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"
|
override fun searchMangaSelector() = "div.items div.item"
|
||||||
|
@ -116,22 +116,23 @@ abstract class WPComics(
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||||
|
|
||||||
// Details
|
// Details
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
document.select("article#item-detail").let { info ->
|
document.select("article#item-detail").let { info ->
|
||||||
author = info.select("li.author p.col-xs-8").text()
|
author = info.select("li.author p.col-xs-8").text()
|
||||||
status = info.select("li.status p.col-xs-8").text().toStatus()
|
status = info.select("li.status p.col-xs-8").text().toStatus()
|
||||||
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
|
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()!!)
|
thumbnail_url = imageOrNull(info.select("div.col-image img").first()!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun String?.toStatus(): Int {
|
open fun String?.toStatus(): Int {
|
||||||
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành")
|
val ongoingWords = listOf("Ongoing", "Updating", "Đang tiến hành", "連載中")
|
||||||
val completedWords = listOf("Complete", "Completed", "Hoàn thành")
|
val completedWords = listOf("Complete", "Completed", "Hoàn thành", "完結済み")
|
||||||
return when {
|
return when {
|
||||||
this == null -> SManga.UNKNOWN
|
this == null -> SManga.UNKNOWN
|
||||||
ongoingWords.doesInclude(this) -> SManga.ONGOING
|
ongoingWords.doesInclude(this) -> SManga.ONGOING
|
||||||
|
@ -141,7 +142,6 @@ abstract class WPComics(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
// Chapters
|
||||||
|
|
||||||
override fun chapterListSelector() = "div.list-chapter li.row:not(.heading)"
|
override fun chapterListSelector() = "div.list-chapter li.row:not(.heading)"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
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 {
|
protected open fun String?.toDate(): Long {
|
||||||
this ?: return 0
|
this ?: return 0L
|
||||||
|
|
||||||
val secondWords = listOf("second", "giây")
|
val secondWords = listOf("second", "giây")
|
||||||
val minuteWords = listOf("minute", "phút")
|
val minuteWords = listOf("minute", "phút")
|
||||||
|
@ -182,10 +182,10 @@ abstract class WPComics(
|
||||||
(if (gmtOffset == null) this.substringAfterLast(" ") else "$this $gmtOffset").let {
|
(if (gmtOffset == null) this.substringAfterLast(" ") else "$this $gmtOffset").let {
|
||||||
// timestamp has year
|
// timestamp has year
|
||||||
if (Regex("""\d+/\d+/\d\d""").find(it)?.value != null) {
|
if (Regex("""\d+/\d+/\d\d""").find(it)?.value != null) {
|
||||||
dateFormat.parse(it)?.time ?: 0
|
dateFormat.parse(it)?.time ?: 0L
|
||||||
} else {
|
} else {
|
||||||
// MangaSum - timestamp sometimes doesn't have year (current year implied)
|
// 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
|
// Pages
|
||||||
|
|
||||||
// sources sometimes have an image element with an empty attr that isn't really an image
|
|
||||||
open fun imageOrNull(element: Element): String? {
|
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 {
|
fun Element.hasValidAttr(attr: String): Boolean {
|
||||||
val regex = Regex("""https?://.*""", RegexOption.IGNORE_CASE)
|
val regex = Regex("""https?://.*""", RegexOption.IGNORE_CASE)
|
||||||
return when {
|
return when {
|
||||||
|
@ -226,80 +225,74 @@ abstract class WPComics(
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||||
|
|
||||||
// Filters
|
// 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(name: String, pairs: List<Pair<String?, String>>) : UriPartFilter(name, pairs)
|
||||||
protected class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Genre", vals)
|
|
||||||
|
|
||||||
protected open fun getStatusList(): Array<Pair<String?, String>> = arrayOf(
|
protected open fun getStatusList(): List<Pair<String?, String>> =
|
||||||
Pair(null, "Tất cả"),
|
listOf(
|
||||||
Pair("1", "Đang tiến hành"),
|
Pair(null, intl["STATUS_ALL"]),
|
||||||
Pair("2", "Đã hoàn thành"),
|
Pair("1", intl["STATUS_ONGOING"]),
|
||||||
Pair("3", "Tạm ngừng"),
|
Pair("2", intl["STATUS_COMPLETED"]),
|
||||||
)
|
)
|
||||||
protected open fun getGenreList(): Array<Pair<String?, String>> = arrayOf(
|
|
||||||
null to "Tất cả",
|
protected var genreList: List<Pair<String?, String>> = emptyList()
|
||||||
"action" to "Action",
|
|
||||||
"adult" to "Adult",
|
private val scope = CoroutineScope(Dispatchers.IO)
|
||||||
"adventure" to "Adventure",
|
|
||||||
"anime" to "Anime",
|
protected fun launchIO(block: () -> Unit) = scope.launch { block() }
|
||||||
"chuyen-sinh" to "Chuyển Sinh",
|
|
||||||
"comedy" to "Comedy",
|
private var fetchGenresAttempts: Int = 0
|
||||||
"comic" to "Comic",
|
|
||||||
"cooking" to "Cooking",
|
protected fun fetchGenres() {
|
||||||
"co-dai" to "Cổ Đại",
|
if (fetchGenresAttempts < 3 && genreList.isEmpty()) {
|
||||||
"doujinshi" to "Doujinshi",
|
try {
|
||||||
"drama" to "Drama",
|
genreList =
|
||||||
"dam-my" to "Đam Mỹ",
|
client.newCall(genresRequest()).execute()
|
||||||
"ecchi" to "Ecchi",
|
.asJsoup()
|
||||||
"fantasy" to "Fantasy",
|
.let(::parseGenres)
|
||||||
"gender-bender" to "Gender Bender",
|
} catch (_: Exception) {
|
||||||
"harem" to "Harem",
|
} finally {
|
||||||
"historical" to "Historical",
|
fetchGenresAttempts++
|
||||||
"horror" to "Horror",
|
}
|
||||||
"josei" to "Josei",
|
}
|
||||||
"live-action" to "Live action",
|
}
|
||||||
"manga" to "Manga",
|
|
||||||
"manhua" to "Manhua",
|
protected open fun genresRequest() = GET("$baseUrl/$searchPath", headers)
|
||||||
"manhwa" to "Manhwa",
|
|
||||||
"martial-arts" to "Martial Arts",
|
protected open val genresSelector = ".genres ul.nav li:not(.active) a"
|
||||||
"mature" to "Mature",
|
|
||||||
"mecha" to "Mecha",
|
protected open val genresUrlDelimiter = "/"
|
||||||
"mystery" to "Mystery",
|
|
||||||
"ngon-tinh" to "Ngôn Tình",
|
protected open fun parseGenres(document: Document): List<Pair<String?, String>> {
|
||||||
"one-shot" to "One shot",
|
val items = document.select(genresSelector)
|
||||||
"psychological" to "Psychological",
|
return buildList(items.size + 1) {
|
||||||
"romance" to "Romance",
|
add(Pair(null, intl["STATUS_ALL"]))
|
||||||
"school-life" to "School Life",
|
items.mapTo(this) {
|
||||||
"sci-fi" to "Sci-fi",
|
Pair(
|
||||||
"seinen" to "Seinen",
|
it.attr("href")
|
||||||
"shoujo" to "Shoujo",
|
.removeSuffix("/")
|
||||||
"shoujo-ai" to "Shoujo Ai",
|
.substringAfterLast(genresUrlDelimiter),
|
||||||
"shounen" to "Shounen",
|
it.text(),
|
||||||
"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",
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun getFilterList(): FilterList {
|
override fun getFilterList(): FilterList {
|
||||||
|
launchIO { fetchGenres() }
|
||||||
return FilterList(
|
return FilterList(
|
||||||
StatusFilter(getStatusList()),
|
StatusFilter(intl["STATUS"], getStatusList()),
|
||||||
GenreFilter(getGenreList()),
|
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>>) :
|
protected open class UriPartFilter(displayName: String, private val pairs: List<Pair<String?, String>>) :
|
||||||
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
|
Filter.Select<String>(displayName, pairs.map { it.second }.toTypedArray()) {
|
||||||
fun toUriPart() = vals[state].first
|
fun toUriPart() = pairs[state].first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,13 @@ import org.jsoup.nodes.Element
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
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 searchPath = "search-comic"
|
||||||
override val popularPath = "hot-comic"
|
override val popularPath = "hot-comic"
|
||||||
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/comic-update?page=$page", headers)
|
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 pageListRequest(chapter: SChapter): Request = GET(baseUrl + "${chapter.url}/all")
|
||||||
|
|
||||||
override fun getStatusList(): Array<Pair<String?, String>> = arrayOf(
|
override fun genresRequest() = GET("$baseUrl/comic-list", headers)
|
||||||
Pair(null, "All"),
|
|
||||||
Pair("ongoing", "Ongoing"),
|
override val genresSelector = ".genres h2:contains(Genres) + ul.nav li a"
|
||||||
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 & 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 getFilterList(): FilterList {
|
override fun getFilterList(): FilterList {
|
||||||
|
launchIO { fetchGenres() }
|
||||||
return FilterList(
|
return FilterList(
|
||||||
Filter.Header("Search query won't use Genre/Status filter"),
|
Filter.Header("Search query won't use Genre/Status filter"),
|
||||||
StatusFilter(getStatusList()),
|
StatusFilter("Status", getStatusList()),
|
||||||
GenreFilter(getGenreList()),
|
if (genreList.isEmpty()) {
|
||||||
|
Filter.Header("Tap 'Reset' to load genres")
|
||||||
|
} else {
|
||||||
|
GenreFilter("Genre", genreList)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,27 +13,36 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
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 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 {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
document.select("article#item-detail").let { info ->
|
document.select("article#item-detail").let { info ->
|
||||||
author = info.select("li.author p.col-xs-8").text()
|
author = info.select("li.author p.col-xs-8").text()
|
||||||
status = when {
|
status = info.select("li.status p.col-xs-8").text().toStatus()
|
||||||
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
|
|
||||||
}
|
|
||||||
genre = info.select("li.kind p.col-xs-8 a").joinToString { it.text() }
|
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")!!)
|
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 {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val filterList = filters.let { if (it.isEmpty()) getFilterList() else it }
|
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 ->
|
filterList.forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
|
@ -51,6 +60,7 @@ class JManga : WPComics("JManga", "https://jmanga.vip", "ja", SimpleDateFormat("
|
||||||
|
|
||||||
return GET(url.build(), headers)
|
return GET(url.build(), headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
val minuteWords = listOf("minute", "分")
|
val minuteWords = listOf("minute", "分")
|
||||||
val hourWords = listOf("hour", "時間")
|
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("-1", "全て"),
|
||||||
Pair("0", "完結済み"),
|
Pair("0", "完結済み"),
|
||||||
Pair("1", "連載中"),
|
Pair("1", "連載中"),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
override fun getGenreList(): Array<Pair<String?, String>> {
|
override val genresSelector = ".genres ul.nav li:not(.active) a"
|
||||||
return arrayOf(
|
|
||||||
null to "全てのジャンル",
|
override val genresUrlDelimiter = "="
|
||||||
"TL" to "TL",
|
|
||||||
"BL" to "BL",
|
|
||||||
" ファンタジー " to " ファンタジー ",
|
|
||||||
"恋愛" to "恋愛",
|
|
||||||
"ドラマ" to "ドラマ",
|
|
||||||
"アクション" to "アクション",
|
|
||||||
"ホラー・ミステリー" to "ホラー・ミステリー",
|
|
||||||
"裏社会・アングラ" to "裏社会・アングラ",
|
|
||||||
"スポーツ" to "スポーツ",
|
|
||||||
"グルメ" to "グルメ",
|
|
||||||
"日常" to "日常",
|
|
||||||
"SF" to "SF",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,9 +6,13 @@ import okhttp3.Response
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class NetTruyen : WPComics("NetTruyen", "https://www.nettruyenff.com", "vi", SimpleDateFormat("dd/MM/yy", Locale.getDefault()), null) {
|
class NetTruyen : WPComics(
|
||||||
override fun String.replaceSearchPath() = replace("/$searchPath?status=2&", "/truyen-full?")
|
"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.
|
* 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.
|
* 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)
|
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+",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
ext {
|
||||||
|
extName = 'NetTruyenCO (unoriginal)'
|
||||||
|
extClass = '.NetTruyenCO'
|
||||||
|
themePkg = 'wpcomics'
|
||||||
|
baseUrl = 'https://nettruyenco.vn'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -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()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
ext {
|
||||||
|
extName = 'NetTruyenX (unoriginal)'
|
||||||
|
extClass = '.NetTruyenX'
|
||||||
|
themePkg = 'wpcomics'
|
||||||
|
baseUrl = 'https://nettruyenx.com'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -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()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,13 @@ import okhttp3.Response
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
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"
|
override val searchPath = "the-loai"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,62 +25,4 @@ class NhatTruyen : WPComics("NhatTruyen", "https://nhattruyenup.com", "vi", Simp
|
||||||
}
|
}
|
||||||
return super.searchMangaParse(response)
|
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+",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
ext {
|
||||||
|
extName = 'NhatTruyenS (unoriginal)'
|
||||||
|
extClass = '.NhatTruyenS'
|
||||||
|
themePkg = 'wpcomics'
|
||||||
|
baseUrl = 'https://nhattruyens.com'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -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()!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|