TruyenTranh3Q: Update Icon, Update domain. Fix mangaDetailsParse, Fix parseDate (#10933)

* Update Icon

* Update domain, Fix Status, Fix ParseDate

* Bump Version

* = ̄ω ̄=
This commit is contained in:
are-are-are 2025-10-08 22:38:55 +07:00 committed by Draff
parent 07c8fd1080
commit 0e218bbfac
Signed by: Draff
GPG Key ID: E8A89F3211677653
7 changed files with 57 additions and 56 deletions

View File

@ -1,8 +1,8 @@
ext {
extName = 'TruyenTranh3Q'
extClass = '.TruyenTranh3Q'
baseUrl = 'https://truyentranh3q.com'
extVersionCode = 1
baseUrl = 'https://truyentranh3qb.com'
extVersionCode = 2
isNsfw = true
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.extension.vi.truyentranh3q
import android.net.ParseException
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
@ -10,6 +9,7 @@ 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 keiyoushi.utils.tryParse
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -27,7 +27,7 @@ import java.util.Locale
class TruyenTranh3Q : ParsedHttpSource() {
override val name: String = "TruyenTranh3Q"
override val lang: String = "vi"
override val baseUrl: String = "https://truyentranh3q.com"
override val baseUrl: String = "https://truyentranh3qb.com"
override val supportsLatest: Boolean = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
@ -74,15 +74,14 @@ class TruyenTranh3Q : ParsedHttpSource() {
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
// search
private val searchPath = "tim-kiem-nang-cao"
private val queryParam = "keyword"
private val searchUrl = "$baseUrl/tim-kiem-nang-cao"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/$searchPath".toHttpUrl().newBuilder()
val url = searchUrl.toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
// always add search query if present
if (query.isNotBlank()) {
url.addQueryParameter(queryParam, query)
url.addQueryParameter("keyword", query)
}
// process filters regardless of search query
@ -124,62 +123,64 @@ class TruyenTranh3Q : ParsedHttpSource() {
override fun mangaDetailsParse(document: Document): SManga {
return SManga.create().apply {
document.selectFirst(".book_info > .book_other")?.let { info ->
title = info.selectFirst("h1[itemprop=name]")!!.text()
title = info.select("h1[itemprop=name]").text()
author = info.selectFirst("ul.list-info li.author p.col-xs-9")?.text()
status = when (info.selectFirst("ul.list-info li.status p.col-xs-9")?.text()) {
"Đang Cập Nhật" -> SManga.ONGOING
"Hoàn Thành" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
status = parseStatus(info.selectFirst("ul.list-info li.status p.col-xs-9")?.text())
genre = info.select(".list01 li a").joinToString { it.text() }
}
description = document.selectFirst(".book_detail > .story-detail-info")?.text()
thumbnail_url = document.selectFirst(".book_detail > .book_info > .book_avatar > img")?.attr("abs:src")
description = document.select(".book_detail > .story-detail-info").joinToString { it.wholeText() }
thumbnail_url = document.selectFirst(".book_detail > .book_info > .book_avatar > img")?.absUrl("abs:src")
}
}
private fun parseStatus(status: String?): Int {
val ongoingStatus = listOf("Đang Cập Nhật", "Đang Tiến Hành")
val completedStatus = listOf("Hoàn Thành", "Đã Hoàn Thành")
return when {
status == null -> SManga.UNKNOWN
ongoingStatus.any { status.contains(it, ignoreCase = true) } -> SManga.ONGOING
completedStatus.any { status.contains(it, ignoreCase = true) } -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
// chapters
override fun chapterListSelector(): String = ".works-chapter-list .works-chapter-item"
private fun parseRelativeDate(dateString: String): Long {
val now = Calendar.getInstance()
val number = Regex("""(\d+)""").find(dateString)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
val timeUnits = mapOf(
"giây" to Calendar.SECOND,
"phút" to Calendar.MINUTE,
"giờ" to Calendar.HOUR,
"ngày" to Calendar.DAY_OF_YEAR,
"tuần" to Calendar.WEEK_OF_YEAR,
"tháng" to Calendar.MONTH,
"năm" to Calendar.YEAR,
)
// extract the number and time unit from the string
val parts = dateString.replace(" trước", "").split(" ")
if (parts.size < 2) return 0L
val number = parts[0].toIntOrNull() ?: return 0L
val unit = parts[1]
// find the matching time unit
val calendarUnit = timeUnits.entries.find { (key, _) -> unit.startsWith(key) }?.value ?: return 0L
// subtract the time
now.add(calendarUnit, -number)
return now.timeInMillis
return when {
listOf("giây", "giây trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.SECOND, -number); cal.timeInMillis
}
listOf("phút", "phút trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.MINUTE, -number); cal.timeInMillis
}
listOf("giờ", "giờ trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.HOUR, -number); cal.timeInMillis
}
listOf("ngày", "ngày trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.DAY_OF_YEAR, -number); cal.timeInMillis
}
listOf("tuần", "tuần trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.WEEK_OF_YEAR, -number); cal.timeInMillis
}
listOf("tháng", "tháng trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.MONTH, -number); cal.timeInMillis
}
listOf("năm", "năm trước").any { dateString.contains(it, ignoreCase = true) } -> {
cal.add(Calendar.YEAR, -number); cal.timeInMillis
}
else -> 0
}
}
private fun parseChapterDate(dateString: String): Long {
val relativeTime = parseRelativeDate(dateString)
return if (relativeTime != 0L) {
relativeTime
} else {
try {
dateFormat.parse(dateString)?.time ?: 0L
} catch (e: ParseException) {
0L
}
val listCal = listOf("giây", "phút", "giờ", "ngày", "tuần", "tháng", "năm", "trước")
return when {
listCal.any { dateString.contains(it, ignoreCase = true) } -> parseRelativeDate(dateString)
else -> dateFormat.tryParse(dateString)
}
}
@ -205,10 +206,10 @@ class TruyenTranh3Q : ParsedHttpSource() {
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
// filters
class SortFilter(name: String, val options: List<String>) : Filter.Select<String>(name, options.toTypedArray())
class StatusFilter(name: String, val options: List<String>) : Filter.Select<String>(name, options.toTypedArray())
class CountryFilter(name: String, val options: List<String>, val countryValues: List<String>) : Filter.Select<String>(name, options.toTypedArray())
class MinChapterFilter(name: String, val options: List<String>, val chapterValues: List<Int>) : Filter.Select<String>(name, options.toTypedArray())
class SortFilter(name: String, options: List<String>) : Filter.Select<String>(name, options.toTypedArray())
class StatusFilter(name: String, options: List<String>) : Filter.Select<String>(name, options.toTypedArray())
class CountryFilter(name: String, options: List<String>, val countryValues: List<String>) : Filter.Select<String>(name, options.toTypedArray())
class MinChapterFilter(name: String, options: List<String>, val chapterValues: List<Int>) : Filter.Select<String>(name, options.toTypedArray())
class Genre(name: String, val id: Int) : Filter.TriState(name)
class GenreFilter(name: String, state: List<Genre>) : Filter.Group<Genre>(name, state)
@ -246,7 +247,7 @@ class TruyenTranh3Q : ParsedHttpSource() {
private var genreList: List<Genre> = emptyList()
private var fetchGenreAttempts: Int = 0
private fun genresRequest() = GET("$baseUrl/$searchPath", headers)
private fun genresRequest() = GET(searchUrl, headers)
private fun parseGenres(document: Document): List<Genre> {
return document.select(".genre-item").mapIndexed { index, element ->