diff --git a/.run/MDBGenerator.run.xml b/.run/MDBGenerator.run.xml new file mode 100644 index 000000000..cf014fd0d --- /dev/null +++ b/.run/MDBGenerator.run.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/multisrc/overrides/mdb/manhuadb/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mdb/manhuadb/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..930fd5799 Binary files /dev/null and b/multisrc/overrides/mdb/manhuadb/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/manhuadb/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mdb/manhuadb/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..6e498d7e1 Binary files /dev/null and b/multisrc/overrides/mdb/manhuadb/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/manhuadb/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mdb/manhuadb/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..c336c12de Binary files /dev/null and b/multisrc/overrides/mdb/manhuadb/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/manhuadb/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mdb/manhuadb/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..18ace07de Binary files /dev/null and b/multisrc/overrides/mdb/manhuadb/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/manhuadb/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mdb/manhuadb/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..0918867a1 Binary files /dev/null and b/multisrc/overrides/mdb/manhuadb/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/manhuadb/res/web_hi_res_512.png b/multisrc/overrides/mdb/manhuadb/res/web_hi_res_512.png new file mode 100644 index 000000000..58e0ac626 Binary files /dev/null and b/multisrc/overrides/mdb/manhuadb/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mdb/manhuadb/src/ManhuaDB.kt b/multisrc/overrides/mdb/manhuadb/src/ManhuaDB.kt new file mode 100644 index 000000000..b63b55069 --- /dev/null +++ b/multisrc/overrides/mdb/manhuadb/src/ManhuaDB.kt @@ -0,0 +1,45 @@ +package eu.kanade.tachiyomi.extension.zh.manhuadb + +import android.util.Base64 +import eu.kanade.tachiyomi.multisrc.mdb.MDB +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.Response +import org.jsoup.nodes.Element +import org.jsoup.select.Evaluator +import org.jsoup.select.QueryParser +import uy.kohesive.injekt.injectLazy + +class ManhuaDB : MDB("漫画DB", "https://www.manhuadb.com") { + + 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("Not used.") + override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used.") + override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used.") + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used.") + + override val authorSelector: Evaluator = QueryParser.parse("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 { + val list: List = Base64.decode(imgData, Base64.DEFAULT) + .let { json.decodeFromString(String(it)) } + val host = readerConfig.attr("data-host") + val dir = readerConfig.attr("data-img_pre") + return list.map { host + dir + it["img"]!!.jsonPrimitive.content } + } +} diff --git a/multisrc/overrides/mdb/maofly/additional.gradle b/multisrc/overrides/mdb/maofly/additional.gradle new file mode 100644 index 000000000..cd52f14a8 --- /dev/null +++ b/multisrc/overrides/mdb/maofly/additional.gradle @@ -0,0 +1,3 @@ +dependencies { + implementation 'com.github.softwarevidal:lz-string4java:lz-string4java-1.0.0' +} diff --git a/multisrc/overrides/mdb/maofly/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mdb/maofly/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..85fdeae04 Binary files /dev/null and b/multisrc/overrides/mdb/maofly/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/maofly/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mdb/maofly/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..b2ec828bc Binary files /dev/null and b/multisrc/overrides/mdb/maofly/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/maofly/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mdb/maofly/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..d7bf4ec6d Binary files /dev/null and b/multisrc/overrides/mdb/maofly/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/maofly/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mdb/maofly/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..70c131597 Binary files /dev/null and b/multisrc/overrides/mdb/maofly/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/maofly/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mdb/maofly/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..44d92b323 Binary files /dev/null and b/multisrc/overrides/mdb/maofly/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/mdb/maofly/res/web_hi_res_512.png b/multisrc/overrides/mdb/maofly/res/web_hi_res_512.png new file mode 100644 index 000000000..5856d2d0d Binary files /dev/null and b/multisrc/overrides/mdb/maofly/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/mdb/maofly/src/Maofly.kt b/multisrc/overrides/mdb/maofly/src/Maofly.kt new file mode 100644 index 000000000..97baeb3e5 --- /dev/null +++ b/multisrc/overrides/mdb/maofly/src/Maofly.kt @@ -0,0 +1,57 @@ +package eu.kanade.tachiyomi.extension.zh.maofly + +import eu.kanade.tachiyomi.AppInfo +import eu.kanade.tachiyomi.multisrc.mdb.MDB +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Response +import org.jsoup.nodes.Element +import org.jsoup.select.Evaluator +import org.jsoup.select.QueryParser +import rufus.lzstring4java.LZString +import java.text.SimpleDateFormat +import java.util.Locale + +class Maofly : MDB("漫画猫", "https://www.maofly.com") { + + override val supportsLatest = true + + override fun listUrl(params: String) = "$baseUrl/list/$params.html" + override fun extractParams(listUrl: String) = listUrl.substringAfter("/list/").removeSuffix(".html") + override fun searchUrl(page: Int, query: String) = "$baseUrl/search.html?q=$query&page=$page" + + override fun popularMangaNextPageSelector() = "div.pagination > li:last-child" // in the last page it's a span + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/update-page-$page.html", headers) + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + override fun latestUpdatesSelector() = searchMangaSelector() + override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) + + override fun transformTitle(title: String) = title.run { substring(1, length - 1) } // 《title》 + override val authorSelector: Evaluator = QueryParser.parse("td.pub-duration") + override fun transformDescription(description: String) = + description.substringAfter("的漫画作品。").substringBeforeLast(" 。。欢迎您到漫画猫畅快阅读。") + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + return document.select(chapterListSelector()).map { chapterFromElement(it) }.apply { + if (!isNewDateLogic) return@apply + this[0].date_upload = document.selectFirst(dateSelector).text() + .let { dateFormat.parse(it)!!.time } + } + } + + // https://www.maofly.com/static/js/vg-read-v1.js + override fun parseImages(imgData: String, readerConfig: Element): List { + val list = LZString.decompressFromBase64(imgData).split(',') + val host = readerConfig.attr("data-chapter-domain") + return list.map { "$host/uploads/$it" } + } + + companion object { + private val dateSelector = QueryParser.parse("th:contains(上次更新) + td") + private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH) + private val isNewDateLogic = AppInfo.getVersionCode() >= 81 + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mdb/MDB.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mdb/MDB.kt new file mode 100644 index 000000000..5b85198fb --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mdb/MDB.kt @@ -0,0 +1,197 @@ +package eu.kanade.tachiyomi.multisrc.mdb + +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 org.jsoup.select.QueryParser +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.client.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(listComicLinkSelector) + setUrlWithoutDomain(link.attr("href")) + title = link.text() + thumbnail_url = element.selectFirst(imgSelector).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 = + if (query.isNotEmpty()) { + val request = GET(searchUrl(page, query), headers) + client.newCall(request).asObservableSuccess().map { searchMangaParse(it) } + } else { + val params = filters.filterIsInstance().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("Not used.") + + protected open fun transformTitle(title: String) = title + protected abstract val authorSelector: Evaluator + protected open fun transformDescription(description: String) = description + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + title = document.selectFirst(h1Selector).text().let { transformTitle(it) } + author = document.selectFirst(authorSelector).text() + description = document.selectFirst(descriptionSelector).text().let { transformDescription(it) } + genre = parseGenre(document).joinToString(", ") + status = when (document.selectFirst(statusSelector).text()) { + "连载中" -> SManga.ONGOING + "已完结" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + thumbnail_url = document.selectFirst(coverSelector).attr("src") + } + + protected open fun parseGenre(document: Document): List { + val list = mutableListOf() + list.add(document.selectFirst(regionSelector).text()) + list.add(document.selectFirst(audienceSelector).text().removeSuffix("漫画")) + val tags = document.select(tagSelector) + 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 { + val imgData = document.selectFirst(scriptSelector).data() + .substringAfter("img_data = ").run { + val endIndex = indexOf(this[0], startIndex = 1) // find end quote + substring(1, endIndex) + } + val readerConfig = document.selectFirst(readerConfigSelector) + return parseImages(imgData, readerConfig).mapIndexed { i, it -> + Page(i, imageUrl = it) + } + } + + protected abstract fun parseImages(imgData: String, readerConfig: Element): List + + override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") + + protected data class Category(val name: String, val values: Array, val params: List) { + fun toFilter() = CategoryFilter(name, values, params) + } + + protected class CategoryFilter(name: String, values: Array, val params: List) : + Filter.Select(name, values) { + fun getParam() = params[state] + } + + private lateinit var categories: List + + protected open fun parseCategories(document: Document) { + if (::categories.isInitialized) return + val filters = document.select(filterSelector) + val list = ArrayList(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(tags.size + 1).apply { add("全部") } + val params = ArrayList(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)) + } + list.add(Category(children[0].selectFirst(spanSelector).text(), values.toTypedArray(), params)) + } else if (filterContainer.hasClass("form-row")) { // Dropdown filter + for (select in filterContainer.select(selectSelector)) { + val options = select.children() + val values = ArrayList(options.size).apply { add("全部") } + val params = ArrayList(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("请尝试重新从图源列表点击进入图源"), + ) + + companion object { + private val listComicLinkSelector = QueryParser.parse("h2 > a") + private val imgSelector = Evaluator.Tag("img") + private val h1Selector = Evaluator.Tag("h1") + private val coverSelector = QueryParser.parse("td.comic-cover > img") + private val descriptionSelector = QueryParser.parse("p.comic_story") + private val tagSelector = QueryParser.parse("ul.tags > li > a") + private val statusSelector = QueryParser.parse("a.comic-pub-state") + private val regionSelector = QueryParser.parse("th:contains(地区) + td") + private val audienceSelector = QueryParser.parse("th:contains(面向读者) + td") + private val scriptSelector = QueryParser.parse("body > script:containsData(img_data)") + private val readerConfigSelector = Evaluator.Class("vg-r-data") + private val filterSelector = QueryParser.parse("div.search_div > div") + private val spanSelector = Evaluator.Tag("span") + private val selectSelector = Evaluator.Tag("select") + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mdb/MDBGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mdb/MDBGenerator.kt new file mode 100644 index 000000000..fbc6b9a54 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mdb/MDBGenerator.kt @@ -0,0 +1,21 @@ +package eu.kanade.tachiyomi.multisrc.mdb + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class MDBGenerator : ThemeSourceGenerator { + override val themeClass = "MDB" + override val themePkg = "mdb" + override val baseVersionCode = 1 + override val sources = listOf( + SingleLang("ManhuaDB", "https://www.manhuadb.com", "zh", sourceName = "漫画DB", overrideVersionCode = 3), + SingleLang("Maofly", "https://www.maofly.com", "zh", sourceName = "漫画猫", overrideVersionCode = 1), + ) + + companion object { + @JvmStatic + fun main(args: Array) { + MDBGenerator().createAll() + } + } +} diff --git a/src/zh/manhuadb/AndroidManifest.xml b/src/zh/manhuadb/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/manhuadb/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/zh/manhuadb/build.gradle b/src/zh/manhuadb/build.gradle deleted file mode 100644 index 43b3ceae8..000000000 --- a/src/zh/manhuadb/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'ManhuaDB' - pkgNameSuffix = 'zh.manhuadb' - extClass = '.ManhuaDB' - extVersionCode = 3 -} - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/manhuadb/res/mipmap-hdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 52688c673..000000000 Binary files a/src/zh/manhuadb/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-mdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 3600e9e50..000000000 Binary files a/src/zh/manhuadb/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-xhdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index d322f78ab..000000000 Binary files a/src/zh/manhuadb/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6926e3891..000000000 Binary files a/src/zh/manhuadb/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/manhuadb/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 6ef208b6f..000000000 Binary files a/src/zh/manhuadb/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/manhuadb/res/web_hi_res_512.png b/src/zh/manhuadb/res/web_hi_res_512.png deleted file mode 100644 index 99922fa8c..000000000 Binary files a/src/zh/manhuadb/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/manhuadb/src/eu/kanade/tachiyomi/extension/zh/manhuadb/ManhuaDB.kt b/src/zh/manhuadb/src/eu/kanade/tachiyomi/extension/zh/manhuadb/ManhuaDB.kt deleted file mode 100644 index 340f8cf7b..000000000 --- a/src/zh/manhuadb/src/eu/kanade/tachiyomi/extension/zh/manhuadb/ManhuaDB.kt +++ /dev/null @@ -1,111 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.manhuadb - -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 eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element -import java.util.regex.Pattern - -class ManhuaDB : ParsedHttpSource() { - - override val baseUrl = "https://www.manhuadb.com" - - override val lang = "zh" - - override val name = "漫画DB" - - override val supportsLatest = true - - override fun headersBuilder(): Headers.Builder = - super.headersBuilder().add("Referer", "https://www.manhuadb.com") - - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - name = element.attr("title") - url = element.attr("href") - } - - /** - * Rewrite the method to ensure consistency with previous format orders - */ - override fun chapterListParse(response: Response): List { - return super.chapterListParse(response).reversed() - } - - override fun chapterListSelector() = "#comic-book-list > div > ol > li > a" - - override fun imageUrlParse(document: Document): String { - return document.select("div.text-center > img.img-fluid").attr("abs:src") - } - - override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) - - override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() - - override fun latestUpdatesRequest(page: Int): Request = popularMangaRequest(page) - - override fun latestUpdatesSelector() = popularMangaSelector() - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.select("h1.comic-title").text() - thumbnail_url = document.select("td.comic-cover > img").attr("abs:src") - author = document.select("a.comic-creator").text() - description = document.select("p.comic_story").text() - status = when (document.select("td > a.comic-pub-state").text()) { - "连载中" -> SManga.ONGOING - "已完结" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - genre = document.select("ul.tags > li a").joinToString { it.text() } - } - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() - val pageStr = document.select("ol.breadcrumb > li:eq(2)").text() - val pageNumMatcher = Pattern.compile("共\\s*(\\d+)").matcher(pageStr) - if (pageNumMatcher.find()) { - val page = Integer.parseInt(pageNumMatcher.group(1)!!) - var path = document.select("ol.breadcrumb > li:eq(2) > a").attr("href") - path = path.substring(1, path.length - 5) - for (i in 0 until page) - pages.add(Page(i, "$baseUrl/${path}_p${i + 1}.html")) - } - return pages - } - - override fun popularMangaFromElement(element: Element): SManga { - val manga = SManga.create() - element.select("h2").first().let { - manga.setUrlWithoutDomain(it.select("a").first().attr("href")) - manga.title = it.text() - } - manga.thumbnail_url = element.select("a > img").attr("abs:src") - return manga - } - - override fun popularMangaNextPageSelector() = "a:contains(下页):not(.disabled)" - - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manhua/list-page-$page.html") - - override fun popularMangaSelector() = "div.comic-book-unit" - - override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.attr("title") - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.select("img").attr("abs:src") - } - - override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/search?q=$query&p=$page", headers) - } - - override fun searchMangaSelector() = "a.d-block" -} diff --git a/src/zh/maofly/AndroidManifest.xml b/src/zh/maofly/AndroidManifest.xml deleted file mode 100644 index 30deb7f79..000000000 --- a/src/zh/maofly/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/zh/maofly/build.gradle b/src/zh/maofly/build.gradle deleted file mode 100644 index 50e46a933..000000000 --- a/src/zh/maofly/build.gradle +++ /dev/null @@ -1,13 +0,0 @@ - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' - -ext { - extName = 'Maofly' - pkgNameSuffix = 'zh.maofly' - extClass = '.Maofly' - extVersionCode = 1 -} - - -apply from: "$rootDir/common.gradle" diff --git a/src/zh/maofly/res/mipmap-hdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7c276e1ef..000000000 Binary files a/src/zh/maofly/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/maofly/res/mipmap-mdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 5e1e7c879..000000000 Binary files a/src/zh/maofly/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/maofly/res/mipmap-xhdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 023e50c4d..000000000 Binary files a/src/zh/maofly/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/maofly/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 394b51683..000000000 Binary files a/src/zh/maofly/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/maofly/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 805e84b22..000000000 Binary files a/src/zh/maofly/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/zh/maofly/res/web_hi_res_512.png b/src/zh/maofly/res/web_hi_res_512.png deleted file mode 100644 index abeef0d7e..000000000 Binary files a/src/zh/maofly/res/web_hi_res_512.png and /dev/null differ diff --git a/src/zh/maofly/src/eu/kanade/tachiyomi/extension/zh/maofly/Maofly.kt b/src/zh/maofly/src/eu/kanade/tachiyomi/extension/zh/maofly/Maofly.kt deleted file mode 100644 index f92e52f3c..000000000 --- a/src/zh/maofly/src/eu/kanade/tachiyomi/extension/zh/maofly/Maofly.kt +++ /dev/null @@ -1,96 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.maofly - -import android.net.Uri -import com.squareup.duktape.Duktape -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 eu.kanade.tachiyomi.source.online.ParsedHttpSource -import okhttp3.Headers -import okhttp3.Request -import org.jsoup.nodes.Document -import org.jsoup.nodes.Element - -class Maofly : ParsedHttpSource() { - override val name: String = "漫画猫" - override val lang: String = "zh" - override val supportsLatest: Boolean = false - override val baseUrl: String = "https://www.maofly.com" - override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl) - - // Popular - - override fun popularMangaRequest(page: Int) = GET("$baseUrl/list-page-$page.html", headers) - override fun popularMangaNextPageSelector(): String? = "li.page-item > a:contains(下一页)" - override fun popularMangaSelector(): String = "div.comic-main-section > div.comic-book-unit" - override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply { - title = element.selectFirst("h2 > a").text() - setUrlWithoutDomain(element.selectFirst("h2 > a").attr("href")) - thumbnail_url = element.selectFirst("img").attr("src") - } - - // Latest - - override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used.") - override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used.") - override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used.") - override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used.") - - // Search - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val uri = Uri.parse(baseUrl).buildUpon() - .appendPath("search.html") - .appendQueryParameter("q", query) - .appendQueryParameter("page", page.toString()) - return GET(uri.toString(), headers) - } - - override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector() - override fun searchMangaSelector(): String = "div.comic-main-section > div > div" - override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element) - - // Details - - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - title = document.selectFirst("h1.comic-title").text().dropLast(1).drop(1) - thumbnail_url = document.selectFirst("td.comic-cover > img").attr("abs:src") - author = document.selectFirst("td.pub-duration").text() - artist = author - description = document.selectFirst("div.comic-info > p.comic_story").text() - genre = document.select("div.comic-info > ul.tags > li").eachText().joinToString(", ") - status = when { - !document.select("td.comic-titles > a:contains(已完结)").isEmpty() -> SManga.COMPLETED - !document.select("td.comic-titles > a:contains(连载中)").isEmpty() -> SManga.ONGOING - else -> SManga.UNKNOWN - } - } - - // Chapters - - override fun chapterListSelector(): String = "div#comic-book-list li.sort_div" - override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { - setUrlWithoutDomain(element.select("a").attr("href")) - name = element.select("a").text() - } - - // Pages - - override fun pageListParse(document: Document): List { - val script = document.selectFirst("script:containsData(img_data)").data() - val imgData = script.substringAfter("img_data = ").substringBefore("\n") - val decoded = Duktape.create().use { it.evaluate("${LZSTRING}LZString.decompressFromBase64($imgData)").toString() } - val imgServer = document.selectFirst("div.vg-r-data").attr("data-chapter-domain") - return decoded.split(",").mapIndexed { i, url -> - Page(i, "", "$imgServer/uploads/$url") - } - } - - override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used.") - - companion object { - const val LZSTRING = """var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);""" - } -}