[BlogTruyen] Clean title from chapter name, add manga metadata to description (#15115)

* [BlogTruyen] Clean title from chapter name, add manga metadata to description

* add intent processing for author and team

* add uploader

* only accept what we can process

* undelete url activity
This commit is contained in:
beerpsi 2023-01-27 04:55:45 +07:00 committed by GitHub
parent 5e91d36be0
commit d94ece3166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 27 deletions

View File

@ -13,8 +13,34 @@
<data android:host="blogtruyen.vn" /> <data android:host="blogtruyen.vn" />
<data android:host="m.blogtruyen.vn" /> <data android:host="m.blogtruyen.vn" />
<data android:pathPattern="/..*/..*" <data android:scheme="https" />
android:scheme="https" />
<data android:pathPattern="/tac-gia/..*" />
<data android:pathPattern="/nhom-dich/..*" />
<!--
Try to ensure that the passed URL is a chapter `c{id}` or a manga `{id}`, with `id`
being a number.
-->
<data android:pathPattern="/c1.*/..*" />
<data android:pathPattern="/c2.*/..*" />
<data android:pathPattern="/c3.*/..*" />
<data android:pathPattern="/c4.*/..*" />
<data android:pathPattern="/c5.*/..*" />
<data android:pathPattern="/c6.*/..*" />
<data android:pathPattern="/c7.*/..*" />
<data android:pathPattern="/c8.*/..*" />
<data android:pathPattern="/c9.*/..*" />
<data android:pathPattern="/1.*/..*" />
<data android:pathPattern="/2.*/..*" />
<data android:pathPattern="/3.*/..*" />
<data android:pathPattern="/4.*/..*" />
<data android:pathPattern="/5.*/..*" />
<data android:pathPattern="/6.*/..*" />
<data android:pathPattern="/7.*/..*" />
<data android:pathPattern="/8.*/..*" />
<data android:pathPattern="/9.*/..*" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>

View File

@ -5,7 +5,7 @@ ext {
extName = 'BlogTruyen' extName = 'BlogTruyen'
pkgNameSuffix = 'vi.blogtruyen' pkgNameSuffix = 'vi.blogtruyen'
extClass = '.BlogTruyen' extClass = '.BlogTruyen'
extVersionCode = 13 extVersionCode = 14
isNsfw = true isNsfw = true
} }

View File

