From d8c84d381383f57c53e15e94156f101b0509737a Mon Sep 17 00:00:00 2001 From: DitFranXX <45893338+DitFranXX@users.noreply.github.com> Date: Sun, 14 Apr 2019 12:15:26 +0900 Subject: [PATCH] Update Korean extensions (#1024) JMana: Temporary pagination fix. MSM: Improve image decoder(which don't modifying url anymore), Use `s3` and `img_list1` as fallback, Fix description issue, and Update filter and domain NewToki: Move to Factory, Add Webtoon Source(Not tested), and Update domain. --- src/ko/jmana/build.gradle | 2 +- .../tachiyomi/extension/ko/jmana/JMana.kt | 20 +++-- src/ko/mangashowme/build.gradle | 2 +- .../extension/ko/mangashowme/MSMFilters.kt | 7 +- .../ko/mangashowme/MSMImageDecoder.kt | 75 ++++++---------- .../extension/ko/mangashowme/MangaShowMe.kt | 88 ++++++++++++++++--- src/ko/newtoki/build.gradle | 4 +- .../tachiyomi/extension/ko/newtoki/NewToki.kt | 8 +- .../extension/ko/newtoki/NewTokiFactory.kt | 47 ++++++++++ 9 files changed, 175 insertions(+), 78 deletions(-) create mode 100644 src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt diff --git a/src/ko/jmana/build.gradle b/src/ko/jmana/build.gradle index 7894cf6ab..9b46c206f 100644 --- a/src/ko/jmana/build.gradle +++ b/src/ko/jmana/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: JMana' pkgNameSuffix = 'ko.jmana' extClass = '.JMana' - extVersionCode = 6 + extVersionCode = 7 libVersion = '1.2' } diff --git a/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt b/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt index a46bba186..442923a2d 100644 --- a/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt +++ b/src/ko/jmana/src/eu/kanade/tachiyomi/extension/ko/jmana/JMana.kt @@ -49,12 +49,8 @@ class JMana : ParsedHttpSource() { popularMangaFromElement(element) } - val hasNextPage = try { - val page = document.select(popularMangaNextPageSelector()) - !page[page.size - 2].getElementsByTag("a").attr("href").isNullOrEmpty() - } catch (_: Exception) { - false - } + // Can not detect what page is last page but max mangas are 40. + val hasNextPage = mangas.size == 40 return MangasPage(mangas, hasNextPage) } @@ -130,10 +126,20 @@ class JMana : ParsedHttpSource() { } override fun latestUpdatesSelector() = popularMangaSelector() - override fun latestUpdatesParse(response: Response) = popularMangaParse(response) override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/frame") override fun latestUpdatesNextPageSelector() = "" + override fun latestUpdatesParse(response: Response): MangasPage { + val document = response.asJsoup() + + val mangas = document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } + + val hasNextPage = false + + return MangasPage(mangas, hasNextPage) + } //We are able to get the image URL directly from the page list diff --git a/src/ko/mangashowme/build.gradle b/src/ko/mangashowme/build.gradle index bb18e8451..e7b3356ba 100644 --- a/src/ko/mangashowme/build.gradle +++ b/src/ko/mangashowme/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: MangaShow.Me (ManaMoa)' pkgNameSuffix = 'ko.mangashowme' extClass = '.MangaShowMe' - extVersionCode = 9 + extVersionCode = 10 libVersion = '1.2' } diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt index 88566a0f5..62e2004bb 100644 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMFilters.kt @@ -43,7 +43,6 @@ private fun searchNaming() = arrayOf( private fun searchStatus() = arrayOf( "Not Set", - "미분류", "주간", "격주", "월간", @@ -65,7 +64,6 @@ private fun searchGenres() = listOf( SearchCheckBox(0, "드라마"), SearchCheckBox(0, "라노벨"), SearchCheckBox(0, "러브코미디"), - SearchCheckBox(0, "로맨스"), SearchCheckBox(0, "먹방"), SearchCheckBox(0, "백합"), SearchCheckBox(0, "붕탁"), @@ -76,7 +74,6 @@ private fun searchGenres() = listOf( SearchCheckBox(0, "애니화"), SearchCheckBox(0, "액션"), SearchCheckBox(0, "역사"), - SearchCheckBox(0, "요리"), SearchCheckBox(0, "음악"), SearchCheckBox(0, "이세계"), SearchCheckBox(0, "일상"), @@ -94,7 +91,7 @@ fun getFilters() = FilterList( Filter.Separator(), SearchMatch(), Filter.Separator(), - TextField("Author/Artist (Exact Search)", "author") + TextField("Author/Artist (Exact search)", "author") ) fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: String, filters: FilterList): Request { @@ -107,7 +104,7 @@ fun searchComplexFilterMangaRequestBuilder(baseUrl: String, page: Int, query: St filters.forEach { filter -> when (filter) { is SearchMatch -> { - matchFilter = filter.state + matchFilter = filter.state + 1 } is TextField -> { diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt index c3ab6f755..4acf7d680 100644 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MSMImageDecoder.kt @@ -4,8 +4,10 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas import android.graphics.Rect -import eu.kanade.tachiyomi.network.GET -import okhttp3.* +import okhttp3.Interceptor +import okhttp3.MediaType +import okhttp3.Response +import okhttp3.ResponseBody import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream @@ -15,26 +17,15 @@ import java.io.InputStream * It's not need now, but it remains in this code for sometime. */ -internal class ImageDecoder(private val version: String, scripts: String) { - private val cnt = substringBetween(scripts, "var view_cnt = ", ";") +internal class ImageDecoder(scripts: String) { + private val cnt = substringBetween(scripts, "var view_cnt = ", ";") .toIntOrNull() ?: 0 private val chapter = substringBetween(scripts, "var chapter = ", ";") .toIntOrNull() ?: 0 fun request(url: String): String { - return when (version) { - "v1" -> decodeVersion1ImageUrl(cnt, chapter, url) - else -> url - } - } - - private fun decodeVersion1ImageUrl(cnt: Int, chapter: Int, url: String): String { - return HttpUrl.parse(url)!!.newBuilder() - .addQueryParameter("cnt", cnt.toString()) - .addQueryParameter("ch", chapter.toString()) - .addQueryParameter("ver", "v1") - .addQueryParameter("type", "ImageDecodeRequest") - .build()!!.toString() + if (cnt < 10) return url + return "$url??$chapter;$cnt" } } @@ -42,21 +33,18 @@ internal class ImageDecoder(private val version: String, scripts: String) { internal class ImageDecoderInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val req = chain.request() - val url = req.url().toString() - return if (url.contains("ImageDecodeRequest")) { + val response = chain.proceed(req) + + val decodeHeader = req.header("ImageDecodeRequest") + + return if (decodeHeader != null) { try { - val reqUrl = HttpUrl.parse(url)!! + val s = decodeHeader.split(";").map { it.toInt() } - val viewCnt = reqUrl.queryParameter("cnt")!! - val version = reqUrl.queryParameter("ver")!! - val chapter = reqUrl.queryParameter("ch")!! - val imageUrl = url.split("?").first() - - val response = chain.proceed(GET("$imageUrl?quick")) - if (viewCnt.toInt() < 10) return response // Pass decoder if it's not scrambled. + if (s[1] < 10) return response val res = response.body()!!.byteStream().use { - decodeImageRequest(version, chapter, viewCnt, it) + decodeImageRequest(it, s[0], s[1]) } val rb = ResponseBody.create(MediaType.parse("image/png"), res) @@ -66,19 +54,28 @@ internal class ImageDecoderInterceptor : Interceptor { throw IOException("Image decoder failure.", e) } } else { - chain.proceed(req) + response } } + private fun decodeImageRequest(img: InputStream, chapter: Int, view_cnt: Int): ByteArray { + val decoded = BitmapFactory.decodeStream(img) + val result = imageDecoder(decoded, chapter, view_cnt) + + val output = ByteArrayOutputStream() + result.compress(Bitmap.CompressFormat.PNG, 100, output) + return output.toByteArray() + } + /* - * `decodeV1ImageNative` is modified version of + * `imageDecoder` is modified version of * https://github.com/junheah/MangaViewAndroid/blob/b69a4427258fe7fc5fb5363108572bbee0d65e94/app/src/main/java/ml/melun/mangaview/mangaview/Decoder.java#L6-L60 * * MIT License * * Copyright (c) 2019 junheah */ - private fun decodeV1ImageNative(input: Bitmap, chapter: Int, view_cnt: Int, half: Int = 0, _CX: Int = MangaShowMe.V1_CX, _CY: Int = MangaShowMe.V1_CY): Bitmap { + private fun imageDecoder(input: Bitmap, chapter: Int, view_cnt: Int, half: Int = 0, _CX: Int = MangaShowMe.V1_CX, _CY: Int = MangaShowMe.V1_CY): Bitmap { if (view_cnt == 0) return input val viewCnt = view_cnt / 10 var CX = _CX @@ -159,22 +156,6 @@ internal class ImageDecoderInterceptor : Interceptor { Math.floor(1000 * (n - Math.floor(n))) + Math.floor(10000 * (a - Math.floor(a)))).toInt() } - - private fun decodeImageRequest(version: String, chapter: String, view_cnt: String, img: InputStream): ByteArray { - return when (version) { - "v1" -> decodeV1Image(chapter, view_cnt, img) - else -> img.readBytes() - } - } - - private fun decodeV1Image(chapter: String, view_cnt: String, img: InputStream): ByteArray { - val decoded = BitmapFactory.decodeStream(img) - val result = decodeV1ImageNative(decoded, chapter.toInt(), view_cnt.toInt()) - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - return output.toByteArray() - } } private fun substringBetween(target: String, prefix: String, suffix: String): String = { diff --git a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt index 357070083..726fb873a 100644 --- a/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt +++ b/src/ko/mangashowme/src/eu/kanade/tachiyomi/extension/ko/mangashowme/MangaShowMe.kt @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit **/ class MangaShowMe : ParsedHttpSource() { override val name = "MangaShow.Me" - override val baseUrl = "https://manamoa.net" + override val baseUrl = "https://manamoa2.net" override val lang: String = "ko" // Latest updates currently returns duplicate manga as it separates manga into chapters @@ -33,9 +33,38 @@ class MangaShowMe : ParsedHttpSource() { .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(ImageDecoderInterceptor()) + .addInterceptor { chain -> + val req = chain.request() + + // only for image Request + if (!req.url().host().contains("filecdn.xyz")) return@addInterceptor chain.proceed(req) + + val secondUrl = req.header("SecondUrlToRequest") + + fun get(flag: Int = 0): Request { + val url = when (flag) { + 1 -> req.url().toString().replace("img.", "s3.") + 2 -> secondUrl!! + else -> req.url().toString() + } + + return req.newBuilder()!!.url(url) + .removeHeader("ImageDecodeRequest") + .removeHeader("SecondUrlToRequest") + .build()!! + } + + val res = chain.proceed(get()) + val length = res.header("content-length") + if (length == null || length.toInt() < 50000) { + val s3res = chain.proceed(get(1)) // s3 + if (!s3res.isSuccessful && secondUrl != null) { + chain.proceed(get(2)) // secondUrl + } else s3res + } else res + } .build()!! - //override fun popularMangaSelector() = "div.basic-post-gallery > div > div.post-row" override fun popularMangaSelector() = "div.manga-list-gallery > div > div.post-row" override fun popularMangaFromElement(element: Element): SManga { @@ -84,8 +113,8 @@ class MangaShowMe : ParsedHttpSource() { val publishTypeText = thumbnailElement.select("a.publish_type").text() ?: "" val authorText = thumbnailElement.select("a.author").text() ?: "" val mangaLike = info.select("div.recommend > i.fa").first().text() ?: "0" - val mangaChaptersLike = mangaElementsSum(document.select("div.addedAt i.fa.fa-thumbs-up > span")) - val mangaComments = mangaElementsSum(document.select("div.addedAt i.fa.fa-comment > span")) + val mangaChaptersLike = mangaElementsSum(document.select(".title i.fa.fa-thumbs-up > span")) + val mangaComments = mangaElementsSum(document.select(".title i.fa.fa-comment > span")) val genres = mutableListOf() document.select("div.left-info > .manga-tags > a.tag").forEach { genres.add(it.text()) @@ -180,14 +209,30 @@ class MangaShowMe : ParsedHttpSource() { val pages = mutableListOf() try { - val element = document.select("div.col-md-9.at-col.at-main script") - val imageUrl = element.html().substringAfter("var img_list = [").substringBefore("];") + val element = document.select("div.col-md-9.at-col.at-main script").html() + val imageUrl = element.substringAfter("var img_list = [").substringBefore("];") val imageUrls = JSONArray("[$imageUrl]") - val decoder = ImageDecoder("v1", element.html()) - (0 until imageUrls.length()) - .map { imageUrls.getString(it) } - .forEach { pages.add(Page(pages.size, "", decoder.request(it))) } + val imageUrl1 = element.substringAfter("var img_list1 = [").substringBefore("];") + val imageUrls1 = JSONArray("[$imageUrl1]") + + val decoder = ImageDecoder(element) + + if (imageUrls.length() != imageUrls1.length()) { + (0 until imageUrls.length()) + .map { imageUrls.getString(it) } + .forEach { pages.add(Page(pages.size, decoder.request(it), "${it.substringBefore("!!")}?quick")) } + } else { + (0 until imageUrls.length()) + .map { + imageUrls.getString(it) + try { + "!!${imageUrls1.getString(it)}" + } catch (_: Exception) { + "" + } + } + .forEach { pages.add(Page(pages.size, decoder.request(it), "${it.substringBefore("!!")}?quick")) } + } } catch (e: Exception) { e.printStackTrace() } @@ -195,6 +240,29 @@ class MangaShowMe : ParsedHttpSource() { return pages } + override fun imageRequest(page: Page): Request { + val requestHeaders = try { + val data = page.url.substringAfter("??", "") + val secondUrl = page.url.substringAfter("!!", "").substringBefore("??") + + val builder = headers.newBuilder()!! + + if (data.isNotBlank()) { + builder.add("ImageDecodeRequest", data) + } + + if (secondUrl.isNotBlank()) { + builder.add("SecondUrlToRequest", secondUrl) + } + + builder.build()!! + } catch (_: Exception) { + headers + } + + return GET(page.imageUrl!!, requestHeaders) + } + // Latest not supported override fun latestUpdatesSelector() = throw UnsupportedOperationException("This method should not be called!") diff --git a/src/ko/newtoki/build.gradle b/src/ko/newtoki/build.gradle index 50ed45fda..46dc2f495 100644 --- a/src/ko/newtoki/build.gradle +++ b/src/ko/newtoki/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'kotlin-android' ext { appName = 'Tachiyomi: NewToki' pkgNameSuffix = 'ko.newtoki' - extClass = '.NewToki' - extVersionCode = 6 + extClass = '.NewTokiFactory' + extVersionCode = 7 libVersion = '1.2' } diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt index 5164d6802..904341eba 100644 --- a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt +++ b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewToki.kt @@ -16,9 +16,7 @@ import java.util.* /** * NewToki Source **/ -class NewToki : ParsedHttpSource() { - override val name = "NewToki" - override val baseUrl = "https://newtoki7.net" +open class NewToki(override val name: String, override val baseUrl: String, private val boardName: String) : ParsedHttpSource() { override val lang: String = "ko" override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient @@ -39,7 +37,7 @@ class NewToki : ParsedHttpSource() { override fun popularMangaNextPageSelector() = "ul.pagination > li:not(.disabled)" // Do not add page parameter if page is 1 to prevent tracking. - override fun popularMangaRequest(page: Int) = GET("$baseUrl/comic" + if (page > 1) "/p$page" else "") + override fun popularMangaRequest(page: Int) = GET("$baseUrl/$boardName" + if (page > 1) "/p$page" else "") override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() @@ -61,7 +59,7 @@ class NewToki : ParsedHttpSource() { override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) override fun searchMangaNextPageSelector() = popularMangaSelector() override fun searchMangaParse(response: Response) = popularMangaParse(response) - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comic" + (if (page > 1) "/p$page" else "") + "?stx=$query") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/$boardName" + (if (page > 1) "/p$page" else "") + "?stx=$query") override fun mangaDetailsParse(document: Document): SManga { diff --git a/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt new file mode 100644 index 000000000..c9552d547 --- /dev/null +++ b/src/ko/newtoki/src/eu/kanade/tachiyomi/extension/ko/newtoki/NewTokiFactory.kt @@ -0,0 +1,47 @@ +package eu.kanade.tachiyomi.extension.ko.newtoki + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import okhttp3.HttpUrl +import okhttp3.Request + +private const val baseDomain = "newtoki10" + +class NewTokiFactory : SourceFactory { + override fun createSources(): List = listOf( + NewTokiManga(), + NewTokiWebtoon() + ) +} + +class NewTokiManga : NewToki("NewToki", "https://$baseDomain.net", "comic") + +class NewTokiWebtoon : NewToki("NewToki (Webtoon)", "https://$baseDomain.com", "webtoon") { + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = HttpUrl.parse("$baseUrl/webtoon" + (if (page > 1) "/p$page" else ""))!!.newBuilder() + filters.forEach { filter -> + when (filter) { + is SearchTypeList -> { + if (filter.state > 0) { + url.addQueryParameter("toon", filter.values[filter.state]) + } + } + } + } + + if (!query.isBlank()) { + url.addQueryParameter("stx", query) + } + + return GET(url.toString()) + } + + private class SearchTypeList : Filter.Select("Type", arrayOf("전체", "일반웹툰", "성인웹툰", "BL/GL", "완결웹툰")) + + override fun getFilterList() = FilterList( + SearchTypeList() + ) +} \ No newline at end of file