Initial commit
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
BIN
multisrc/overrides/sinmh/gufengmh/res/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
50
multisrc/overrides/sinmh/gufengmh/src/Gufengmh.kt
Normal file
@@ -0,0 +1,50 @@
|
||||
package eu.kanade.tachiyomi.extension.zh.gufengmh
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.sinmh.ProgressiveParser
|
||||
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import org.jsoup.nodes.Document
|
||||
import rx.Observable
|
||||
import rx.Single
|
||||
|
||||
class Gufengmh : SinMH("古风漫画网", "https://www.gufengmh.com") {
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga =
|
||||
super.mangaDetailsParse(document).apply {
|
||||
if (status == SManga.COMPLETED) return@apply
|
||||
val firstChapter = document.selectFirst(".chapter-body li > a") ?: return@apply
|
||||
if (firstChapter.attr("href").startsWith("javascript")) {
|
||||
status = SManga.LICENSED
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
|
||||
Single.create<List<SChapter>> { subscriber ->
|
||||
val pcResponse = client.newCall(GET(baseUrl + manga.url, headers)).execute()
|
||||
val pcResult = chapterListParse(pcResponse, ".chapter-body li > a", "span.sj")
|
||||
if (pcResult.none { it.url.isEmpty() }) return@create subscriber.onSuccess(pcResult)
|
||||
// Example: https://www.gufengmh9.com/manhua/niaoling/
|
||||
val mobileResponse = client.newCall(GET(mobileUrl + manga.url, headers)).execute()
|
||||
val mobileResult = chapterListParse(mobileResponse, ".list li > a", ".pic_zi:nth-of-type(4) > dd")
|
||||
val pcAscending = pcResult.asReversed()
|
||||
val mobileAscending = mobileResult.asReversed()
|
||||
for ((pcChapter, mobileChapter) in pcAscending zip mobileAscending) {
|
||||
if (pcChapter.name != mobileChapter.name) return@create subscriber.onSuccess(mobileResult)
|
||||
pcChapter.url = mobileChapter.url
|
||||
}
|
||||
pcAscending.forEachIndexed { i, chapter ->
|
||||
if (chapter.url.isNotEmpty()) return@forEachIndexed
|
||||
if (i == 0) return@create subscriber.onSuccess(mobileResult)
|
||||
val prevUrl = pcAscending[i - 1].url
|
||||
val response = client.newCall(GET(baseUrl + prevUrl, headers)).execute()
|
||||
chapter.url = buildString {
|
||||
append(prevUrl, 0, prevUrl.lastIndexOf('/') + 1)
|
||||
append(ProgressiveParser(response.body.string()).substringBetween("""nextChapterData = {"id":""", ","))
|
||||
append(".html")
|
||||
}
|
||||
}
|
||||
subscriber.onSuccess(pcResult)
|
||||
}.toObservable()
|
||||
}
|
||||
BIN
multisrc/overrides/sinmh/imitui/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
multisrc/overrides/sinmh/imitui/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
multisrc/overrides/sinmh/imitui/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
multisrc/overrides/sinmh/imitui/res/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
16
multisrc/overrides/sinmh/imitui/src/Imitui.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package eu.kanade.tachiyomi.extension.zh.imitui
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class Imitui : SinMH("爱米推漫画", "https://www.imitui.com") {
|
||||
|
||||
override fun chapterListSelector() = ".chapter-body li > a:not([href^=/comic/app/])"
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> =
|
||||
document.select("img[onclick]").mapIndexed { index, img ->
|
||||
val url = img.attr("data-src").ifEmpty { img.attr("src") }
|
||||
Page(index, imageUrl = url)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 19 KiB |
BIN
multisrc/overrides/sinmh/manhuadui/res/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
15
multisrc/overrides/sinmh/manhuadui/src/YKMH.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package eu.kanade.tachiyomi.extension.zh.manhuadui
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
// This site blocks IP outside China
|
||||
class YKMH : SinMH("优酷漫画", "http://www.ykmh.com") {
|
||||
override val id = 1637952806167036168
|
||||
override val mobileUrl = "http://wap.ykmh.com"
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = mangaDetailsParseDMZJStyle(document, hasBreadcrumb = false)
|
||||
|
||||
override fun List<SChapter>.sortedDescending() = this
|
||||
}
|
||||
BIN
multisrc/overrides/sinmh/qinqin/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
multisrc/overrides/sinmh/qinqin/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
multisrc/overrides/sinmh/qinqin/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
multisrc/overrides/sinmh/qinqin/res/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
30
multisrc/overrides/sinmh/qinqin/src/Qinqin.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
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 org.jsoup.select.Elements
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class Qinqin : SinMH("亲亲漫画", "https://www.acgud.com") {
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/post/?page=$page", headers)
|
||||
|
||||
override fun mangaDetailsParse(document: Document) = mangaDetailsParseDMZJStyle(document, hasBreadcrumb = true)
|
||||
|
||||
override fun Elements.sectionsDescending() = this
|
||||
|
||||
// https://www.acgud.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))
|
||||
}
|
||||
}
|
||||
3
multisrc/overrides/sinmh/wuqimanga/additional.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
implementation project(':lib-unpacker')
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
BIN
multisrc/overrides/sinmh/wuqimanga/res/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
128
multisrc/overrides/sinmh/wuqimanga/src/WuqiManga.kt
Normal file
@@ -0,0 +1,128 @@
|
||||
package eu.kanade.tachiyomi.extension.zh.wuqimanga
|
||||
|
||||
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
|
||||
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"
|
||||
// Updating the domain to www.wuqimh.com causes some requests to return 404
|
||||
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("$mobileUrl/templates_pc/default/scripts/configs.js", headers)).execute().let {
|
||||
Regex("""\['(.+?)']""").find(it.body.string())!!.groupValues[1].run { "http://$this" }
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers)
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val script = document.selectFirst("body > script")!!.html()
|
||||
val unpacked = Unpacker.unpack(script, ":[", "]")
|
||||
.ifEmpty { return emptyList() }
|
||||
.replace("\\", "")
|
||||
.removeSurrounding("\"").split("\",\"")
|
||||
val list = unpacked.filterNot { it.endsWith("/ManHuaKu/222.jpg") }.map { image ->
|
||||
if (image.startsWith("http")) image else imageHost + image
|
||||
}
|
||||
if (list.isEmpty()) return emptyList()
|
||||
client.newCall(GET(list[0], headers)).execute().apply { close() }.also {
|
||||
if (!it.isSuccessful) throw Exception("该章节的图片加载出错:${it.code}")
|
||||
}
|
||||
return list.mapIndexed { i, imageUrl -> Page(i, imageUrl = imageUrl) }
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||