Update 6Manhua parser (#8539)

* Update 6Manhua parser

* Code review changes

* Code review changes

* Code review changes
This commit is contained in:
Senxian Z 2025-04-25 01:10:48 +08:00 committed by Draff
parent 6433c41cb7
commit 2edb3b6164
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
5 changed files with 118 additions and 43 deletions

View File

@ -1,9 +1,7 @@
ext {
extName = '6Manhua'
extClass = '.SixMH'
themePkg = 'mccms'
baseUrl = 'https://www.liumanhua.com'
overrideVersionCode = 5
extVersionCode = 13
isNsfw = true
}

View File

@ -1,18 +1,24 @@
package eu.kanade.tachiyomi.extension.zh.sixmh
import android.util.Base64
import kotlin.experimental.xor
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
private val keys = arrayOf("Ni1iWGQ5aU4=", "Ni1SWHlqcnk=", "Ni1vWXZ3Vnk=", "Ni00Wlk1N1U=", "Ni1tYkpwVTc=", "Ni02TU0yRWk=", "Ni01NFRpUXI=", "Ni1QaDV4eDk=", "Ni1iWWdlUFI=", "Ni1aOUEzYlc=")
internal fun decodeData(encodedData: String): String {
// Key derived from https://www.liumanhua.com/template/pc/liumanhua/js/index-v2.js line 571
// excerpt:
// var _0x493e85 = CryptoJS[_0x5b6369(0x15c,'a#uL')]['Utf8'][_0x5b6369(0x147,'H0qZ')](_0x5b6369(0x152,')X69'));
// _0x5b6369(0x152,')X69') provides the key
val aesKey = "9S8\$vJnU2ANeSRoF"
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
val secretKeySpec = SecretKeySpec(aesKey.toByteArray(), "AES")
internal fun decodeData(encodedData: String, cid: Int): String {
val key = Base64.decode(keys[cid % keys.size], Base64.DEFAULT)
val keyLength = key.size
val decodedData = Base64.decode(encodedData, Base64.DEFAULT)
val decryptedData = StringBuilder()
for (i in decodedData.indices) {
val decryptedCharCode = decodedData[i] xor key[i % keyLength]
decryptedData.appendCodePoint(decryptedCharCode.toInt())
}
return Base64.decode(decryptedData.toString(), Base64.DEFAULT).decodeToString()
val decodedBase64 = Base64.decode(encodedData, Base64.DEFAULT)
val iv = IvParameterSpec(decodedBase64.sliceArray(0 until 16))
val cipherText = decodedBase64.sliceArray(16 until decodedBase64.size)
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv)
val decryptedText = cipher.doFinal(cipherText)
return decryptedText.toString(Charsets.UTF_8)
}

View File

@ -3,4 +3,6 @@ package eu.kanade.tachiyomi.extension.zh.sixmh
import kotlinx.serialization.Serializable
@Serializable
data class Image(val id: String, val url: String)
data class Data(
val images: List<String>,
)

View File

@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.extension.zh.sixmh
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
abstract class SimpleParsedHttpSource : ParsedHttpSource() {
abstract fun simpleMangaSelector(): String
abstract fun simpleMangaFromElement(element: Element): SManga
abstract fun simpleNextPageSelector(): String?
override fun popularMangaSelector() = simpleMangaSelector()
override fun popularMangaFromElement(element: Element) = simpleMangaFromElement(element)
override fun popularMangaNextPageSelector() = simpleNextPageSelector()
override fun latestUpdatesSelector() = simpleMangaSelector()
override fun latestUpdatesFromElement(element: Element) = simpleMangaFromElement(element)
override fun latestUpdatesNextPageSelector() = simpleNextPageSelector()
override fun searchMangaSelector() = simpleMangaSelector()
override fun searchMangaFromElement(element: Element) = simpleMangaFromElement(element)
override fun searchMangaNextPageSelector() = simpleNextPageSelector()
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
}

View File

@ -1,38 +1,80 @@
package eu.kanade.tachiyomi.extension.zh.sixmh
import android.app.Application
import android.os.Build
import eu.kanade.tachiyomi.multisrc.mccms.MCCMS
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import keiyoushi.utils.parseAs
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class SixMH : MCCMS("六漫画", "https://www.liumanhua.com") {
private val dataRegex = Regex("var DATA = '([A-Za-z0-9+/=]+)'")
private val json by injectLazy<Json>()
override val versionId get() = 2
class SixMH : SimpleParsedHttpSource() {
private val paramsRegex = Regex("params = '([A-Za-z0-9+/=]+)'")
override val versionId get() = 3
override val name: String = "六漫画"
override val lang: String = "zh"
override val supportsLatest: Boolean = true
override val baseUrl: String = "https://www.liumanhua.com"
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Delete old preferences for "6漫画/zh/1"
Injekt.get<Application>().deleteSharedPreferences("source_7259486566651312186")
override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/order/hits/page/$page", headers)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/category/order/addtime/page/$page", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/index.php/search".toHttpUrl().newBuilder()
.addQueryParameter("key", query)
.build()
return GET(url, headers)
}
override fun simpleNextPageSelector(): String? = null
override fun simpleMangaSelector(): String = "div.cy_list_mh ul"
override fun simpleMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.selectFirst("li.title > a")!!.text()
setUrlWithoutDomain(element.selectFirst("li.title > a")!!.absUrl("href"))
thumbnail_url = element.selectFirst("img")?.absUrl("src")
}
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
val element = document.selectFirst("div.cy_info")!!
title = element.selectFirst("div.cy_title")!!.text()
thumbnail_url = element.selectFirst("div.cy_info_cover > a > img.pic")?.absUrl("src")
description = element.selectFirst("div.cy_desc #comic-description")?.text()
val infoElements = element.select("div.cy_xinxi")
author = infoElements[0].selectFirst("span:first-child > a")?.text()
status = parseStatus(infoElements[0].selectFirst("span:nth-child(2)")?.text())
genre = infoElements[1].selectFirst("span:first-child > a")?.text()
}
// Chapters
override fun chapterListSelector(): String = "ul#mh-chapter-list-ol-0 li.chapter__item"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
name = element.selectFirst("a > p")!!.text()
}
// Pages
override fun pageListParse(response: Response): List<Page> {
val body = response.body.string()
val encodedData = paramsRegex.find(body)?.groupValues?.get(1) ?: ""
val decodedData = decodeData(encodedData)
val images = decodedData.parseAs<Data>().images
return images.mapIndexed { index, url -> Page(index, imageUrl = url) }
}
override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException()
private fun parseStatus(status: String?): Int {
return when {
status == null -> SManga.UNKNOWN
status.contains("连载") -> SManga.ONGOING
status.contains("完结") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
override fun getMangaUrl(manga: SManga) = "https://m.liumanhua.com" + manga.url
override fun getChapterUrl(chapter: SChapter) = "https://m.liumanhua.com" + chapter.url
override fun pageListParse(response: Response): List<Page> {
val encodedData = dataRegex.find(response.body.string())?.groupValues?.get(1) ?: ""
val cid = response.request.url.pathSegments.last().removeSuffix(".html").toIntOrNull() ?: 0
val decodedData = decodeData(encodedData, cid)
val images = json.decodeFromString<List<Image>>(decodedData)
return images.mapIndexed { index, image -> Page(index, imageUrl = image.url) }
}
}