diff --git a/src/zh/onemanhua/build.gradle b/src/zh/onemanhua/build.gradle new file mode 100644 index 000000000..fc927ae25 --- /dev/null +++ b/src/zh/onemanhua/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + appName = 'Tachiyomi: OneManHua' + pkgNameSuffix = 'zh.onemanhua' + extClass = '.Onemanhua' + extVersionCode = 1 + libVersion = '1.2' +} + +apply from: "$rootDir/common.gradle" diff --git a/src/zh/onemanhua/res/mipmap-hdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..31a389a2e Binary files /dev/null and b/src/zh/onemanhua/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/zh/onemanhua/res/mipmap-mdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..71aa63a34 Binary files /dev/null and b/src/zh/onemanhua/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/zh/onemanhua/res/mipmap-xhdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..ea719834d Binary files /dev/null and b/src/zh/onemanhua/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/zh/onemanhua/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..10db12f6a Binary files /dev/null and b/src/zh/onemanhua/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/zh/onemanhua/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/onemanhua/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..0b2ce14c7 Binary files /dev/null and b/src/zh/onemanhua/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/zh/onemanhua/res/web_hi_res_512.png b/src/zh/onemanhua/res/web_hi_res_512.png new file mode 100644 index 000000000..461735204 Binary files /dev/null and b/src/zh/onemanhua/res/web_hi_res_512.png differ diff --git a/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/Onemanhua.kt b/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/Onemanhua.kt new file mode 100644 index 000000000..972063e07 --- /dev/null +++ b/src/zh/onemanhua/src/eu/kanade/tachiyomi/extension/zh/onemanhua/Onemanhua.kt @@ -0,0 +1,211 @@ +package eu.kanade.tachiyomi.extension.zh.onemanhua + +import android.util.Base64 +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.* +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import okhttp3.HttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.lang.Error +import java.util.ArrayList +import java.util.regex.Pattern +import javax.crypto.Cipher +import javax.crypto.spec.SecretKeySpec + +class Onemanhua : ParsedHttpSource() { + override val lang = "zh" + override val supportsLatest = true + override val name = "One漫画" + override val baseUrl = "https://www.onemanhua.com" + + private var decryptKey = "JRUIFMVJDIWE569j" + private var imageServerUrl = "https://img.onemanhua.com/comic/" + + // Common + private var commonSelector = "li.fed-list-item" + private var commonNextPageSelector = "a:contains(下页):not(.fed-btns-disad)" + private fun commonMangaFromElement(element: Element): SManga { + var picElement = element.select("a.fed-list-pics").first() + var manga = SManga.create().apply { + title = element.select("a.fed-list-title").first().text() + thumbnail_url = picElement.attr("data-original") + } + + manga.setUrlWithoutDomain(picElement.attr("href")) + + return manga + } + + // Popular Manga + override fun popularMangaRequest(page: Int) = GET("$baseUrl/show?orderBy=dailyCount&page=$page", headers) + override fun popularMangaNextPageSelector() = commonNextPageSelector + override fun popularMangaSelector() = commonSelector + override fun popularMangaFromElement(element: Element) = commonMangaFromElement(element) + + // Latest Updates + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/show?orderBy=update&page=$page", headers) + override fun latestUpdatesNextPageSelector() = commonNextPageSelector + override fun latestUpdatesSelector() = commonSelector + override fun latestUpdatesFromElement(element: Element) = commonMangaFromElement(element) + + // Filter + private class StatusFilter : Filter.TriState("已完结") + private class SortFilter : Filter.Select("排序", arrayOf("更新日", "收录日", "日点击", "月点击"), 2) + override fun getFilterList() = FilterList( + SortFilter(), + StatusFilter() + ) + + // Search + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return if (query.isNotBlank()) { + GET("$baseUrl/search?searchString=$query&page=$page", headers) + } else { + val url = HttpUrl.parse("$baseUrl/show")!!.newBuilder() + url.addQueryParameter("page", page.toString()) + + filters.forEach { filter -> + when (filter) { + is StatusFilter -> { + if (!filter.isIgnored()) { + url.addQueryParameter("status", arrayOf("0", "2", "1")[filter.state]) + } + } + is SortFilter -> { + url.addQueryParameter("orderBy", arrayOf("update", "create", "dailyCount", "weeklyCount", "monthlyCount")[filter.state]) + } + } + } + GET(url.toString(), headers) + } + } + override fun searchMangaNextPageSelector() = commonNextPageSelector + override fun searchMangaSelector() = "dl.fed-deta-info, $commonSelector" + override fun searchMangaFromElement(element: Element): SManga { + if (element.tagName() == "li") { + return commonMangaFromElement(element) + } + + var picElement = element.select("a.fed-list-pics").first() + var manga = SManga.create().apply { + title = element.select("h1.fed-part-eone a").first().text() + thumbnail_url = picElement.attr("data-original") + } + + manga.setUrlWithoutDomain(picElement.attr("href")) + + return manga + } + + override fun mangaDetailsParse(document: Document): SManga { + var picElement = document.select("a.fed-list-pics").first() + var detailElements = document.select("ul.fed-part-rows li.fed-col-xs12") + var manga = SManga.create().apply { + title = document.select("h1.fed-part-eone").first().text().trim() + thumbnail_url = picElement.attr("data-original") + status = when (detailElements[0].select("a").first().text()) { + "连载中" -> SManga.ONGOING + "已完结" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + author = detailElements[1].select("a").first().text() + + genre = detailElements[4].select("a").joinToString{ it.text() } + description = detailElements[5].select(".fed-part-esan").first().text().trim() + } + + return manga + } + + override fun chapterListSelector(): String = "div:not(.fed-hidden) > div.all_data_list > ul.fed-part-rows a" + + override fun chapterFromElement(element: Element): SChapter { + val chapter = SChapter.create().apply { + name = element.attr("title") + } + chapter.setUrlWithoutDomain(element.attr("href")) + return chapter + } + + override fun imageUrlParse(document: Document) = "" + + override fun pageListParse(document: Document): List { + // 1. get C_DATA from HTML + var encodedData = getEncodedMangaData(document) + // 2. decode C_DATA by Base64 + var decodedData = String(Base64.decode(encodedData, Base64.NO_WRAP)) + // 3. decrypt C_DATA + var decryptedData = decryptAES(decodedData, decryptKey) + + val result = ArrayList() + + if (decryptedData != null) { + var imgRelativePath = getImgRelativePath(decryptedData) + var startImg = getStartImg(decryptedData) + var totalPages = getTotalPages(decryptedData) + + for(i in startImg..totalPages) { + result.add(Page(i, "", "${imageServerUrl}${imgRelativePath}${"%04d".format(i)}.jpg")) + } + } + + return result + } + + private fun getEncodedMangaData(document: Document): String? { + val scriptElements = document.getElementsByTag("script") + val pattern = Pattern.compile("C_DATA=\'(.+?)\'") + for (element in scriptElements) { + if (element.data().contains("C_DATA")) { + val matcher = pattern.matcher(element.data()) + if (matcher.find()) { + return matcher.group(1) + } + } + } + + throw Error("Unable to match for C_DATA") + } + + private fun decryptAES(value: String, key: String): String? { + val secretKey = SecretKeySpec(key.toByteArray(), "AES") + val cipher = Cipher.getInstance("AES/ECB/PKCS5Padding") + cipher.init(Cipher.DECRYPT_MODE, secretKey) + + val code = Base64.decode(value, Base64.NO_WRAP) + + return String(cipher.doFinal(code)) + } + + private fun getImgRelativePath(mangaData: String): String { + val pattern = Pattern.compile("imgpath:\"(.+?)\"") + var matcher = pattern.matcher(mangaData) + if (matcher.find()) { + return matcher.group(1) + } + + throw Error("Unable to match for imgPath") + } + + private fun getTotalPages(mangaData: String): Int { + val pattern = Pattern.compile("totalimg:([0-9]+?),") + var matcher = pattern.matcher(mangaData) + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)) + } + + throw Error("Unable to match for totalimg") + } + + private fun getStartImg(mangaData: String): Int { + val pattern = Pattern.compile("startimg:([0-9]+?),") + var matcher = pattern.matcher(mangaData) + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)) + } + + throw Error("Unable to match for startimg") + } +}