commit
794a737b6d
@ -1 +1 @@
|
|||||||
total: 1178
|
total: 1179
|
||||||
@ -0,0 +1,295 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.vi
|
||||||
|
|
||||||
|
import androidx.collection.arraySetOf
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@MangaSourceParser("GOCTRUYENTRANH", "Góc Truyện Tranh", "vi")
|
||||||
|
internal class GocTruyenTranh(context: MangaLoaderContext) :
|
||||||
|
PagedMangaParser(context, MangaParserSource.GOCTRUYENTRANH, 30) {
|
||||||
|
|
||||||
|
override val configKeyDomain = ConfigKey.Domain("goctruyentranh.net")
|
||||||
|
|
||||||
|
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||||
|
super.onCreateConfig(keys)
|
||||||
|
keys.add(userAgentKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP)
|
||||||
|
|
||||||
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||||
|
SortOrder.UPDATED,
|
||||||
|
SortOrder.NEWEST,
|
||||||
|
SortOrder.NEWEST_ASC,
|
||||||
|
SortOrder.ALPHABETICAL,
|
||||||
|
SortOrder.RATING,
|
||||||
|
SortOrder.POPULARITY
|
||||||
|
)
|
||||||
|
|
||||||
|
override val filterCapabilities: MangaListFilterCapabilities
|
||||||
|
get() = MangaListFilterCapabilities(
|
||||||
|
isMultipleTagsSupported = true,
|
||||||
|
isTagsExclusionSupported = false,
|
||||||
|
isSearchSupported = true,
|
||||||
|
isSearchWithFiltersSupported = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getFilterOptions() = MangaListFilterOptions(
|
||||||
|
availableTags = availableTags(),
|
||||||
|
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
|
||||||
|
availableContentTypes = EnumSet.of(
|
||||||
|
ContentType.MANGA,
|
||||||
|
ContentType.MANHWA,
|
||||||
|
ContentType.MANHUA,
|
||||||
|
ContentType.OTHER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
||||||
|
val url = buildString {
|
||||||
|
append("https://")
|
||||||
|
append(domain)
|
||||||
|
append("/baseapi/comics/filterComic")
|
||||||
|
append("?keyword=")
|
||||||
|
append(filter.query?.urlEncoded() ?: "")
|
||||||
|
|
||||||
|
if (filter.tags.isNotEmpty()) {
|
||||||
|
append("&categories=")
|
||||||
|
append(filter.tags.joinToString(",") { tag ->
|
||||||
|
availableTags().find { it.title == tag.title }?.key ?: tag.key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
append("&status=")
|
||||||
|
when {
|
||||||
|
filter.states.isEmpty() -> append("")
|
||||||
|
filter.states.size > 1 -> append("")
|
||||||
|
else -> append(
|
||||||
|
when (filter.states.first()) {
|
||||||
|
MangaState.ONGOING -> "0"
|
||||||
|
MangaState.FINISHED -> "1"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
append("&sort=")
|
||||||
|
append(
|
||||||
|
when (order) {
|
||||||
|
SortOrder.UPDATED -> "recently_updated"
|
||||||
|
SortOrder.NEWEST -> "latest"
|
||||||
|
SortOrder.NEWEST_ASC -> "oldest"
|
||||||
|
SortOrder.RATING -> "rating"
|
||||||
|
SortOrder.ALPHABETICAL -> "alphabet"
|
||||||
|
SortOrder.POPULARITY -> "mostView"
|
||||||
|
else -> "recently_updated"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (filter.types.isNotEmpty()) {
|
||||||
|
append("&country=")
|
||||||
|
append(
|
||||||
|
filter.types.joinToString(",") {
|
||||||
|
when (it) {
|
||||||
|
ContentType.MANGA -> "manga"
|
||||||
|
ContentType.MANHWA -> "manhwa"
|
||||||
|
ContentType.MANHUA -> "manhua"
|
||||||
|
ContentType.OTHER -> "other"
|
||||||
|
else -> "manga"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
append("&page=")
|
||||||
|
append(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
val json = webClient.httpGet(url).parseJson()
|
||||||
|
val data = json.getJSONObject("comics").getJSONArray("data")
|
||||||
|
|
||||||
|
return List(data.length()) { i ->
|
||||||
|
val item = data.getJSONObject(i)
|
||||||
|
val slug = item.getString("slug")
|
||||||
|
val mangaUrl = buildString {
|
||||||
|
append("https://")
|
||||||
|
append(domain)
|
||||||
|
append("/")
|
||||||
|
append(slug)
|
||||||
|
}
|
||||||
|
|
||||||
|
val categories = item.optJSONArray("categories")
|
||||||
|
val tags = if (categories != null) {
|
||||||
|
List(categories.length()) { j ->
|
||||||
|
val category = categories.getJSONObject(j)
|
||||||
|
MangaTag(
|
||||||
|
key = category.getString("id"),
|
||||||
|
title = category.getString("name").toTitleCase(sourceLocale),
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}.toSet()
|
||||||
|
} else {
|
||||||
|
emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
|
Manga(
|
||||||
|
id = generateUid(mangaUrl),
|
||||||
|
url = "/$slug",
|
||||||
|
publicUrl = mangaUrl,
|
||||||
|
title = item.getString("name"),
|
||||||
|
altTitle = item.optString("origin_name").takeIf { it.isNotEmpty() },
|
||||||
|
description = item.optString("content"),
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
isNsfw = isNsfwSource,
|
||||||
|
coverUrl = item.optString("thumbnail"),
|
||||||
|
tags = tags,
|
||||||
|
state = when (item.optString("status")) {
|
||||||
|
"0" -> MangaState.ONGOING
|
||||||
|
"1" -> MangaState.FINISHED
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
source = source,
|
||||||
|
author = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDetails(manga: Manga): Manga {
|
||||||
|
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||||
|
return manga.copy(
|
||||||
|
rating = doc.selectFirst("div > span.leading-none")?.text()?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||||
|
author = doc.selectFirst("div:contains(Tác giả) + div")?.text()?.takeIf { it.isNotEmpty() },
|
||||||
|
chapters = doc.select("ul[itemtype='https://schema.org/ItemList'] li").mapChapters(reversed = true) { i, li ->
|
||||||
|
val a = li.selectFirstOrThrow("a")
|
||||||
|
val href = a.attrAsRelativeUrl("href")
|
||||||
|
val name = li.selectFirst("div.w-\\[50\\%\\].truncate.flex")?.text() ?: ""
|
||||||
|
val dateText = li.selectFirst("div.w-\\[50\\%\\].truncate.text-center")?.text()
|
||||||
|
MangaChapter(
|
||||||
|
id = generateUid(href),
|
||||||
|
name = name,
|
||||||
|
number = i + 1f,
|
||||||
|
volume = 0,
|
||||||
|
url = href,
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = parseChapterDate(dateText),
|
||||||
|
branch = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||||
|
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||||
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||||
|
return doc.select("img.lozad[data-src]").map { img ->
|
||||||
|
val url = img.attr("data-src")
|
||||||
|
MangaPage(
|
||||||
|
id = generateUid(url),
|
||||||
|
url = url,
|
||||||
|
preview = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseChapterDate(dateText: String?): Long {
|
||||||
|
if (dateText == null) return 0
|
||||||
|
|
||||||
|
val number = dateText.filter { it.isDigit() }.toIntOrNull() ?: return 0
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
dateText.contains("phút trước") -> {
|
||||||
|
now - (number * 60 * 1000L)
|
||||||
|
}
|
||||||
|
dateText.contains("giờ trước") -> {
|
||||||
|
now - (number * 60 * 60 * 1000L)
|
||||||
|
}
|
||||||
|
dateText.contains("ngày trước") -> {
|
||||||
|
now - (number * 24 * 60 * 60 * 1000L)
|
||||||
|
}
|
||||||
|
else -> 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun availableTags() = arraySetOf(
|
||||||
|
MangaTag("Action", "1", source),
|
||||||
|
MangaTag("Adventure", "2", source),
|
||||||
|
MangaTag("Fantasy", "3", source),
|
||||||
|
MangaTag("Manhua", "4", source),
|
||||||
|
MangaTag("Chuyển Sinh", "5", source),
|
||||||
|
MangaTag("Truyện Màu", "6", source),
|
||||||
|
MangaTag("Xuyên Không", "7", source),
|
||||||
|
MangaTag("Manhwa", "8", source),
|
||||||
|
MangaTag("Drama", "9", source),
|
||||||
|
MangaTag("Historical", "10", source),
|
||||||
|
MangaTag("Manga", "11", source),
|
||||||
|
MangaTag("Seinen", "12", source),
|
||||||
|
MangaTag("Comedy", "13", source),
|
||||||
|
MangaTag("Martial Arts", "14", source),
|
||||||
|
MangaTag("Mystery", "15", source),
|
||||||
|
MangaTag("Romance", "16", source),
|
||||||
|
MangaTag("Shounen", "17", source),
|
||||||
|
MangaTag("Sports", "18", source),
|
||||||
|
MangaTag("Supernatural", "19", source),
|
||||||
|
MangaTag("Harem", "20", source),
|
||||||
|
MangaTag("Webtoon", "21", source),
|
||||||
|
MangaTag("School Life", "22", source),
|
||||||
|
MangaTag("Psychological", "23", source),
|
||||||
|
MangaTag("Cổ Đại", "24", source),
|
||||||
|
MangaTag("Ecchi", "25", source),
|
||||||
|
MangaTag("Gender Bender", "26", source),
|
||||||
|
MangaTag("Shoujo", "27", source),
|
||||||
|
MangaTag("Slice of Life", "28", source),
|
||||||
|
MangaTag("Ngôn Tình", "29", source),
|
||||||
|
MangaTag("Horror", "30", source),
|
||||||
|
MangaTag("Sci-fi", "31", source),
|
||||||
|
MangaTag("Tragedy", "32", source),
|
||||||
|
MangaTag("Mecha", "33", source),
|
||||||
|
MangaTag("Comic", "34", source),
|
||||||
|
MangaTag("One shot", "35", source),
|
||||||
|
MangaTag("Shoujo Ai", "36", source),
|
||||||
|
MangaTag("Anime", "37", source),
|
||||||
|
MangaTag("Josei", "38", source),
|
||||||
|
MangaTag("Smut", "39", source),
|
||||||
|
MangaTag("Shounen Ai", "40", source),
|
||||||
|
MangaTag("Mature", "41", source),
|
||||||
|
MangaTag("Soft Yuri", "42", source),
|
||||||
|
MangaTag("Adult", "43", source),
|
||||||
|
MangaTag("Doujinshi", "44", source),
|
||||||
|
MangaTag("Live action", "45", source),
|
||||||
|
MangaTag("Trinh Thám", "46", source),
|
||||||
|
MangaTag("Việt Nam", "47", source),
|
||||||
|
MangaTag("Truyện scan", "48", source),
|
||||||
|
MangaTag("Cooking", "49", source),
|
||||||
|
MangaTag("Tạp chí truyện tranh", "50", source),
|
||||||
|
MangaTag("16+", "51", source),
|
||||||
|
MangaTag("Thiếu Nhi", "52", source),
|
||||||
|
MangaTag("Soft Yaoi", "53", source),
|
||||||
|
MangaTag("Đam Mỹ", "54", source),
|
||||||
|
MangaTag("BoyLove", "55", source),
|
||||||
|
MangaTag("Yaoi", "56", source),
|
||||||
|
MangaTag("18+", "57", source),
|
||||||
|
MangaTag("Người Thú", "58", source),
|
||||||
|
MangaTag("ABO", "59", source),
|
||||||
|
MangaTag("Mafia", "60", source),
|
||||||
|
MangaTag("Isekai", "61", source),
|
||||||
|
MangaTag("Hệ Thống", "62", source),
|
||||||
|
MangaTag("NTR", "63", source),
|
||||||
|
MangaTag("Yuri", "64", source),
|
||||||
|
MangaTag("Girl Love", "65", source),
|
||||||
|
MangaTag("Demons", "66", source),
|
||||||
|
MangaTag("Huyền Huyễn", "67", source),
|
||||||
|
MangaTag("Detective", "68", source),
|
||||||
|
MangaTag("Trọng Sinh", "69", source),
|
||||||
|
MangaTag("Magic", "70", source),
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue