|
@ -1,10 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'MangaRAW'
|
|
||||||
extClass = '.MangaRAW'
|
|
||||||
themePkg = 'madara'
|
|
||||||
baseUrl = 'https://rawmanga.su'
|
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = false
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 12 KiB |
|
@ -1,17 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.ja.rawmanga
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
|
||||||
|
|
||||||
class MangaRAW : Madara(
|
|
||||||
"MangaRAW",
|
|
||||||
"https://rawmanga.su",
|
|
||||||
"ja",
|
|
||||||
) {
|
|
||||||
override val useLoadMoreRequest = LoadMoreStrategy.Never
|
|
||||||
|
|
||||||
override val useNewChapterEndpoint = true
|
|
||||||
|
|
||||||
override val filterNonMangaItems = false
|
|
||||||
|
|
||||||
override val mangaSubString = "r"
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Dida Manhua'
|
|
||||||
extClass = '.DidaManhua'
|
|
||||||
themePkg = 'mccms'
|
|
||||||
baseUrl = 'https://www.didamanhua.com/index.php'
|
|
||||||
overrideVersionCode = 1
|
|
||||||
isNsfw = true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
|
@ -1,17 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.zh.didamanhua
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mccms.MCCMS
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mccms.MCCMSConfig
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
|
|
||||||
class DidaManhua : MCCMS(
|
|
||||||
"嘀嗒漫画",
|
|
||||||
"https://www.didamanhua.com/index.php",
|
|
||||||
"zh",
|
|
||||||
MCCMSConfig(useMobilePageList = true),
|
|
||||||
) {
|
|
||||||
// Details and chapter pages are broken
|
|
||||||
override fun getMangaUrl(manga: SManga) = baseUrl
|
|
||||||
override fun getChapterUrl(chapter: SChapter) = baseUrl
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'ManhuaDB'
|
|
||||||
extClass = '.ManhuaDB'
|
|
||||||
extVersionCode = 7
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 7.1 KiB |
|
@ -1,184 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.zh.manhuadb
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
||||||
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
|
|
||||||
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.Response
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import org.jsoup.select.Evaluator
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
/** ManhuaDB: https://www.manhuadb.com/ */
|
|
||||||
abstract class MDB(
|
|
||||||
override val name: String,
|
|
||||||
override val baseUrl: String,
|
|
||||||
override val lang: String = "zh",
|
|
||||||
) : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder().rateLimit(2).build()
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl)
|
|
||||||
|
|
||||||
protected abstract fun listUrl(params: String): String
|
|
||||||
protected abstract fun extractParams(listUrl: String): String
|
|
||||||
protected abstract fun searchUrl(page: Int, query: String): String
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET(listUrl("page-$page"), headers)
|
|
||||||
override fun popularMangaSelector() = "div.comic-main-section > div.comic-book-unit"
|
|
||||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
|
||||||
val link = element.selectFirst("h2 > a")!!
|
|
||||||
setUrlWithoutDomain(link.attr("href"))
|
|
||||||
title = link.text()
|
|
||||||
thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.absUrl("src")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
parseCategories(document) // parse categories here
|
|
||||||
val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) }
|
|
||||||
val hasNextPage = popularMangaNextPageSelector()?.let { document.selectFirst(it) } != null
|
|
||||||
return MangasPage(mangas, hasNextPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
|
||||||
if (query.isNotEmpty()) {
|
|
||||||
val request = GET(searchUrl(page, query), headers)
|
|
||||||
client.newCall(request).asObservableSuccess().map { searchMangaParse(it) }
|
|
||||||
} else {
|
|
||||||
val params = filters.filterIsInstance<CategoryFilter>().map { it.getParam() }
|
|
||||||
.filterTo(mutableListOf()) { it.isNotEmpty() }.apply { add("page-$page") }
|
|
||||||
val request = GET(listUrl(params.joinToString("-")), headers)
|
|
||||||
client.newCall(request).asObservableSuccess().map { popularMangaParse(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
|
||||||
override fun searchMangaSelector() = "div.comic-main-section > div.row > div"
|
|
||||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
|
||||||
final override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
protected open fun transformTitle(title: String) = title
|
|
||||||
protected abstract val authorSelector: String
|
|
||||||
protected open fun transformDescription(description: String) = description
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
|
||||||
title = transformTitle(document.selectFirst(Evaluator.Tag("h1"))!!.text())
|
|
||||||
author = document.selectFirst(authorSelector)!!.text()
|
|
||||||
description = transformDescription(document.selectFirst("p.comic_story")!!.text())
|
|
||||||
genre = parseGenre(document).joinToString(", ")
|
|
||||||
status = when (document.selectFirst("a.comic-pub-state")!!.text()) {
|
|
||||||
"连载中" -> SManga.ONGOING
|
|
||||||
"已完结" -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
thumbnail_url = document.selectFirst("td.comic-cover > img")!!.absUrl("src")
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun parseGenre(document: Document): List<String> {
|
|
||||||
val list = mutableListOf<String>()
|
|
||||||
list.add(document.selectFirst("th:contains(地区) + td")!!.text())
|
|
||||||
list.add(document.selectFirst("th:contains(面向读者) + td")!!.text().removeSuffix("漫画"))
|
|
||||||
val tags = document.select("ul.tags > li > a")
|
|
||||||
for (i in 1 until tags.size) { // skip status
|
|
||||||
list.add(tags[i].text())
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListSelector() = "#comic-book-list li > a"
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
|
||||||
setUrlWithoutDomain(element.attr("href"))
|
|
||||||
name = element.attr("title")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
val imgData = document.selectFirst("body > script:containsData(img_data)")!!.data()
|
|
||||||
.substringAfter("img_data = ").run {
|
|
||||||
val endIndex = indexOf(this[0], startIndex = 1) // find end quote
|
|
||||||
substring(1, endIndex)
|
|
||||||
}
|
|
||||||
val readerConfig = document.selectFirst(Evaluator.Class("vg-r-data"))!!
|
|
||||||
return parseImages(imgData, readerConfig).mapIndexed { i, it ->
|
|
||||||
Page(i, imageUrl = it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract fun parseImages(imgData: String, readerConfig: Element): List<String>
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
protected class Category(val name: String, private val values: Array<String>, private val params: List<String>) {
|
|
||||||
fun toFilter() = CategoryFilter(name, values, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class CategoryFilter(name: String, values: Array<String>, private val params: List<String>) :
|
|
||||||
Filter.Select<String>(name, values) {
|
|
||||||
fun getParam() = params[state]
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var categories: List<Category>
|
|
||||||
|
|
||||||
protected open fun parseCategories(document: Document) {
|
|
||||||
if (::categories.isInitialized) return
|
|
||||||
val filters = document.select("div.search_div > div")
|
|
||||||
val list = ArrayList<Category>(filters.size + 1)
|
|
||||||
for (filter in filters) {
|
|
||||||
val children = filter.children()
|
|
||||||
val filterContainer = children[1]
|
|
||||||
if (filterContainer.hasClass("row")) { // Normal filter
|
|
||||||
val tags = filterContainer.children()
|
|
||||||
val values = ArrayList<String>(tags.size + 1).apply { add("全部") }
|
|
||||||
val params = ArrayList<String>(tags.size + 1).apply { add("") }
|
|
||||||
for (tag in tags) {
|
|
||||||
val link = tag.child(0).child(0)
|
|
||||||
values.add(link.text())
|
|
||||||
params.add(link.attr("href").let(::extractParams).let(::parseParam))
|
|
||||||
}
|
|
||||||
val name = children[0].selectFirst(Evaluator.Tag("span"))!!.text()
|
|
||||||
list.add(Category(name, values.toTypedArray(), params))
|
|
||||||
} else if (filterContainer.hasClass("form-row")) { // Dropdown filter
|
|
||||||
for (select in filterContainer.select(Evaluator.Tag("select"))) {
|
|
||||||
val options = select.children()
|
|
||||||
val values = ArrayList<String>(options.size).apply { add("全部") }
|
|
||||||
val params = ArrayList<String>(options.size).apply { add("") }
|
|
||||||
for (i in 1 until options.size) {
|
|
||||||
values.add(options[i].text())
|
|
||||||
params.add(options[i].attr("value").let(::extractParams).let(::parseParam))
|
|
||||||
}
|
|
||||||
list.add(Category(options[0].text(), values.toTypedArray(), params))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
categories = list
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseParam(params: String): String {
|
|
||||||
val parts = params.split('-')
|
|
||||||
for (i in 1 until parts.size step 2) {
|
|
||||||
if (parts[i] != "0") return "${parts[i - 1]}-${parts[i]}"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilterList() =
|
|
||||||
if (::categories.isInitialized) {
|
|
||||||
FilterList(
|
|
||||||
Filter.Header("如果使用文本搜索,将会忽略分类筛选"),
|
|
||||||
*categories.map { it.toFilter() }.toTypedArray(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
FilterList(
|
|
||||||
Filter.Header("点击“重置”即可刷新分类,如果失败,"),
|
|
||||||
Filter.Header("请尝试重新从图源列表点击进入图源"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.zh.manhuadb
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Base64
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class ManhuaDB : MDB("漫画DB", "https://www.manhuadb.com"), ConfigurableSource {
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
override fun listUrl(params: String) = "$baseUrl/manhua/list-$params.html"
|
|
||||||
override fun extractParams(listUrl: String) = listUrl.substringAfter("/list-").removeSuffix(".html")
|
|
||||||
override fun searchUrl(page: Int, query: String) = "$baseUrl/search?q=$query&p=$page"
|
|
||||||
|
|
||||||
override fun popularMangaNextPageSelector() = "nav > div.form-inline > :nth-last-child(2):not(.disabled)"
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override val authorSelector = "a.comic-creator"
|
|
||||||
override fun transformDescription(description: String) = description.substringBeforeLast("欢迎在漫画DB观看")
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response) = super.chapterListParse(response).asReversed()
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
|
||||||
|
|
||||||
// https://www.manhuadb.com/assets/js/vg-read.js
|
|
||||||
override fun parseImages(imgData: String, readerConfig: Element): List<String> {
|
|
||||||
val list: List<Image> = Base64.decode(imgData, Base64.DEFAULT)
|
|
||||||
.let { json.decodeFromString(String(it)) }
|
|
||||||
val host = readerConfig.attr("data-host")
|
|
||||||
val dir = readerConfig.attr("data-img_pre")
|
|
||||||
val useWebp = preferences.getBoolean(WEBP_PREF, true)
|
|
||||||
return list.map {
|
|
||||||
host + dir + if (useWebp && it.img_webp != null) it.img_webp else it.img
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Image(val img: String, val img_webp: String? = null)
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = WEBP_PREF
|
|
||||||
title = "优先使用 WebP 图片格式"
|
|
||||||
summary = "默认开启,可以节省网站流量"
|
|
||||||
setDefaultValue(true)
|
|
||||||
}.let { screen.addPreference(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val WEBP_PREF = "WEBP"
|
|
||||||
}
|
|
||||||
}
|
|