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")
|
||||
}
|
||||
|
||||
baseVersionCode = 4
|
||||
baseVersionCode = 5
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:i18n"))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 & 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)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = "="
|
||||
}
|
|
@ -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+",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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.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+",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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()!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|