[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="m.blogtruyen.vn" />
<data android:pathPattern="/..*/..*"
android:scheme="https" />
<data 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>
</activity>
</application>

View File

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

View File

@ -48,6 +48,8 @@ class BlogTruyen : ParsedHttpSource() {
companion object {
const val PREFIX_ID_SEARCH = "id:"
const val PREFIX_AUTHOR_SEARCH = "author:"
const val PREFIX_TEAM_SEARCH = "team:"
}
override fun headersBuilder(): Headers.Builder =
@ -105,23 +107,53 @@ class BlogTruyen : ParsedHttpSource() {
): Observable<MangasPage> {
return when {
query.startsWith(PREFIX_ID_SEARCH) -> {
val id = query.removePrefix(PREFIX_ID_SEARCH).trim()
if (!id.startsWith("/")) {
throw Exception("ID tìm kiếm không hợp lệ")
var id = query.removePrefix(PREFIX_ID_SEARCH).trim()
// 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(
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)
}
}
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 {
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 {
addQueryParameter("txt", query)
addQueryParameter("p", page.toString())
@ -181,34 +213,75 @@ class BlogTruyen : ParsedHttpSource() {
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 {
val anchor = document.selectFirst(".entry-title a")
setUrlWithoutDomain(anchor.attr("href"))
title = anchor.attr("title")
.replace("truyện tranh ", "")
.trim()
title = getMangaTitle(document)
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() }
status = parseStatus(
document.select("span.color-red:not(.bold)").text()
)
description = document.selectFirst(".manga-detail .detail .content").let {
if (it.select("p").any()) {
it.select("p").joinToString("\n", transform = ::brToNewline)
} else {
brToNewline(it)
description = StringBuilder().apply {
// the actual synopsis
val synopsisBlock = document.selectFirst(".manga-detail .detail .content")
// 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 {
return element.run {
select("br").prepend("\\n")
text().replace("\\n", "\n").replace("\n ", "\n")
}
private fun Element.textWithNewlines() = run {
select("p").prepend("\\n")
select("br").prepend("\\n")
text().replace("\\n", "\n").replace("\n ", "\n")
}
private fun parseStatus(status: String) = when {
@ -218,13 +291,21 @@ class BlogTruyen : ParsedHttpSource() {
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 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()
setUrlWithoutDomain(anchor.attr("href"))
name = anchor.attr("title").trim()
name = anchor.attr("title").replace(title, "", true).trim()
date_upload = kotlin.runCatching {
dateFormat.parse(
element.selectFirst("span.publishedDate").text()

View File

@ -12,12 +12,17 @@ class BlogTruyenUrlActivity : Activity() {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val id = "/${pathSegments[0]}/${pathSegments[1]}"
try {
startActivity(
Intent().apply {
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)
}
)