SinMH: add Qinqin Manhua, rewrite 57Manhua, refactor (#12270)

* SinMH: add Qinqin Manhua and refactor

* minor refactor

* add sorting filter

* some tiny optimizations

* rewrite Wuqi Manhua

* optimize assets of Wuqi Manhua

* [skip ci] rename extension to 57Manhua
This commit is contained in:
stevenyomi 2022-06-22 17:41:55 +08:00 committed by GitHub
parent 0253ff3513
commit 3dc8e83d79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 321 additions and 250 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -1,17 +1,31 @@
package eu.kanade.tachiyomi.extension.zh.imitui
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.util.asJsoup
import org.jsoup.nodes.Document
import rx.Observable
class Imitui : SinMH("爱米推漫画", "https://www.imitui.com") {
override fun pageListParse(document: Document): List<Page> {
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
client.newCall(GET(baseUrl + chapter.url, headers)).asObservableSuccess().map {
val pcResult = pageListParse(it)
if (pcResult.isNotEmpty()) return@map pcResult
val response = client.newCall(GET(mobileUrl + chapter.url, headers)).execute()
mobilePageListParse(response.asJsoup())
}
private fun mobilePageListParse(document: Document): List<Page> {
val pageCount = document.select("div.image-content > p").text().removePrefix("1/").toInt()
val prefix = document.location().removeSuffix(".html")
return (0 until pageCount).map { Page(it, url = "$prefix-${it + 1}.html") }
}
// mobile
override fun imageUrlParse(document: Document): String =
document.select("div.image-content > img#image").attr("src")
}

View File

@ -2,30 +2,13 @@ package eu.kanade.tachiyomi.extension.zh.manhuadui
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
class YKMH : SinMH("优酷漫画", "http://www.ykmh.com") {
override val id = 1637952806167036168
override val mobileUrl = "http://wap.ykmh.com"
override val comicItemSelector = "li.list-comic"
override val comicItemTitleSelector = "h3 > a, p > a"
// DMZJ style
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst("h1").text()
val details = document.selectFirst("ul.comic_deCon_liO").children()
author = details[0].selectFirst("a").text()
status = when (details[1].selectFirst("a").text()) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
genre = (details[2].select("a") + details[3].select("a")).joinToString(", ") { it.text() }
description = document.selectFirst("p.comic_deCon_d").text()
thumbnail_url = document.selectFirst("div.comic_i_img > img").attr("src")
}
override fun mangaDetailsParse(document: Document) = mangaDetailsParseDMZJStyle(document, hasBreadcrumb = false)
override fun List<SChapter>.sortedDescending() = this
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.extension.zh.qinqin
import android.util.Base64
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
import eu.kanade.tachiyomi.network.GET
import org.jsoup.nodes.Document
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class Qinqin : SinMH("亲亲漫画", "https://www.acgqd.com") {
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/post/?page=$page", headers)
override fun mangaDetailsParse(document: Document) = mangaDetailsParseDMZJStyle(document, hasBreadcrumb = true)
// https://www.acgqd.com/js/jmzz20191018.js
override fun parsePageImages(chapterImages: String): List<String> {
val key = SecretKeySpec("cxNB23W8xzKJV26O".toByteArray(), "AES")
val iv = IvParameterSpec("opb4x7z21vg1f3gI".toByteArray())
val result = Cipher.getInstance("AES/CBC/PKCS7Padding").run {
init(Cipher.DECRYPT_MODE, key, iv)
doFinal(Base64.decode(chapterImages, Base64.DEFAULT))
}
return super.parsePageImages(String(result))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,146 @@
package eu.kanade.tachiyomi.extension.zh.wuqimanga
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
// Memo: the old implementation had a string preference with key "IMAGE_SERVER"
class WuqiManga : SinMH("57漫画", "http://www.wuqimh.net") {
override val nextPageSelector = "span.pager > a:last-child" // in the last page it's a span
override val comicItemSelector = "#contList > li, .book-result > li"
override val comicItemTitleSelector = "p > a, dt > a"
// 人气排序的漫画全是 404所以就用默认的最新发布了
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/order-id-p-$page", headers)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/order-addtime-p-$page", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val isSearch = query.isNotEmpty()
val params = arrayListOf<String>()
if (isSearch) params.add(query)
filters.filterIsInstance<UriPartFilter>().mapTo(params) { it.toUriPart() }
params.add("p-")
val url = buildString(120) {
append(baseUrl)
append(if (isSearch) "/search/q_" else "/list/")
params.joinTo(this, separator = "-", postfix = page.toString())
}
return GET(url, headers)
}
override fun mangaDetailsParse(document: Document) = super.mangaDetailsParse(document).apply {
val comment = document.selectFirst(".book-title > h2").text()
if (comment.isNotEmpty()) description = "$comment\n\n$description"
}
override fun mangaDetailsParseDefaultGenre(document: Document, detailsList: Element): String =
document.selectFirst("div.crumb").select("a[href^=/list/]")
.map { it.text().removeSuffix("").removeSuffix("漫画") }
.filter { it.isNotEmpty() }.joinToString(", ")
override fun chapterListSelector() = ".chapter-list li > a"
override fun List<SChapter>.sortedDescending() = this
override val dateSelector = ".cont-list dt:contains(更新于) + dd"
override val imageHost: String by lazy {
client.newCall(GET("$baseUrl/templates/wuqi/default/scripts/configs.js", headers)).execute().use {
Regex("""\['(.+?)']""").find(it.body!!.string())!!.groupValues[1].run { "http://$this" }
}
}
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers)
// Reference: https://github.com/evanw/packer/blob/master/packer.js
override fun pageListParse(document: Document): List<Page> {
val script = document.selectFirst("body > script").html().let(::ProgressiveParser)
val imageList = script.substringBetween(":[", "]").replace("\\", "")
if (imageList.isEmpty()) return emptyList()
script.consumeUntil("""};',""")
val dictionary = script.substringBetween("'", "'").split('|')
val size = dictionary.size
val unpacked = Regex("""\b\w+\b""").replace(imageList) {
with(it.value) {
val key = parseRadix62()
return@replace if (key < size) dictionary[key] else this
}
}.removeSurrounding("'").split("','")
return unpacked.filterNot { it.endsWith("/ManHuaKu/222.jpg") }.mapIndexed { i, image ->
val imageUrl = if (image.startsWith("http")) image else imageHost + image
Page(i, imageUrl = imageUrl)
}.also { list ->
if (list.isEmpty()) return emptyList()
client.newCall(GET(list[0].imageUrl!!, headers)).execute().use {
if (!it.isSuccessful) throw Exception("该章节的图片加载出错:${it.code}")
}
}
}
override fun parseCategories(document: Document) {
if (categories.isNotEmpty()) return
val labelSelector = Evaluator.Tag("label")
val linkSelector = Evaluator.Tag("a")
val filterMap = LinkedHashMap<String, LinkedHashMap<String, String>>(8)
document.select(Evaluator.Class("filter")).forEach { row ->
val tags = row.select(linkSelector)
if (tags.isEmpty()) return@forEach
val name = row.selectFirst(labelSelector).text().removeSuffix("")
if (!filterMap.containsKey(name)) {
filterMap[name] = LinkedHashMap(tags.size * 2)
}
val tagMap = filterMap[name]!!
for (tag in tags) {
val tagName = tag.text()
if (!tagMap.containsKey(tagName))
tagMap[tagName] = tag.attr("href").removePrefix("/list/").substringBeforeLast("-order-")
}
}
categories = filterMap.map {
val tagMap = it.value
Category(it.key, tagMap.keys.toTypedArray(), tagMap.values.toTypedArray())
}
}
override fun getFilterList(): FilterList {
val list: ArrayList<Filter<*>>
if (categories.isNotEmpty()) {
list = ArrayList(categories.size + 2)
with(list) {
add(Filter.Header("使用文本搜索时,只有地区、年份、字母选项有效"))
categories.forEach { add(it.toUriPartFilter()) }
}
} else {
list = ArrayList(4)
with(list) {
add(Filter.Header("点击“重置”即可刷新分类,如果失败,"))
add(Filter.Header("请尝试重新从图源列表点击进入图源"))
add(Filter.Header("使用文本搜索时,只有地区、年份、字母选项有效"))
}
}
list.add(UriPartFilter("排序方式", sortNames, sortKeys))
return FilterList(list)
}
private val sortNames = arrayOf("最新发布", "最近更新", "人气最旺", "评分最高")
private val sortKeys = arrayOf("order-id", "order-addtime", "order-hits", "order-gold")
}
private fun String.parseRadix62(): Int {
var result = 0
for (char in this) {
result = result * 62 + when {
char <= '9' -> char.code - '0'.code
char >= 'a' -> char.code - 'a'.code + 10
else -> char.code - 'A'.code + 36
}
}
return result
}

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.multisrc.sinmh
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -14,6 +15,7 @@ import okhttp3.Headers
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
import java.text.SimpleDateFormat
import java.util.Locale
@ -31,17 +33,20 @@ abstract class SinMH(
protected open val mobileUrl = _baseUrl.replace("www", "m")
override val supportsLatest = true
override val client = network.client.newBuilder().rateLimit(2).build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", baseUrl)
protected open val nextPageSelector = "ul.pagination > li.next:not(.disabled)"
protected open val comicItemSelector = "#contList > li"
protected open val comicItemTitleSelector = "p > a"
protected open val comicItemSelector = "#contList > li, li.list-comic"
protected open val comicItemTitleSelector = "p > a, h3 > a"
protected open fun mangaFromElement(element: Element) = SManga.create().apply {
val titleElement = element.selectFirst(comicItemTitleSelector)
title = titleElement.text()
setUrlWithoutDomain(titleElement.attr("abs:href"))
thumbnail_url = element.selectFirst("img").attr("abs:src")
setUrlWithoutDomain(titleElement.attr("href"))
val image = element.selectFirst(Evaluator.Tag("img"))
thumbnail_url = image.attr("src").ifEmpty { image.attr("data-src") }
}
// Popular
@ -80,30 +85,61 @@ abstract class SinMH(
override fun searchMangaSelector(): String = comicItemSelector
override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
if (query.isNotBlank()) {
if (query.isNotEmpty()) {
GET("$baseUrl/search/?keywords=$query&page=$page", headers)
} else {
val categories = filters.filterIsInstance<UriPartFilter>().map { it.toUriPart() }
.filter { it.isNotEmpty() }.joinToString("-") + "-"
GET("$baseUrl/list/$categories/", headers)
.filter { it.isNotEmpty() }
val sort = filters.filterIsInstance<SortFilter>().firstOrNull()?.toUriPart().orEmpty()
val url = StringBuilder(baseUrl).append("/list/").apply {
categories.joinTo(this, separator = "-", postfix = "-/")
}.append(sort).append("?page=").append(page).toString()
GET(url, headers)
}
// Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.selectFirst(".book-title > h1 > span").text()
author = document.selectFirst(".detail-list strong:contains(作者) + a").text()
description = document.selectFirst("#intro-all").text().trim()
title = document.selectFirst(".book-title > h1").text()
val detailsList = document.selectFirst(Evaluator.Class("detail-list"))
author = detailsList.select("strong:contains(作者) ~ *").text()
description = document.selectFirst(Evaluator.Id("intro-all")).text().trim()
.removePrefix("漫画简介:").trim()
.removePrefix("漫画简介:").trim() // some sources have double prefix
genre = document.selectFirst(".detail-list strong:contains(类型) + a").text() + ", " +
document.select(".breadcrumb-bar a[href*=/list/]").joinToString(", ") { it.text() }
status = when (document.selectFirst(".detail-list strong:contains(状态) + a").text()) {
genre = mangaDetailsParseDefaultGenre(document, detailsList)
status = when (detailsList.selectFirst("strong:contains(状态) + *").text()) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = document.selectFirst("p.cover > img").attr("abs:src")
thumbnail_url = document.selectFirst("div.book-cover img").attr("src")
}
protected open fun mangaDetailsParseDefaultGenre(document: Document, detailsList: Element): String {
val category = detailsList.selectFirst("strong:contains(类型) + a")
val breadcrumbs = document.selectFirst("div.breadcrumb-bar").select("a[href^=/list/]")
breadcrumbs.add(0, category)
return breadcrumbs.joinToString(", ") { it.text() }
}
protected fun mangaDetailsParseDMZJStyle(document: Document, hasBreadcrumb: Boolean) = SManga.create().apply {
val detailsDiv = document.selectFirst("div.comic_deCon")
title = detailsDiv.selectFirst(Evaluator.Tag("h1")).text()
val details = detailsDiv.select("> ul > li")
val linkSelector = Evaluator.Tag("a")
author = details[0].selectFirst(linkSelector).text()
status = when (details[1].selectFirst(linkSelector).text()) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
genre = mutableListOf<Element>().apply {
add(details[2].selectFirst(linkSelector)) // 类别
addAll(details[3].select(linkSelector)) // 类型
if (hasBreadcrumb) addAll(document.selectFirst("div.mianbao").select("a[href^=/list/]"))
}.mapTo(mutableSetOf()) { it.text() }.joinToString(", ")
description = detailsDiv.selectFirst("> p.comic_deCon_d").text()
thumbnail_url = document.selectFirst("div.comic_i_img > img").attr("src")
}
// Chapters
@ -117,7 +153,7 @@ abstract class SinMH(
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return document.select(chapterListSelector()).map { chapterFromElement(it) }.sortedDescending().apply {
if (isNewDateLogic) {
if (isNewDateLogic && this.isNotEmpty()) {
val date = document.selectFirst(dateSelector).textNodes().last().text()
this[0].date_upload = DATE_FORMAT.parse(date)?.time ?: 0L
}
@ -126,31 +162,55 @@ abstract class SinMH(
override fun chapterListSelector() = ".chapter-body li > a:not([href^=/comic/app/])"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
name = element.text()
setUrlWithoutDomain(element.attr("href"))
val children = element.children()
name = if (children.isEmpty()) element.text() else children[0].text()
}
// Pages
override fun pageListRequest(chapter: SChapter) = GET(mobileUrl + chapter.url, headers)
protected open val imageHost: String by lazy {
client.newCall(GET("$baseUrl/js/config.js", headers)).execute().use {
Regex("""resHost:.+?"domain":\["(.+?)"""").find(it.body!!.string())!!
.groupValues[1].substringAfter(':').run { "https:$this" }
}
}
// baseUrl/js/common.js/getChapterImage()
override fun pageListParse(document: Document): List<Page> {
val script = document.selectFirst("body > script").html()
val images = script.substringAfter("chapterImages = [\"").substringBefore("\"]").split("\",\"")
val path = script.substringAfter("chapterPath = \"").substringBefore("\";")
// assume cover images are on the page image server
val server = script.substringAfter("pageImage = \"").substringBefore("/images/cover")
return images.mapIndexed { i, image ->
val unescapedImage = image.replace("""\/""", "/")
val imageUrl = if (unescapedImage.startsWith("/")) {
"$server$unescapedImage"
} else {
"$server/$path$unescapedImage"
val script = document.selectFirst("body > script").html().let(::ProgressiveParser)
val images = script.substringBetween("chapterImages = ", ";")
if (images.length <= 2) return emptyList() // [] or ""
val path = script.substringBetween("chapterPath = \"", "\";")
return images.let(::parsePageImages).mapIndexed { i, image ->
val imageUrl = when {
image.startsWith("https://") -> image
image.startsWith("/") -> "$imageHost$image"
else -> "$imageHost/$path$image"
}
Page(i, imageUrl = imageUrl)
}
}
protected class ProgressiveParser(private val text: String) {
private var startIndex = 0
fun consumeUntil(string: String) = with(text) { startIndex = indexOf(string, startIndex) + string.length }
fun substringBetween(left: String, right: String): String = with(text) {
val leftIndex = indexOf(left, startIndex) + left.length
val rightIndex = indexOf(right, leftIndex)
startIndex = rightIndex + right.length
return substring(leftIndex, rightIndex)
}
}
// default parsing of ["...","..."]
protected open fun parsePageImages(chapterImages: String): List<String> =
if (chapterImages.length > 2) {
chapterImages.run { substring(2, length - 2) }.replace("""\/""", "/").split("\",\"")
} else emptyList() // []
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used.")
protected class UriPartFilter(displayName: String, values: Array<String>, private val uriParts: Array<String>) :
@ -158,38 +218,52 @@ abstract class SinMH(
fun toUriPart(): String = uriParts[state]
}
protected data class Category(val name: String, val values: Array<String>, val uriParts: Array<String>) {
private class SortFilter : Filter.Select<String>("排序方式", sortNames) {
fun toUriPart(): String = sortKeys[state]
}
protected class Category(private val name: String, private val values: Array<String>, private val uriParts: Array<String>) {
fun toUriPartFilter() = UriPartFilter(name, values, uriParts)
}
private lateinit var categories: List<Category>
protected var categories: List<Category> = emptyList()
protected open fun parseCategories(document: Document) {
if (::categories.isInitialized) return
categories = document.selectFirst(".filter-nav").children().map { element ->
val name = element.selectFirst("label").text()
val tags = element.select("a")
if (categories.isNotEmpty()) return
val labelSelector = Evaluator.Tag("label")
val linkSelector = Evaluator.Tag("a")
categories = document.selectFirst(Evaluator.Class("filter-nav")).children().map { element ->
val name = element.selectFirst(labelSelector).text()
val tags = element.select(linkSelector)
val values = tags.map { it.text() }.toTypedArray()
val uriParts = tags.map { it.attr("href").removePrefix("/list/").removeSuffix("/") }.toTypedArray()
Category(name, values, uriParts)
}
}
override fun getFilterList() =
if (::categories.isInitialized) FilterList(
Filter.Header("如果使用文本搜索,将会忽略分类筛选"),
*categories.map(Category::toUriPartFilter).toTypedArray()
) else FilterList(
Filter.Header("点击“重置”即可刷新分类,如果失败,"),
Filter.Header("请尝试重新从图源列表点击进入图源"),
)
override fun getFilterList(): FilterList {
val list: ArrayList<Filter<*>>
if (categories.isNotEmpty()) {
list = ArrayList(categories.size + 2)
with(list) {
add(Filter.Header("如果使用文本搜索,将会忽略分类筛选"))
categories.forEach { add(it.toUriPartFilter()) }
}
} else {
list = ArrayList(3)
with(list) {
add(Filter.Header("点击“重置”即可刷新分类,如果失败,"))
add(Filter.Header("请尝试重新从图源列表点击进入图源"))
}
}
list.add(SortFilter())
return FilterList(list)
}
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
private val isNewDateLogic = run {
val commitCount = AppInfo.getVersionName().substringAfter('-', "")
if (commitCount.isNotEmpty()) // Preview
commitCount.toInt() >= 4442
else // Stable
AppInfo.getVersionCode() >= 81
companion object {
private val DATE_FORMAT by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
private val isNewDateLogic = AppInfo.getVersionCode() >= 81
private val sortNames = arrayOf("按发布排序", "按发布排序(逆序)", "按更新排序", "按更新排序(逆序)", "按点击排序", "按点击排序(逆序)")
private val sortKeys = arrayOf("post/", "-post/", "update/", "-update/", "click/", "-click/")
}
}

View File

@ -6,7 +6,7 @@ import generator.ThemeSourceGenerator
class SinMHGenerator : ThemeSourceGenerator {
override val themeClass = "SinMH"
override val themePkg = "sinmh"
override val baseVersionCode = 4
override val baseVersionCode = 5
override val sources = listOf(
SingleLang(
name = "Gufeng Manhua", baseUrl = "https://www.gufengmh9.com", lang = "zh",
@ -20,6 +20,14 @@ class SinMHGenerator : ThemeSourceGenerator {
name = "YKMH", baseUrl = "http://www.ykmh.com", lang = "zh", className = "YKMH",
pkgName = "manhuadui", sourceName = "优酷漫画", overrideVersionCode = 17
),
SingleLang(
name = "Qinqin Manhua", baseUrl = "https://www.acgqd.com", lang = "zh",
className = "Qinqin", sourceName = "亲亲漫画", overrideVersionCode = 0
),
SingleLang(
name = "57Manhua", baseUrl = "http://www.wuqimh.net", lang = "zh",
className = "WuqiManga", sourceName = "57漫画", overrideVersionCode = 3
),
)
companion object {

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'WuqiManga'
pkgNameSuffix = 'zh.wuqimanga'
extClass = '.WuqiManga'
extVersionCode = 3
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,167 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.wuqimanga
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import com.squareup.duktape.Duktape
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
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 eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class WuqiManga : ParsedHttpSource(), ConfigurableSource {
override val name = "57漫画"
override val baseUrl = "http://www.wuqimh.net"
override val lang = "zh"
override val supportsLatest = true
// Server list can be found from baseUrl/templates/wuqi/default/scripts/configs.js?v=1.0.3
private val imageServer = arrayOf("http://imagesold.benzidui.com", "http://images.tingliu.cc")
private val preferences: SharedPreferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/order-addtime-p-$page", headers)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/order-hits-p-$page", headers)
override fun popularMangaSelector() = "ul#contList > li"
override fun popularMangaNextPageSelector(): String? = "span.pager > a.prev:contains(下一页)"
override fun popularMangaFromElement(element: Element): SManga {
val coverEl = element.select("a img").first()
val cover = if (coverEl.hasAttr("data-src")) {
coverEl.attr("data-src")
} else {
coverEl.attr("src")
}
val title = element.select("a").attr("title")
val url = element.select("a").attr("href")
val manga = SManga.create()
manga.thumbnail_url = cover
manga.title = title
manga.url = url
return manga
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/search/q_$query-p-$page", headers)
}
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaSelector() = "div.book-result li.cf"
override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create()
element.select("div.book-detail").first().let {
val titleEl = it.select("dl > dt > a")
manga.setUrlWithoutDomain(titleEl.attr("href"))
manga.title = titleEl.attr("title").trim()
manga.description = it.select("dd.intro").text()
val status = it.select("dd.tags.status")
manga.status = if (status.select("span.red").first().text().contains("连载中")) {
SManga.ONGOING
} else {
SManga.COMPLETED
}
for (el in it.select("dd.tags")) {
if (el.select("span strong").text().contains("作者")) {
manga.author = el.select("span a").text()
}
}
}
manga.thumbnail_url = element.select("a.bcover > img").attr("src")
return manga
}
override fun chapterListSelector() = throw Exception("Not used")
override fun headersBuilder() = Headers.Builder().add("Referer", "$baseUrl/")
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36")
override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a")
val chapter = SChapter.create()
chapter.url = urlElement.attr("href")
chapter.name = urlElement.attr("alt").trim()
return chapter
}
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
manga.description = document.selectFirst("div#intro-all > p").text()
manga.title = document.select(".book-title h1").text().trim()
manga.thumbnail_url = document.select(".hcover img").attr("src")
for (element in document.select("ul.detail-list li span")) {
if (element.select("strong").text().contains("漫画作者")) {
manga.author = element.select("a").text()
break
}
}
return manga
}
override fun imageUrlParse(document: Document): String = ""
override fun chapterListParse(response: Response): List<SChapter> {
val chapters = mutableListOf<SChapter>()
response.asJsoup().select("div.chapter div.chapter-list>ul").asReversed().forEach {
it.select("li a").forEach {
chapters.add(
SChapter.create().apply {
url = it.attr("href")
name = it.attr("title")
}
)
}
}
return chapters
}
override fun pageListParse(document: Document): List<Page> {
val html = document.html()
val packed = Regex("eval(.*?)\\n").find(html)?.groups?.get(1)?.value
val result = Duktape.create().use {
it.evaluate(packed!!) as String
}
val re2 = Regex("""\{.*\}""")
val imgJsonStr = re2.find(result)?.groups?.get(0)?.value
val server = preferences.getString("IMAGE_SERVER", imageServer[0])
// The website is using single quotes in json, so kotlinx.serialization doesn't work
return imgJsonStr!!.substringAfter("'fs':['").substringBefore("'],").split("','").mapIndexed { index, it ->
Page(index, "", if (it.startsWith("http")) it else "$server$it")
}
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = "IMAGE_SERVER"
title = "线路"
summary = "需要清除章节缓存以生效"
entries = arrayOf("主线", "备用")
entryValues = imageServer
setDefaultValue(imageServer[0])
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putString(key, newValue as String).commit()
}
}.let(screen::addPreference)
}
}