[TruyenQQ] Refactor, add filters, fix empty page list (#15065)
This commit is contained in:
parent
1754456d2e
commit
34285655aa
|
@ -5,7 +5,7 @@ ext {
|
|||
extName = 'TruyenQQ'
|
||||
pkgNameSuffix = 'vi.truyenqq'
|
||||
extClass = '.TruyenQQ'
|
||||
extVersionCode = 9
|
||||
extVersionCode = 10
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
package eu.kanade.tachiyomi.extension.vi.truyenqq
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
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 eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -25,7 +26,7 @@ class TruyenQQ : ParsedHttpSource() {
|
|||
|
||||
override val lang: String = "vi"
|
||||
|
||||
override val baseUrl: String = "http://truyenqqhot.com"
|
||||
override val baseUrl: String = "https://truyenqqhot.com"
|
||||
|
||||
override val supportsLatest: Boolean = true
|
||||
|
||||
|
@ -36,91 +37,70 @@ class TruyenQQ : ParsedHttpSource() {
|
|||
.followRedirects(true)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
|
||||
override fun headersBuilder(): Headers.Builder =
|
||||
super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
|
||||
private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.US)
|
||||
|
||||
private val floatPattern = Regex("""\d+(?:\.\d+)?""")
|
||||
// Trang html chứa popular
|
||||
override fun popularMangaRequest(page: Int): Request =
|
||||
GET("$baseUrl/truyen-yeu-thich/trang-$page.html", headers)
|
||||
|
||||
// Selector trả về array các manga (chọn cả ảnh cx được tí nữa parse)
|
||||
override fun popularMangaSelector(): String = "ul.grid > li"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
val anchor = element.selectFirst(".book_info .qtip a")
|
||||
setUrlWithoutDomain(anchor.attr("href"))
|
||||
title = anchor.text()
|
||||
thumbnail_url = element.select(".book_avatar img").attr("abs:src")
|
||||
}
|
||||
|
||||
// Selector của nút trang kế tiếp
|
||||
override fun popularMangaNextPageSelector(): String =
|
||||
".page_redirect > a:nth-last-child(2) > p:not(.active)"
|
||||
|
||||
// Trang html chứa Latest (các cập nhật mới nhất)
|
||||
override fun latestUpdatesRequest(page: Int): Request =
|
||||
GET("$baseUrl/truyen-moi-cap-nhat/trang-$page.html", headers)
|
||||
|
||||
// Selector trả về array các manga update (giống selector ở trên)
|
||||
override fun latestUpdatesSelector(): String = popularMangaSelector()
|
||||
// Selector của nút trang kế tiếp
|
||||
override fun popularMangaNextPageSelector(): String = ".page_redirect > a:nth-last-child(2) > p:not(.active)"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga =
|
||||
popularMangaFromElement(element)
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String = popularMangaNextPageSelector()
|
||||
|
||||
// Trang html chứa popular
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/truyen-yeu-thich/trang-$page.html", headers)
|
||||
}
|
||||
// Trang html chứa Latest (các cập nhật mới nhất)
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/truyen-moi-cap-nhat/trang-$page.html", headers)
|
||||
}
|
||||
|
||||
// respond là html của trang popular chứ không phải của element đã select
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val imgURL = document.select(".book_avatar img").map { it.attr("abs:src") }
|
||||
val mangas = document.select(popularMangaSelector()).mapIndexed { index, element -> popularMangaFromElement(element, imgURL[index]) }
|
||||
|
||||
val hasNextPage = popularMangaNextPageSelector().let { selector ->
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
return MangasPage(mangas, hasNextPage)
|
||||
}
|
||||
|
||||
// Từ 1 element trong list popular đã select ở trên parse thông tin 1 Manga
|
||||
// Trông code bất ổn nhưng t đang cố làm theo blogtruyen vì t không biết gì hết XD
|
||||
private fun popularMangaFromElement(element: Element, imgURL: String): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select(".book_info .book_name h3 a").first().let {
|
||||
manga.setUrlWithoutDomain((it.attr("href")))
|
||||
manga.title = it.text().trim()
|
||||
manga.thumbnail_url = imgURL
|
||||
}
|
||||
return manga
|
||||
}
|
||||
|
||||
// Không dùng bản này của fuction nên throw Exception, dùng function ở trên (có 2 params)
|
||||
override fun popularMangaFromElement(element: Element): SManga = throw Exception("Not Used")
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
element.select(".book_info .book_name h3 a").first().let {
|
||||
manga.setUrlWithoutDomain((it.attr("href")))
|
||||
manga.title = it.text().trim()
|
||||
}
|
||||
manga.thumbnail_url = element.select(".book_avatar img").first().attr("abs:src")
|
||||
return manga
|
||||
}
|
||||
|
||||
// Tìm kiếm
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$baseUrl/tim-kiem/trang-$page.html"
|
||||
val uri = url.toHttpUrlOrNull()!!.newBuilder()
|
||||
uri.addQueryParameter("q", query)
|
||||
return GET(uri.toString(), headers)
|
||||
|
||||
// Todo Filters
|
||||
val url = if (query.isNotBlank()) {
|
||||
"$baseUrl/tim-kiem/trang-$page.html".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", query)
|
||||
.build()
|
||||
.toString()
|
||||
} else {
|
||||
val builder = "$baseUrl/tim-kiem-nang-cao/trang-$page.html".toHttpUrl().newBuilder()
|
||||
(if (filters.isEmpty()) getFilterList() else filters).filterIsInstance<UriFilter>()
|
||||
.forEach { it.addToUri(builder) }
|
||||
builder.build().toString()
|
||||
}
|
||||
return GET(url, headers)
|
||||
}
|
||||
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
|
||||
override fun searchMangaSelector(): String = popularMangaSelector()
|
||||
override fun searchMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element)
|
||||
|
||||
// Details
|
||||
override fun searchMangaSelector(): String = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector(): String = popularMangaNextPageSelector()
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
|
||||
val info = document.selectFirst(".list-info")
|
||||
|
||||
title = document.select("h1").text()
|
||||
author = info.select(".org").joinToString { it.text() }
|
||||
artist = author
|
||||
val glist = document.select(".list01 li").map { it.text() }
|
||||
genre = glist.joinToString()
|
||||
description = document.select(".story-detail-info").text()
|
||||
genre = document.select(".list01 li").joinToString { it.text() }
|
||||
description = document.select(".story-detail-info").textWithLinebreaks()
|
||||
thumbnail_url = document.select("img[itemprop=image]").attr("abs:src")
|
||||
status = when (info.select(".status > p:last-child").text()) {
|
||||
"Đang Cập Nhật" -> SManga.ONGOING
|
||||
|
@ -129,29 +109,177 @@ class TruyenQQ : ParsedHttpSource() {
|
|||
}
|
||||
}
|
||||
|
||||
// Chapters
|
||||
private fun Elements.textWithLinebreaks(): String {
|
||||
this.select("p").prepend("\\n")
|
||||
this.select("br").prepend("\\n")
|
||||
return this.text().replace("\\n", "\n").replace("\n ", "\n")
|
||||
}
|
||||
|
||||
// Chapters
|
||||
override fun chapterListSelector(): String = "div.works-chapter-list div.works-chapter-item"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.select("a").attr("abs:href"))
|
||||
setUrlWithoutDomain(element.select("a").attr("href"))
|
||||
name = element.select("a").text().trim()
|
||||
date_upload = parseDate(element.select(".time-chap").text())
|
||||
chapter_number = floatPattern.find(name)?.value?.toFloatOrNull() ?: -1f
|
||||
}
|
||||
private fun parseDate(date: String): Long {
|
||||
return dateFormat.parse(date)?.time ?: 0L
|
||||
}
|
||||
|
||||
private fun parseDate(date: String): Long = kotlin.runCatching {
|
||||
dateFormat.parse(date)?.time
|
||||
}.getOrNull() ?: 0L
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request = super.pageListRequest(chapter)
|
||||
.newBuilder()
|
||||
.cacheControl(CacheControl.FORCE_NETWORK)
|
||||
.build()
|
||||
|
||||
// Pages
|
||||
override fun pageListParse(document: Document): List<Page> =
|
||||
document.select(".page-chapter img")
|
||||
.mapIndexed { idx, it ->
|
||||
Page(idx, imageUrl = it.attr("abs:src"))
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
|
||||
document.select("img.lazy").forEachIndexed { index, element ->
|
||||
add(Page(index, "", element.attr("abs:src")))
|
||||
override fun imageUrlParse(document: Document): String =
|
||||
throw UnsupportedOperationException("Not used")
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
Filter.Header("Không dùng chung với tìm kiếm bằng tên"),
|
||||
CountryFilter(),
|
||||
StatusFilter(),
|
||||
ChapterCountFilter(),
|
||||
SortByFilter(),
|
||||
GenreList(getGenreList()),
|
||||
)
|
||||
|
||||
interface UriFilter {
|
||||
fun addToUri(builder: HttpUrl.Builder)
|
||||
}
|
||||
|
||||
open class UriPartFilter(
|
||||
name: String,
|
||||
private val query: String,
|
||||
private val vals: Array<Pair<String, String>>
|
||||
) : UriFilter, Filter.Select<String>(name, vals.map { it.first }.toTypedArray()) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter(query, vals[state].second)
|
||||
}
|
||||
}
|
||||
override fun imageUrlParse(document: Document): String {
|
||||
throw Exception("Not Used")
|
||||
|
||||
class CountryFilter : UriPartFilter(
|
||||
"Quốc gia",
|
||||
"country",
|
||||
arrayOf(
|
||||
"Tất cả" to "0",
|
||||
"Trung Quốc" to "1",
|
||||
"Việt Nam" to "2",
|
||||
"Hàn Quốc" to "3",
|
||||
"Nhật Bản" to "4",
|
||||
"Mỹ" to "5",
|
||||
)
|
||||
)
|
||||
|
||||
class StatusFilter : UriPartFilter(
|
||||
"Tình trạng",
|
||||
"status",
|
||||
arrayOf(
|
||||
"Tất cả" to "-1",
|
||||
"Đang tiến hành" to "0",
|
||||
"Hoàn thành" to "2",
|
||||
)
|
||||
)
|
||||
|
||||
class ChapterCountFilter : UriPartFilter(
|
||||
"Số lượng chương",
|
||||
"minchapter",
|
||||
arrayOf(
|
||||
"0" to "0",
|
||||
">= 100" to "100",
|
||||
">= 200" to "200",
|
||||
">= 300" to "300",
|
||||
">= 400" to "400",
|
||||
">= 500" to "500",
|
||||
)
|
||||
)
|
||||
|
||||
class SortByFilter : UriFilter, Filter.Sort(
|
||||
"Sắp xếp",
|
||||
arrayOf("Ngày đăng", "Ngày cập nhật", "Lượt xem"),
|
||||
Selection(2, false)
|
||||
) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
val index = state?.index ?: 2
|
||||
val ascending = if (state?.ascending == true) 1 else 0
|
||||
builder.addQueryParameter("sort", (index * 2 + ascending).toString())
|
||||
}
|
||||
}
|
||||
|
||||
// Not Used
|
||||
class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
class GenreList(state: List<Genre>) : UriFilter, Filter.Group<Genre>("Thể loại", state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
val genres = mutableListOf<String>()
|
||||
val genresEx = mutableListOf<String>()
|
||||
|
||||
state.forEach {
|
||||
when (it.state) {
|
||||
TriState.STATE_INCLUDE -> genres.add(it.id)
|
||||
TriState.STATE_EXCLUDE -> genresEx.add(it.id)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
builder.addQueryParameter("category", genres.joinToString(","))
|
||||
builder.addQueryParameter("notcategory", genresEx.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
// console.log([...document.querySelectorAll(".genre-item")].map(e => `Genre("${e.innerText}", "${e.querySelector("span").dataset.id}")`).join(",\n"))
|
||||
private fun getGenreList() = listOf(
|
||||
Genre("Action", "26"),
|
||||
Genre("Adventure", "27"),
|
||||
Genre("Anime", "62"),
|
||||
Genre("Chuyển Sinh", "91"),
|
||||
Genre("Cổ Đại", "90"),
|
||||
Genre("Comedy", "28"),
|
||||
Genre("Comic", "60"),
|
||||
Genre("Demons", "99"),
|
||||
Genre("Detective", "100"),
|
||||
Genre("Doujinshi", "96"),
|
||||
Genre("Drama", "29"),
|
||||
Genre("Fantasy", "30"),
|
||||
Genre("Gender Bender", "45"),
|
||||
Genre("Harem", "47"),
|
||||
Genre("Historical", "51"),
|
||||
Genre("Horror", "44"),
|
||||
Genre("Huyền Huyễn", "468"),
|
||||
Genre("Isekai", "85"),
|
||||
Genre("Josei", "54"),
|
||||
Genre("Mafia", "69"),
|
||||
Genre("Magic", "58"),
|
||||
Genre("Manhua", "35"),
|
||||
Genre("Manhwa", "49"),
|
||||
Genre("Martial Arts", "41"),
|
||||
Genre("Military", "101"),
|
||||
Genre("Mystery", "39"),
|
||||
Genre("Ngôn Tình", "87"),
|
||||
Genre("One shot", "95"),
|
||||
Genre("Psychological", "40"),
|
||||
Genre("Romance", "36"),
|
||||
Genre("School Life", "37"),
|
||||
Genre("Sci-fi", "43"),
|
||||
Genre("Seinen", "42"),
|
||||
Genre("Shoujo", "38"),
|
||||
Genre("Shoujo Ai", "98"),
|
||||
Genre("Shounen", "31"),
|
||||
Genre("Shounen Ai", "86"),
|
||||
Genre("Slice of life", "46"),
|
||||
Genre("Sports", "57"),
|
||||
Genre("Supernatural", "32"),
|
||||
Genre("Tragedy", "52"),
|
||||
Genre("Trọng Sinh", "82"),
|
||||
Genre("Truyện Màu", "92"),
|
||||
Genre("Webtoon", "55"),
|
||||
Genre("Xuyên Không", "88")
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue