From 2c1789d7785a4f5c1453c2413cdf6c5ed11aedd7 Mon Sep 17 00:00:00 2001 From: beerpsi <92439990+beerpiss@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:39:57 +0700 Subject: [PATCH] TruyenGiHot: Update base URL and a lot of fixes (#747) --- src/vi/truyengihot/AndroidManifest.xml | 2 +- src/vi/truyengihot/build.gradle | 2 +- .../extension/vi/truyengihot/TruyenGiHot.kt | 526 ++++-------------- .../vi/truyengihot/TruyenGiHotFilters.kt | 137 +++++ .../vi/truyengihot/TruyenGiHotUtils.kt | 69 +++ 5 files changed, 308 insertions(+), 428 deletions(-) create mode 100644 src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotFilters.kt create mode 100644 src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotUtils.kt diff --git a/src/vi/truyengihot/AndroidManifest.xml b/src/vi/truyengihot/AndroidManifest.xml index 58d189dcf..b816a4e3b 100644 --- a/src/vi/truyengihot/AndroidManifest.xml +++ b/src/vi/truyengihot/AndroidManifest.xml @@ -11,7 +11,7 @@ - diff --git a/src/vi/truyengihot/build.gradle b/src/vi/truyengihot/build.gradle index 9220e4c6f..a42783509 100644 --- a/src/vi/truyengihot/build.gradle +++ b/src/vi/truyengihot/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'TruyenGiHot' extClass = '.TruyenGiHot' - extVersionCode = 3 + extVersionCode = 4 isNsfw = true } diff --git a/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHot.kt b/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHot.kt index 1314d2a54..fdb35e096 100644 --- a/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHot.kt +++ b/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHot.kt @@ -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() - val genresEx = mutableListOf() + 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() + .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 { + 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 { @@ -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>( + 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>, - state: Int = 0, - ) : UriFilter, Filter.Select(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 = 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 = 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> = 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>, - 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> = 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( - "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"), - ), - ) } diff --git a/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotFilters.kt b/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotFilters.kt new file mode 100644 index 000000000..433330d90 --- /dev/null +++ b/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotFilters.kt @@ -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>, + state: Int = 0, +) : UriFilter, Filter.Select(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>, + 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) : Filter.Group(name, state), UriFilter { + override fun addToUri(builder: HttpUrl.Builder) { + val incl = mutableListOf() + val excl = mutableListOf() + + 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>) : UriPartFilter("Nhóm dịch", "group_add", vals) + +internal class TagFilter(state: List) : GenreGroup("Tags", "tag", state) + +internal class ThemesFilter(state: List) : GenreGroup("Themes", "themes", state) + +internal fun getSortItems(): Array> = 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"), +) diff --git a/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotUtils.kt b/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotUtils.kt new file mode 100644 index 000000000..f86c27b25 --- /dev/null +++ b/src/vi/truyengihot/src/eu/kanade/tachiyomi/extension/vi/truyengihot/TruyenGiHotUtils.kt @@ -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 { + return element.select("span[data-val]").map { + Genre(it.text(), it.attr("data-val")) + } + } + + internal fun parseOptions(element: Element): List> { + 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") + } +}