@ -48,6 +48,8 @@ class BlogTruyen : ParsedHttpSource() {
companion object { companion object {
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"
const val PREFIX_AUTHOR_SEARCH = "author:"
const val PREFIX_TEAM_SEARCH = "team:"
} }
override fun headersBuilder(): Headers.Builder = override fun headersBuilder(): Headers.Builder =
@ -105,23 +107,53 @@ class BlogTruyen : ParsedHttpSource() {
): Observable<MangasPage> { ): Observable<MangasPage> {
return when { return when {
query.startsWith(PREFIX_ID_SEARCH) -> { query.startsWith(PREFIX_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_ID_SEARCH).trim() var id = query.removePrefix(PREFIX_ID_SEARCH).trim()
if (!id.startsWith("/")) {
throw Exception("ID tìm kiếm không hợp lệ") // it's a chapter, resolve to manga ID
if (id.startsWith("c")) {
val document = client.newCall(GET("$baseUrl/$id", headers)).execute().asJsoup()
id = document.selectFirst(".breadcrumbs a:last-child").attr("href").removePrefix("/")
} }
fetchMangaDetails( fetchMangaDetails(
SManga.create().apply { SManga.create().apply {
url = id url = "/$id"
} }
) )
.map { MangasPage(listOf(it.apply { url = id }), false) } .map { MangasPage(listOf(it.apply { url = "/$id" }), false) }
} }
else -> super.fetchSearchManga(page, query, filters) else -> super.fetchSearchManga(page, query, filters)
} }
} }
private fun extractIdFromQuery(prefix: String, query: String): String {
val q = query.substringAfter(prefix).trim()
return if (q.contains("-")) {
q.substringAfterLast("-")
} else {
q
}
}
private val ajaxSearchUrls: Map<String, String> = mapOf(
PREFIX_AUTHOR_SEARCH to "Author/AjaxLoadMangaByAuthor?orderBy=3",
PREFIX_TEAM_SEARCH to "TranslateTeam/AjaxLoadMangaByTranslateTeam",
)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
ajaxSearchUrls.keys.forEach {
if (!query.startsWith(it)) {
return@forEach
}
val id = extractIdFromQuery(it, query)
val url = "$baseUrl/ajax/${ajaxSearchUrls[it]}".toHttpUrl().newBuilder()
.addQueryParameter("id", id)
.addQueryParameter("p", page.toString())
.build()
.toString()
return GET(url, headers)
}
val url = "$baseUrl/timkiem/nangcao/1".toHttpUrl().newBuilder().apply { val url = "$baseUrl/timkiem/nangcao/1".toHttpUrl().newBuilder().apply {
addQueryParameter("txt", query) addQueryParameter("txt", query)
addQueryParameter("p", page.toString()) addQueryParameter("p", page.toString())
@ -181,34 +213,75 @@ class BlogTruyen : ParsedHttpSource() {
override fun searchMangaNextPageSelector() = ".pagination .glyphicon-step-forward" override fun searchMangaNextPageSelector() = ".pagination .glyphicon-step-forward"
private fun getMangaTitle(document: Document) = document.selectFirst(".entry-title a")
.attr("title")
.replaceFirst("truyện tranh", "", false)
.trim()
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
val anchor = document.selectFirst(".entry-title a") val anchor = document.selectFirst(".entry-title a")
setUrlWithoutDomain(anchor.attr("href")) setUrlWithoutDomain(anchor.attr("href"))
title = anchor.attr("title") title = getMangaTitle(document)
.replace("truyện tranh ", "")
.trim()
thumbnail_url = document.select(".thumbnail img").attr("abs:src") thumbnail_url = document.select(".thumbnail img").attr("abs:src")
author = document.select("a.color-green.label.label-info").joinToString { it.text() } author = document.select("a[href*=tac-gia]").joinToString { it.text() }
genre = document.select("span.category a").joinToString { it.text() } genre = document.select("span.category a").joinToString { it.text() }
status = parseStatus( status = parseStatus(
document.select("span.color-red:not(.bold)").text() document.select("span.color-red:not(.bold)").text()
) )
description = document.selectFirst(".manga-detail .detail .content").let { description = StringBuilder().apply {
if (it.select("p").any()) { // the actual synopsis
it.select("p").joinToString("\n", transform = ::brToNewline) val synopsisBlock = document.selectFirst(".manga-detail .detail .content")
} else {
brToNewline(it) // replace the facebook blockquote in synopsis with the link (if there is one)
val fbElement = synopsisBlock.selectFirst(".fb-page, .fb-group")
if (fbElement != null) {
val fbLink = fbElement.attr("data-href")
val node = document.createElement("p")
node.appendText(fbLink)
fbElement.replaceWith(node)
} }
} appendLine(synopsisBlock.textWithNewlines().trim())
appendLine()
// other metadata
document.select(".description p").forEach {
val text = it.text()
if (text.contains("Thể loại") ||
text.contains("Tác giả") ||
text.isBlank()
) {
return@forEach
}
if (text.contains("Trạng thái")) {
appendLine(text.substringBefore("Trạng thái").trim())
return@forEach
}
if (text.contains("Nguồn") ||
text.contains("Tham gia update") ||
text.contains("Nhóm dịch")
) {
val key = text.substringBefore(":")
val value = it.select("a").joinToString { el -> el.text() }
appendLine("$key: $value")
return@forEach
}
it.select("a, span").append("\\n")
appendLine(it.text().replace("\\n", "\n").replace("\n ", "\n").trim())
}
}.toString().trim()
} }
private fun brToNewline(element: Element): String { private fun Element.textWithNewlines() = run {
return element.run { select("p").prepend("\\n")
select("br").prepend("\\n") select("br").prepend("\\n")
text().replace("\\n", "\n").replace("\n ", "\n") text().replace("\\n", "\n").replace("\n ", "\n")
}
} }
private fun parseStatus(status: String) = when { private fun parseStatus(status: String) = when {
@ -218,13 +291,21 @@ class BlogTruyen : ParsedHttpSource() {
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val title = getMangaTitle(document)
return document.select(chapterListSelector()).map { chapterFromElement(it, title) }
}
override fun chapterListSelector() = "div.list-wrap > p" override fun chapterListSelector() = "div.list-wrap > p"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used")
private fun chapterFromElement(element: Element, title: String): SChapter = SChapter.create().apply {
val anchor = element.select("span > a").first() val anchor = element.select("span > a").first()
setUrlWithoutDomain(anchor.attr("href")) setUrlWithoutDomain(anchor.attr("href"))
name = anchor.attr("title").trim() name = anchor.attr("title").replace(title, "", true).trim()
date_upload = kotlin.runCatching { date_upload = kotlin.runCatching {
dateFormat.parse( dateFormat.parse(
element.selectFirst("span.publishedDate").text() element.selectFirst("span.publishedDate").text()

View File

@ -12,12 +12,17 @@ class BlogTruyenUrlActivity : Activity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) { if (pathSegments != null && pathSegments.size > 1) {
val id = "/${pathSegments[0]}/${pathSegments[1]}"
try { try {
startActivity( startActivity(
Intent().apply { Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH" action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${BlogTruyen.PREFIX_ID_SEARCH}$id") with(pathSegments[0]) {
when {
equals("tac-gia") -> putExtra("query", "${BlogTruyen.PREFIX_AUTHOR_SEARCH}${pathSegments[1]}")
equals("nhom-dich") -> putExtra("query", "${BlogTruyen.PREFIX_TEAM_SEARCH}${pathSegments[1]}")
else -> putExtra("query", "${BlogTruyen.PREFIX_ID_SEARCH}${pathSegments[0]}/${pathSegments[1]}")
}
}
putExtra("filter", packageName) putExtra("filter", packageName)
} }
) )