mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-22 02:12:42 +01:00
TruyenGiHot: Update base URL and a lot of fixes (#747)
This commit is contained in:
parent
5dcb21b4c2
commit
2c1789d778
@ -11,7 +11,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="truyengihotne.net"
|
||||
<data android:host="truyengihotqua.net"
|
||||
android:scheme="https"
|
||||
android:pathPattern="/truyen-..*" />
|
||||
</intent-filter>
|
||||
|
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'TruyenGiHot'
|
||||
extClass = '.TruyenGiHot'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 4
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
package eu.kanade.tachiyomi.extension.vi.truyengihot
|
||||
|
||||
import android.util.Log
|
||||
import eu.kanade.tachiyomi.extension.vi.truyengihot.TruyenGiHotUtils.imgAttr
|
||||
import eu.kanade.tachiyomi.extension.vi.truyengihot.TruyenGiHotUtils.textWithNewlines
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
@ -10,38 +13,39 @@ 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 kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import rx.Single
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Calendar
|
||||
|
||||
class TruyenGiHot : ParsedHttpSource() {
|
||||
|
||||
override val name: String = "TruyenGiHot"
|
||||
|
||||
override val baseUrl: String = "https://truyengihotne.com"
|
||||
override val baseUrl: String = "https://truyengihotqua.com"
|
||||
|
||||
override val lang: String = "vi"
|
||||
|
||||
override val supportsLatest: Boolean = true
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(1)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder(): Headers.Builder =
|
||||
super.headersBuilder().add("Referer", "$baseUrl/")
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
@ -58,6 +62,7 @@ class TruyenGiHot : ParsedHttpSource() {
|
||||
getSortItems(),
|
||||
Filter.Sort.Selection(2, false),
|
||||
),
|
||||
CategoryFilter(0),
|
||||
),
|
||||
)
|
||||
|
||||
@ -77,6 +82,7 @@ class TruyenGiHot : ParsedHttpSource() {
|
||||
getSortItems(),
|
||||
Filter.Sort.Selection(0, false),
|
||||
),
|
||||
CategoryFilter(0),
|
||||
),
|
||||
)
|
||||
|
||||
@ -114,52 +120,36 @@ class TruyenGiHot : ParsedHttpSource() {
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url =
|
||||
"$baseUrl/tim-kiem-nang-cao.html?listType=table&page=$page".toHttpUrl().newBuilder()
|
||||
.apply {
|
||||
val genres = mutableListOf<String>()
|
||||
val genresEx = mutableListOf<String>()
|
||||
runCatching { fetchFilterOptions() }
|
||||
|
||||
val url =
|
||||
"$baseUrl/danh-sach-truyen.html?listType=thumb&page=$page".toHttpUrl().newBuilder()
|
||||
.apply {
|
||||
addQueryParameter("text_add", query)
|
||||
|
||||
(if (filters.isEmpty()) getFilterList() else filters).forEach {
|
||||
when (it) {
|
||||
is UriFilter -> it.addToUri(this)
|
||||
is GenreFilter -> it.state.forEach { genre ->
|
||||
when (genre.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> genres.add(genre.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> genresEx.add(genre.id)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
addQueryParameter("tag_add", genres.joinToString(","))
|
||||
addQueryParameter("tag_remove", genresEx.joinToString(","))
|
||||
}.build().toString()
|
||||
(if (filters.isEmpty()) getFilterList() else filters)
|
||||
.filterIsInstance<UriFilter>()
|
||||
.forEach { it.addToUri(this) }
|
||||
}.build()
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector(): String = "ul.cw-list li"
|
||||
override fun searchMangaSelector(): String = "ul.contentList li"
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
|
||||
val anchor = element.select("span.title a")
|
||||
setUrlWithoutDomain(anchor.attr("href"))
|
||||
title = anchor.text()
|
||||
thumbnail_url = baseUrl + element.select("span.thumb").attr("style")
|
||||
.substringAfter("url('")
|
||||
.substringBefore("')")
|
||||
thumbnail_url = element.selectFirst("span.thumb img")?.imgAttr()
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector(): String = "li.page-next a:not(.disabled)"
|
||||
override fun searchMangaNextPageSelector(): String = "li.page-next"
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
|
||||
title = document.select(".cover-title").text()
|
||||
author = document.select("p.cover-artist:contains(Tác giả) a").joinToString { it.text() }
|
||||
genre = document.select("a.manga-tags").joinToString { it.text().removePrefix("#") }
|
||||
thumbnail_url = document.select("div.cover-image img").attr("abs:src")
|
||||
thumbnail_url = document.selectFirst("div.cover-image img")?.imgAttr()
|
||||
|
||||
val tags = document.select("img.top-tags.top-tags-full").map {
|
||||
it.attr("src").substringAfterLast("/").substringBefore(".png")
|
||||
@ -171,41 +161,34 @@ class TruyenGiHot : ParsedHttpSource() {
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
description = document.select("div.product-synopsis-content").run {
|
||||
description = document.select("div.content div.textArea").run {
|
||||
select("p").first()?.prepend("|truyengihay-split|")
|
||||
text().substringAfter("|truyengihay-split|").substringBefore(" Xem thêm")
|
||||
textWithNewlines().substringAfter("|truyengihay-split|").substringBefore(" Xem thêm")
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector(): String = "ul.episode-list li a"
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val document = response.asJsoup()
|
||||
val contentType = document.select("ul.breadcrumb li")[1].text()
|
||||
|
||||
// Because they show up even with a manga filter in place
|
||||
if (contentType == "Novel" || contentType == "Anime") {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return document.select(chapterListSelector()).map {
|
||||
chapterFromElement(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector(): String = "ul#episode_list li a"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
|
||||
val infoBlock = element.selectFirst("span.info")!!
|
||||
name = infoBlock.select("span.no").text()
|
||||
date_upload = parseChapterDate(infoBlock.select("span.date").text())
|
||||
}
|
||||
|
||||
private fun parseChapterDate(date: String): Long {
|
||||
val trimmedDate = date.substringBefore(" trước").split(" ")
|
||||
|
||||
val calendar = Calendar.getInstance().apply {
|
||||
val amount = -trimmedDate[0].toInt()
|
||||
val field = when (trimmedDate[1]) {
|
||||
"giây" -> Calendar.SECOND
|
||||
"phút" -> Calendar.MINUTE
|
||||
"giờ" -> Calendar.HOUR_OF_DAY
|
||||
"ngày" -> Calendar.DAY_OF_MONTH
|
||||
"tuần" -> Calendar.WEEK_OF_MONTH
|
||||
"tháng" -> Calendar.MONTH
|
||||
"năm" -> Calendar.YEAR
|
||||
else -> Calendar.SECOND
|
||||
}
|
||||
add(field, amount)
|
||||
}
|
||||
|
||||
return calendar.timeInMillis
|
||||
date_upload = TruyenGiHotUtils.parseChapterDate(infoBlock.select("span.date").text())
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
@ -229,7 +212,7 @@ class TruyenGiHot : ParsedHttpSource() {
|
||||
|
||||
val formBody = FormBody.Builder()
|
||||
.add("token", token)
|
||||
.add("chapter_id", chapterInfo["cid"]!!)
|
||||
.add("chapter_id", chapterInfo["c_id"]!!)
|
||||
.add("m_slug", chapterInfo["mangaSLUG"]!!)
|
||||
.add("m_id", chapterInfo["mangaID"]!!)
|
||||
.add("chapter", chapterInfo["chapter"]!!)
|
||||
@ -247,386 +230,77 @@ class TruyenGiHot : ParsedHttpSource() {
|
||||
throw Exception("Truyện đã bị khoá!")
|
||||
}
|
||||
|
||||
return Jsoup.parseBodyFragment(pageHtml, baseUrl).select("img").mapIndexed { idx, it ->
|
||||
Page(idx, imageUrl = it.attr("abs:src"))
|
||||
return Jsoup.parseBodyFragment(pageHtml, baseUrl).select("img:not([src$=wattermark.png])").mapIndexed { idx, it ->
|
||||
Page(idx, imageUrl = it.imgAttr())
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
SearchTypeFilter(),
|
||||
CategoryFilter(),
|
||||
PublicationTypeFilter(),
|
||||
CountryFilter(),
|
||||
StatusFilter(),
|
||||
ScanlatorFilter(),
|
||||
SortFilter(getSortItems()),
|
||||
GenreFilter(),
|
||||
)
|
||||
override fun getFilterList(): FilterList {
|
||||
val filters = mutableListOf<Filter<*>>(
|
||||
CategoryFilter(),
|
||||
PublicationTypeFilter(),
|
||||
FormatTypeFilter(),
|
||||
MagazineFilter(),
|
||||
ExplicitFilter(),
|
||||
StatusFilter(),
|
||||
).also {
|
||||
if ((tags.isEmpty() && themes.isEmpty() && scanlators.isEmpty()) || fetchFiltersFailed) {
|
||||
it.add(0, Filter.Header("Nhấn 'Đặt lại' để hiện các bộ lọc"))
|
||||
it.add(1, Filter.Separator())
|
||||
}
|
||||
|
||||
interface UriFilter {
|
||||
fun addToUri(builder: HttpUrl.Builder)
|
||||
}
|
||||
if (scanlators.isNotEmpty()) {
|
||||
it.add(ScanlatorFilter(scanlators.toTypedArray()))
|
||||
}
|
||||
|
||||
open class UriPartFilter(
|
||||
name: String,
|
||||
private val query: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
state: Int = 0,
|
||||
) : UriFilter, Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter(query, vals[state].second)
|
||||
if (tags.isNotEmpty()) {
|
||||
it.add(TagFilter(tags))
|
||||
}
|
||||
|
||||
if (themes.isNotEmpty()) {
|
||||
it.add(ThemesFilter(themes))
|
||||
}
|
||||
|
||||
it.add(SortFilter(getSortItems()))
|
||||
}
|
||||
|
||||
return FilterList(filters)
|
||||
}
|
||||
|
||||
private class SearchTypeFilter : UriPartFilter(
|
||||
"Tìm từ khoá theo",
|
||||
"text_type",
|
||||
arrayOf(
|
||||
Pair("Tên truyện", "name"),
|
||||
Pair("Tác giả", "authors"),
|
||||
),
|
||||
)
|
||||
private var tags: List<Genre> = emptyList()
|
||||
|
||||
private class CategoryFilter : UriPartFilter(
|
||||
"Phân loại",
|
||||
"type_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", ""),
|
||||
Pair("Truyện 18+", "truyen-tranh"),
|
||||
Pair("Ngôn tình", "ngon-tinh"),
|
||||
),
|
||||
)
|
||||
private var themes: List<Genre> = emptyList()
|
||||
|
||||
private class PublicationTypeFilter : UriPartFilter(
|
||||
"Thể loại",
|
||||
"genre_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", ""),
|
||||
Pair("Manga", "manga"),
|
||||
Pair("Manhua", "manhua"),
|
||||
Pair("Manhwa", "manhwa"),
|
||||
Pair("Tự sáng tác", "tu-sang-tac"),
|
||||
Pair("Khác", "khac"),
|
||||
),
|
||||
)
|
||||
private var scanlators: List<Pair<String, String>> = emptyList()
|
||||
|
||||
private class CountryFilter : UriPartFilter(
|
||||
"Quốc gia",
|
||||
"country_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", ""),
|
||||
Pair("Âu Mỹ", "au-my"),
|
||||
Pair("Hàn Quốc", "han-quoc"),
|
||||
Pair("Khác", "khac"),
|
||||
Pair("Nhật Bản", "nhat-ban"),
|
||||
Pair("Trung Quốc", "trung-quoc"),
|
||||
Pair("Việt Nam", "viet-nam"),
|
||||
),
|
||||
)
|
||||
private var fetchFiltersFailed = false
|
||||
|
||||
private class StatusFilter : UriPartFilter(
|
||||
"Trạng thái",
|
||||
"status_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("Full", "1"),
|
||||
Pair("Ongoing", "2"),
|
||||
Pair("Drop", "3"),
|
||||
),
|
||||
)
|
||||
private var fetchFiltersAttempts = 0
|
||||
|
||||
private class SortFilter(
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
state: Selection = Selection(2, false),
|
||||
) : UriFilter,
|
||||
Filter.Sort("Sắp xếp", vals.map { it.first }.toTypedArray(), state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter("order_add", vals[state?.index ?: 2].second)
|
||||
builder.addQueryParameter(
|
||||
"order_by_add",
|
||||
if (state?.ascending == true) "ASC" else "DESC",
|
||||
)
|
||||
private fun fetchFilterOptions() {
|
||||
if (fetchFiltersAttempts > 3 || (fetchFiltersAttempts > 0 && !fetchFiltersFailed)) {
|
||||
return
|
||||
}
|
||||
|
||||
Single.fromCallable {
|
||||
val document = client.newCall(GET("$baseUrl/danh-sach-truyen.html", headers)).execute().asJsoup()
|
||||
|
||||
val result = runCatching {
|
||||
tags = TruyenGiHotUtils.parseThemes(document.selectFirst("#contentTag")!!)
|
||||
themes = TruyenGiHotUtils.parseThemes(document.selectFirst("#contentTheme")!!)
|
||||
scanlators = TruyenGiHotUtils.parseOptions(document.selectFirst("#contentGroup")!!)
|
||||
}
|
||||
.onFailure {
|
||||
Log.e("TruyenGiHot", "Could not fetch filtering options", it)
|
||||
}
|
||||
|
||||
fetchFiltersFailed = result.isFailure
|
||||
fetchFiltersAttempts++
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
private fun getSortItems(): Array<Pair<String, String>> = arrayOf(
|
||||
Pair("Mới cập nhật", "last_update"),
|
||||
Pair("Lượt xem", "views"),
|
||||
Pair("Hot", "total_vote"),
|
||||
Pair("Vote", "count_vote"),
|
||||
Pair("Tên A-Z", "name"),
|
||||
)
|
||||
|
||||
private class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
// console.log([...document.querySelectorAll(".wrapper-search-tag .search-content span")].map(e => `Genre("${e.innerText.trim()}", "${e.dataset.val}")`).join(",\n"))
|
||||
private class GenreFilter : Filter.Group<Genre>(
|
||||
"Chủ đề",
|
||||
listOf(
|
||||
Genre("16+", "16"),
|
||||
Genre("18+", "18"),
|
||||
Genre("1Vs1", "1vs1"),
|
||||
Genre("3d", "3d"),
|
||||
Genre("3some", "3some"),
|
||||
Genre("Ác nữ", "ac-nu"),
|
||||
Genre("Ác Quỷ", "ac-quy"),
|
||||
Genre("Action", "action"),
|
||||
Genre("Adult", "adult"),
|
||||
Genre("Adventure", "adventure"),
|
||||
Genre("ai cập", "ai-cap"),
|
||||
Genre("Âm Nhạc", "am-nhac"),
|
||||
Genre("Anh chị em", "anh-chi-em"),
|
||||
Genre("anh chị em kế", "anh-chi-em-ke"),
|
||||
Genre("anh hùng", "anh-hung"),
|
||||
Genre("Anime", "anime"),
|
||||
Genre("artist cg", "artist-cg"),
|
||||
Genre("Âu Cổ", "au-co"),
|
||||
Genre("Bách Hợp", "bach-hop"),
|
||||
Genre("bad boy", "bad-boy"),
|
||||
Genre("bạn thân", "ban-than"),
|
||||
Genre("Bạo Lực", "bao-luc"),
|
||||
Genre("Bdsm", "bdsm"),
|
||||
Genre("BE", "be"),
|
||||
Genre("Bí Ẩn", "bi-an"),
|
||||
Genre("Bi kịch", "bi-kich"),
|
||||
Genre("bị vứt bỏ", "bi-vut-bo"),
|
||||
Genre("big breast", "big-breast"),
|
||||
Genre("BL/Bách hợp", "bl-bach-hop"),
|
||||
Genre("blowjobs", "blowjobs"),
|
||||
Genre("bỏ trốn", "bo-tron"),
|
||||
Genre("cái chết", "cai-chet"),
|
||||
Genre("Cận đại", "can-dai"),
|
||||
Genre("Cấu Huyết", "cau-huyet"),
|
||||
Genre("Châu Âu", "chau-au"),
|
||||
Genre("che", "che"),
|
||||
Genre("Chiến Tranh", "chien-tranh"),
|
||||
Genre("Chuyển Sinh", "chuyen-sinh"),
|
||||
Genre("Chuyển Thế", "chuyen-the"),
|
||||
Genre("Cổ Đại", "co-dai"),
|
||||
Genre("Cổ Trang", "co-trang"),
|
||||
Genre("con gái nô", "con-gai-no"),
|
||||
Genre("con ngoài dã thú", "con-ngoai-da-thu"),
|
||||
Genre("công sở", "cong-so"),
|
||||
Genre("Cung Đấu", "cung-dau"),
|
||||
Genre("đẹp trai Nam chính", "dep-trai-nam-chinh"),
|
||||
Genre("Dị Giới", "di-gioi"),
|
||||
Genre("Dị Năng", "di-nang"),
|
||||
Genre("Điền Văn", "dien-van"),
|
||||
Genre("dl site", "dl-site"),
|
||||
Genre("Đô Thị", "do-thi"),
|
||||
Genre("Đoản Văn", "doan-van"),
|
||||
Genre("độc ác Nữ chính", "doc-ac-nu-chinh"),
|
||||
Genre("Drama", "drama"),
|
||||
Genre("Được nhận nuôi", "duoc-nhan-nuoi"),
|
||||
Genre("Ecchi", "ecchi"),
|
||||
Genre("Fantasy", "fantasy"),
|
||||
Genre("Game", "game"),
|
||||
Genre("Gây cấn", "gay-can"),
|
||||
Genre("Gia Đình", "gia-dinh"),
|
||||
Genre("giả gái/trai", "gia-gai-trai"),
|
||||
Genre("Giai cấp quý tộc", "giai-cap-quy-toc"),
|
||||
Genre("giam cầm", "giam-cam"),
|
||||
Genre("giang hồ", "giang-ho"),
|
||||
Genre("Hài Hước", "hai-huoc"),
|
||||
Genre("hàng khủng", "hang-khung"),
|
||||
Genre("hàng xóm", "hang-xom"),
|
||||
Genre("Hành Động", "hanh-dong"),
|
||||
Genre("Harem", "harem"),
|
||||
Genre("HE", "he"),
|
||||
Genre("Hệ Thống", "he-thong"),
|
||||
Genre("Hentai", "hentai"),
|
||||
Genre("Hiện Đại", "hien-dai"),
|
||||
Genre("Hiểu lầm", "hieu-lam"),
|
||||
Genre("Hoán Đổi", "hoan-doi"),
|
||||
Genre("Hoàng gia", "hoang-gia"),
|
||||
Genre("Hoạt Hình", "hoat-hinh"),
|
||||
Genre("Học Đường", "hoc-duong"),
|
||||
Genre("học sinh", "hoc-sinh"),
|
||||
Genre("hối hận", "hoi-han"),
|
||||
Genre("Hồi hộp", "hoi-hop"),
|
||||
Genre("Huyền Ảo", "huyen-ao"),
|
||||
Genre("Ít che", "it-che"),
|
||||
Genre("kaka*page", "kaka-page"),
|
||||
Genre("khổ dâm", "kho-dam"),
|
||||
Genre("Khoa Học", "khoa-hoc"),
|
||||
Genre("không che", "khong-che"),
|
||||
Genre("Không Màu", "khong-mau"),
|
||||
Genre("Kiếm Hiệp", "kiem-hiep"),
|
||||
Genre("Kinh Dị", "kinh-di"),
|
||||
Genre("Lãng mạn", "lang-man"),
|
||||
Genre("lezh*n", "lezh-n"),
|
||||
Genre("Lịch Sử", "lich-su"),
|
||||
Genre("Light Novel", "light-novel"),
|
||||
Genre("Live action", "live-action"),
|
||||
Genre("loạn luân", "loan-luan"),
|
||||
Genre("Loli", "loli"),
|
||||
Genre("ma", "ma"),
|
||||
Genre("Ma Cà Rồng", "ma-ca-rong"),
|
||||
Genre("mang thai", "mang-thai"),
|
||||
Genre("Manga", "manga"),
|
||||
Genre("Manhua", "manhua"),
|
||||
Genre("Manhwa", "manhwa"),
|
||||
Genre("Mạt Thế", "mat-the"),
|
||||
Genre("mẹ kế", "me-ke"),
|
||||
Genre("Mô tả đế chế", "mo-ta-de-che"),
|
||||
Genre("mystery", "mystery"),
|
||||
Genre("nam duy nhất", "nam-duy-nhat"),
|
||||
Genre("nav*r", "nav-r"),
|
||||
Genre("nét vẽ Đẹp", "net-ve-dep"),
|
||||
Genre("Netflix", "netflix"),
|
||||
Genre("Ngây thơ", "ngay-tho"),
|
||||
Genre("ngoại tình", "ngoai-tinh"),
|
||||
Genre("Ngôn Tình", "ngon-tinh"),
|
||||
Genre("Ngược", "nguoc"),
|
||||
Genre("người hầu", "nguoi-hau"),
|
||||
Genre("nhân thú", "nhan-thu"),
|
||||
Genre("Nhân vật chính", "nhan-vat-chinh"),
|
||||
Genre("nhân vật game", "nhan-vat-game"),
|
||||
Genre("Ninja", "ninja"),
|
||||
Genre("nô lệ", "no-le"),
|
||||
Genre("ntr", "ntr"),
|
||||
Genre("Nữ Cường", "nu-cuong"),
|
||||
Genre("nữ duy nhất", "nu-duy-nhat"),
|
||||
Genre("Nữ Phụ", "nu-phu"),
|
||||
Genre("Oan gia", "oan-gia"),
|
||||
Genre("OE", "oe"),
|
||||
Genre("old man", "old-man"),
|
||||
Genre("oneshot", "oneshot"),
|
||||
Genre("otome game", "otome-game"),
|
||||
Genre("otp", "otp"),
|
||||
Genre("phản diện", "phan-dien"),
|
||||
Genre("Phép Thuật", "phep-thuat"),
|
||||
Genre("Phiêu Lưu", "phieu-luu"),
|
||||
Genre("Phim Bộ", "phim-bo"),
|
||||
Genre("Phim Chiếu Rạp", "phim-chieu-rap"),
|
||||
Genre("Phim Lẻ", "phim-le"),
|
||||
Genre("prologue", "prologue"),
|
||||
Genre("psychological", "psychological"),
|
||||
Genre("quái vật", "quai-vat"),
|
||||
Genre("Quân Sự", "quan-su"),
|
||||
Genre("Quý tộc", "quy-toc"),
|
||||
Genre("rape", "rape"),
|
||||
Genre("Sắc", "sac"),
|
||||
Genre("Sạch", "sach"),
|
||||
Genre("SE", "se"),
|
||||
Genre("seinen", "seinen"),
|
||||
Genre("sex toy", "sex-toy"),
|
||||
Genre("shoujo", "shoujo"),
|
||||
Genre("Shoujo Ai", "shoujo-ai"),
|
||||
Genre("Siêu Năng Lực", "sieu-nang-luc"),
|
||||
Genre("slice of life", "slice-of-life"),
|
||||
Genre("Smut", "smut"),
|
||||
Genre("Sở thích tra tấn", "so-thich-tra-tan"),
|
||||
Genre("Sủng", "sung"),
|
||||
Genre("supernatural", "supernatural"),
|
||||
Genre("tái sinh", "tai-sinh"),
|
||||
Genre("Tâm Lý", "tam-ly"),
|
||||
Genre("thẩm du", "tham-du"),
|
||||
Genre("Thám Hiểm", "tham-hiem"),
|
||||
Genre("Thần Thoại", "than-thoai"),
|
||||
Genre("thánh nữ", "thanh-nu"),
|
||||
Genre("thanh xuân vườn trường", "thanh-xuan-vuon-truong"),
|
||||
Genre("thầy/cô giáo", "thay-co-giao"),
|
||||
Genre("thay Đổi cốt truyện", "thay-doi-cot-truyen"),
|
||||
Genre("thay Đổi giới tính", "thay-doi-gioi-tinh"),
|
||||
Genre("Thể Thao", "the-thao"),
|
||||
Genre("thuần hóa", "thuan-hoa"),
|
||||
Genre("Tiên Hiệp", "tien-hiep"),
|
||||
Genre("Tiểu Thuyết", "tieu-thuyet"),
|
||||
Genre("Tình Cảm", "tinh-cam"),
|
||||
Genre("Tình Tay Ba", "tinh-tay-ba"),
|
||||
Genre("Tổng Tài", "tong-tai"),
|
||||
Genre("trà xanh", "tra-xanh"),
|
||||
Genre("Trailer", "trailer"),
|
||||
Genre("Trinh Thám", "trinh-tham"),
|
||||
Genre("Trọng Sinh", "trong-sinh"),
|
||||
Genre("Truyện Màu", "truyen-mau"),
|
||||
Genre("tsundere", "tsundere"),
|
||||
Genre("Tự Sáng Tác", "tu-sang-tac"),
|
||||
Genre("tưởng tượng", "tuong-tuong"),
|
||||
Genre("tuyển tập", "tuyen-tap"),
|
||||
Genre("vị hôn thê", "vi-hon-the"),
|
||||
Genre("Việt Nam", "viet-nam"),
|
||||
Genre("Võ Thuật", "vo-thuat"),
|
||||
Genre("Vũ Trụ", "vu-tru"),
|
||||
Genre("Webtoon", "webtoon"),
|
||||
Genre("xúc tua", "xuc-tua"),
|
||||
Genre("Xuyên Không", "xuyen-khong"),
|
||||
Genre("Xuyên không/Trọng sinh", "xuyen-khong-trong-sinh"),
|
||||
Genre("Yandere", "yandere"),
|
||||
Genre("Yuri", "yuri"),
|
||||
),
|
||||
)
|
||||
|
||||
// console.log([...document.querySelectorAll(".wrapper-search-group .search-content span")].map(e => `Pair("${e.innerText.trim()}", "${e.dataset.val}")`).join(",\n"))
|
||||
private class ScanlatorFilter : UriPartFilter(
|
||||
"Nhóm dịch",
|
||||
"group_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("Aling - Tiểu Thuyết", "383"),
|
||||
Pair("Angela Diệp Lạc", "361"),
|
||||
Pair("AUTHOR TIỂU MÂY", "362"),
|
||||
Pair("Boom novel", "403"),
|
||||
Pair("Cà chua Team", "421"),
|
||||
Pair("Camellia", "300"),
|
||||
Pair("Cậu Muốn Review Gì Nào?", "342"),
|
||||
Pair("Chloe's Library", "392"),
|
||||
Pair("Delion", "376"),
|
||||
Pair("Ecchi Land", "26"),
|
||||
Pair("Fluer", "396"),
|
||||
Pair("Gangster", "327"),
|
||||
Pair("Hien serena", "330"),
|
||||
Pair("Hoạ Y", "417"),
|
||||
Pair("Khu Vườn Bí Mật Của Rosaria", "401"),
|
||||
Pair("Laziel", "377"),
|
||||
Pair("Lazy Bee", "420"),
|
||||
Pair("Lil Pan", "334"),
|
||||
Pair("Lindy", "399"),
|
||||
Pair("Lọ Lem Hangul", "6"),
|
||||
Pair("Lycoris Radiata - Tiểu Hoa", "407"),
|
||||
Pair("MARY CƠM TRÓ", "423"),
|
||||
Pair("Mary Hạ Lục", "38"),
|
||||
Pair("Mây", "349"),
|
||||
Pair("Mảy Dus GL", "425"),
|
||||
Pair("Mảy Lành Mạnh", "424"),
|
||||
Pair("Mây Mây", "409"),
|
||||
Pair("meoluoihamchoi", "385"),
|
||||
Pair("Miêu Tặc", "343"),
|
||||
Pair("Mộc Trà", "306"),
|
||||
Pair("Một Chiếc Mèo Màu Đen", "390"),
|
||||
Pair("Nam Tử Sa Page", "20"),
|
||||
Pair("Nô Vồ", "393"),
|
||||
Pair("NỒI CƠM TRÓ", "382"),
|
||||
Pair("NỒI CƠM TRÓ 18+", "426"),
|
||||
Pair("Ổ Của Sien", "321"),
|
||||
Pair("Reviewer", "369"),
|
||||
Pair("Reviews", "419"),
|
||||
Pair("RINNIE", "341"),
|
||||
Pair("Rose The One", "337"),
|
||||
Pair("Roselight Team", "402"),
|
||||
Pair("Song Tử", "305"),
|
||||
Pair("The Present Translator", "404"),
|
||||
Pair("Thiên Mộc Thất Tú", "304"),
|
||||
Pair("Thư Viện Latsya", "370"),
|
||||
Pair("Tiệm Kẹo Dẻo Ngòn Ngon", "418"),
|
||||
Pair("Tiểu Miêu Ngốc", "395"),
|
||||
Pair("Tiểu Thuyết Nhà Mây", "347"),
|
||||
Pair("Tiểu Vũ", "360"),
|
||||
Pair("TIỂU VY", "388"),
|
||||
Pair("tieu.yet", "355"),
|
||||
Pair("Trà Và Bánh", "40"),
|
||||
Pair("Traham", "319"),
|
||||
Pair("Truyện dịch Team Behira", "410"),
|
||||
Pair("Truyện Tổng Hợp", "23"),
|
||||
Pair("Windyzzz", "379"),
|
||||
Pair("Xóm Bán Hoa", "364"),
|
||||
Pair("Yu", "406"),
|
||||
Pair("Đào Lý Tửu", "345"),
|
||||
Pair("Đảo San Hô", "397"),
|
||||
Pair("Điền Thất", "373"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,137 @@
|
||||
package eu.kanade.tachiyomi.extension.vi.truyengihot
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
interface UriFilter {
|
||||
fun addToUri(builder: HttpUrl.Builder)
|
||||
}
|
||||
|
||||
open class UriPartFilter(
|
||||
name: String,
|
||||
private val query: String,
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
state: Int = 0,
|
||||
) : UriFilter, Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter(query, vals[state].second)
|
||||
}
|
||||
}
|
||||
|
||||
internal class SortFilter(
|
||||
private val vals: Array<Pair<String, String>>,
|
||||
state: Selection = Selection(2, false),
|
||||
) : UriFilter,
|
||||
Filter.Sort("Sắp xếp", vals.map { it.first }.toTypedArray(), state) {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
builder.addQueryParameter("order_add", vals[state?.index ?: 2].second)
|
||||
builder.addQueryParameter(
|
||||
"order_by_add",
|
||||
if (state?.ascending == true) "ASC" else "DESC",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class Genre(name: String, val id: String) : Filter.TriState(name)
|
||||
|
||||
internal open class GenreGroup(name: String, private val key: String, state: List<Genre>) : Filter.Group<Genre>(name, state), UriFilter {
|
||||
override fun addToUri(builder: HttpUrl.Builder) {
|
||||
val incl = mutableListOf<String>()
|
||||
val excl = mutableListOf<String>()
|
||||
|
||||
state.forEach {
|
||||
when (it.state) {
|
||||
TriState.STATE_INCLUDE -> incl.add(it.id)
|
||||
TriState.STATE_EXCLUDE -> excl.add(it.id)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
builder.addQueryParameter("${key}_add", incl.joinToString(","))
|
||||
builder.addQueryParameter("${key}_remove", excl.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
internal class CategoryFilter(state: Int = 0) : UriPartFilter(
|
||||
"Phân loại",
|
||||
"type_add",
|
||||
arrayOf(
|
||||
// The site also has novels and anime.
|
||||
Pair("Tất cả", "manga"),
|
||||
Pair("Truyện 18+", "audult"),
|
||||
Pair("Ngôn tình", "noaudult"),
|
||||
),
|
||||
state,
|
||||
)
|
||||
|
||||
internal class PublicationTypeFilter : UriPartFilter(
|
||||
"Thể loại",
|
||||
"genre_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("Manga", "29"),
|
||||
Pair("Manhua", "30"),
|
||||
Pair("Manhwa", "31"),
|
||||
Pair("Tự sáng tác", "206"),
|
||||
),
|
||||
)
|
||||
|
||||
internal class FormatTypeFilter : UriPartFilter(
|
||||
"Format",
|
||||
"format_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("R15+", "307"),
|
||||
Pair("R16+", "56"),
|
||||
Pair("R18+", "128"),
|
||||
Pair("R21+", "302"),
|
||||
),
|
||||
)
|
||||
|
||||
internal class MagazineFilter : UriPartFilter(
|
||||
"Magazines",
|
||||
"magazine_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("DL Site", "215"),
|
||||
Pair("kaka*page", "217"),
|
||||
Pair("lezh*n", "216"),
|
||||
Pair("nav*r", "218"),
|
||||
),
|
||||
)
|
||||
|
||||
internal class StatusFilter : UriPartFilter(
|
||||
"Trạng thái",
|
||||
"status_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("Full", "1"),
|
||||
Pair("Ongoing", "2"),
|
||||
Pair("Drop", "3"),
|
||||
),
|
||||
)
|
||||
|
||||
internal class ExplicitFilter : UriPartFilter(
|
||||
"Explicit",
|
||||
"explicit_add",
|
||||
arrayOf(
|
||||
Pair("Tất cả", "0"),
|
||||
Pair("Ecchi", "21"),
|
||||
Pair("Hentai", "73"),
|
||||
Pair("Oneshot", "230"),
|
||||
),
|
||||
)
|
||||
|
||||
internal class ScanlatorFilter(vals: Array<Pair<String, String>>) : UriPartFilter("Nhóm dịch", "group_add", vals)
|
||||
|
||||
internal class TagFilter(state: List<Genre>) : GenreGroup("Tags", "tag", state)
|
||||
|
||||
internal class ThemesFilter(state: List<Genre>) : GenreGroup("Themes", "themes", state)
|
||||
|
||||
internal fun getSortItems(): Array<Pair<String, String>> = arrayOf(
|
||||
Pair("Mới cập nhật", "last_update"),
|
||||
Pair("Lượt xem", "views"),
|
||||
Pair("Rating", "rating"),
|
||||
Pair("Vote", "vote_c"),
|
||||
Pair("Tên A-Z", "name"),
|
||||
)
|
@ -0,0 +1,69 @@
|
||||
package eu.kanade.tachiyomi.extension.vi.truyengihot
|
||||
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
object TruyenGiHotUtils {
|
||||
private val dateFormat: SimpleDateFormat by lazy {
|
||||
SimpleDateFormat("dd.M.yy", Locale.US).apply {
|
||||
timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseChapterDate(date: String): Long {
|
||||
val trimmedDate = date.split(" ")
|
||||
|
||||
if (trimmedDate.size < 2) {
|
||||
return runCatching {
|
||||
dateFormat.parse(date)!!.time
|
||||
}.getOrDefault(0L)
|
||||
}
|
||||
|
||||
val calendar = Calendar.getInstance().apply {
|
||||
val amount = -trimmedDate[0].toInt()
|
||||
val field = when (trimmedDate[1]) {
|
||||
"giây" -> Calendar.SECOND
|
||||
"phút" -> Calendar.MINUTE
|
||||
"giờ" -> Calendar.HOUR_OF_DAY
|
||||
"ngày" -> Calendar.DAY_OF_MONTH
|
||||
"tuần" -> Calendar.WEEK_OF_MONTH
|
||||
"tháng" -> Calendar.MONTH
|
||||
"năm" -> Calendar.YEAR
|
||||
else -> Calendar.SECOND
|
||||
}
|
||||
|
||||
add(field, amount)
|
||||
}
|
||||
|
||||
return calendar.timeInMillis
|
||||
}
|
||||
|
||||
internal fun parseThemes(element: Element): List<Genre> {
|
||||
return element.select("span[data-val]").map {
|
||||
Genre(it.text(), it.attr("data-val"))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseOptions(element: Element): List<Pair<String, String>> {
|
||||
return element.select("span[data-val]").map {
|
||||
Pair(it.text(), it.attr("data-val"))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Element.imgAttr() = when {
|
||||
hasAttr("data-cfsrc") -> absUrl("data-cfsrc")
|
||||
hasAttr("data-lazy-src") -> absUrl("data-lazy-src")
|
||||
hasAttr("data-src") -> absUrl("data-src")
|
||||
hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ")
|
||||
else -> absUrl("src")
|
||||
}
|
||||
|
||||
internal fun Elements.textWithNewlines() = run {
|
||||
select("p, br").prepend("\\n")
|
||||
text().replace("\\n", "\n").replace("\n ", "\n")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user