[HangTruyen] Add source (#1708)

Solved task #1694
master
Draken 1 year ago committed by GitHub
parent c949b95dab
commit 2fff92b023
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1 +1 @@
total: 1204 total: 1205

@ -0,0 +1,197 @@
package org.koitharu.kotatsu.parsers.site.vi
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.LegacyPagedMangaParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.*
import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("HANGTRUYEN", "Hang Truyện", "vi")
internal class HangTruyen(context: MangaLoaderContext) : LegacyPagedMangaParser(context, MangaParserSource.HANGTRUYEN, 10) {
override val configKeyDomain = ConfigKey.Domain("hangtruyen.org")
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.UPDATED_ASC,
SortOrder.NEWEST,
SortOrder.NEWEST_ASC,
SortOrder.POPULARITY,
SortOrder.POPULARITY_ASC,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
ContentType.COMICS,
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("/tim-kiem?page=")
append(page)
if (filter.types.isNotEmpty()) {
append("&categoryIds=")
val categoryIds = filter.types.joinToString(",") { type ->
when (type) {
ContentType.MANGA -> "1"
ContentType.MANHUA -> "2"
ContentType.MANHWA -> "3"
ContentType.COMICS -> "4,5"
else -> "1,2,3,4,5"
}
}
append(categoryIds)
}
if (filter.tags.isNotEmpty()) {
append("&genreIds=")
filter.tags.joinTo(this, separator = ",") { it.key }
}
append("&orderBy=")
append(when (order) {
SortOrder.POPULARITY -> "view_desc"
SortOrder.POPULARITY_ASC -> "view_asc"
SortOrder.UPDATED -> "udpated_at_date_desc"
SortOrder.UPDATED_ASC -> "udpated_at_date_asc"
SortOrder.NEWEST -> "created_at_date_desc"
SortOrder.NEWEST_ASC -> "created_at_date_asc"
else -> "view_desc"
})
if (!filter.query.isNullOrEmpty()) {
append("&keyword=")
val encodedQuery = filter.query.splitByWhitespace().joinToString(separator = "+") { part ->
part.urlEncoded()
}
append(encodedQuery)
}
}
val doc = webClient.httpGet(url.toAbsoluteUrl(domain)).parseHtml()
return doc.select("div.m-post.col-md-6").map { div ->
val href = div.selectFirst("h3.m-name a")?.attrAsRelativeUrl("href") ?: ""
val ratingText = div.selectFirst("span")?.text()?.toFloatOrNull() ?: 0f
val rating = (ratingText / 5f) * 5f
val img = div.selectFirst("img.lzl")?.let { img ->
img.attr("data-src").takeUnless { it.isNullOrEmpty() } ?: img.attr("data-original")
}
val title = div.selectFirst("h3.m-name a")?.text().orEmpty()
Manga(
id = generateUid(href),
title = title,
altTitles = emptySet(),
url = href,
publicUrl = href.toAbsoluteUrl(domain),
rating = rating,
contentRating = if (isNsfwSource) ContentRating.ADULT else ContentRating.SAFE,
coverUrl = img,
tags = emptySet(),
state = null,
authors = emptySet(),
source = source,
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
val script = doc.selectFirst("script:containsData(const mangaDetail)")?.data() ?: return manga
val mangaDetailJson = script.substringAfter("const mangaDetail = ").substringBefore(";")
val mangaDetail = JSONObject(mangaDetailJson)
val mangaSlug = mangaDetail.getString("slug")
val adultTagIds = setOf("29", "31", "210", "211", "175", "41", "212")
val tags = mangaDetail.getJSONArray("genres").mapJSONToSet { genre ->
MangaTag(
key = genre.getInt("id").toString(),
title = genre.getString("name").toTitleCase(sourceLocale),
source = source,
)
}
val isAdult = tags.any { it.key in adultTagIds }
return manga.copy(
title = mangaDetail.getString("title").orEmpty(),
authors = mangaDetail.optString("author").takeUnless { it.isNullOrEmpty() }?.let { setOf(it) } ?: emptySet(),
tags = tags,
description = mangaDetail.getString("overview").orEmpty(),
state = when (mangaDetail.optInt("status")) {
0 -> MangaState.ONGOING
1 -> MangaState.FINISHED
else -> null
},
contentRating = if (isAdult) ContentRating.ADULT else ContentRating.SAFE,
chapters = mangaDetail.getJSONArray("chapters").mapJSON { chapter ->
val chapterSlug = chapter.getString("slug")
val chapterUrl = "$mangaSlug/$chapterSlug"
MangaChapter(
id = generateUid(chapterUrl),
title = chapter.getString("name"),
number = chapter.getDouble("index").toFloat(),
url = chapterUrl,
scanlator = null,
uploadDate = dateFormat.tryParse(chapter.getString("releasedAt")),
branch = null,
source = source,
volume = 0
)
}.reversed()
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val script = doc.selectFirst("script:containsData(let mangaDetail)")?.data() ?: return emptyList()
val chapterDetailJson = script.substringAfter("chapterDetail=").substringBefore("}</script>") + "}"
val chapterDetail = JSONObject(chapterDetailJson)
return chapterDetail.getJSONArray("images").mapJSON { image ->
val url = image.getString("path")
val index = image.getInt("index")
index to url
}.sortedBy { it.first }.map { (_, url) ->
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
private suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/tim-kiem").parseHtml()
return doc.select("div.list-genres span").mapToSet {
MangaTag(
key = it.attr("data-value"),
title = it.text().replace("#", "").toTitleCase(sourceLocale),
source = source,
)
}
}
}
Loading…
Cancel
Save